[
  {
    "path": ".gitattributes",
    "content": "# Set the default behavior, in case people don't have core.autocrlf set.\n* text eol=lf\n\n# Explicitly declare text files you want to always be normalized and converted\n# to native line endings on checkout.\n# *.c text\n# *.h text\n\n# Declare files that will always have CRLF line endings on checkout.\n*.cmd text eol=crlf\n*.bat text eol=crlf\n\n# Denote all files that are truly binary and should not be modified.\ntools/** binary\ntools/rustup-wrapper/** -binary\ntools/elf-cleaner/** -binary\n*.jar binary\n*.exe binary\n*.apk binary\n*.png binary\n*.jpg binary\n*.ttf binary\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n<!--\n\n## READ BEFORE OPENING ISSUES\n\nAll bug reports require you to **USE DEBUG BUILDS**. Please include the version name and version code in the bug report.\n\nIf you experience a bootloop, attach a `dmesg` (kernel logs) when the device refuse to boot. This may very likely require a custom kernel on some devices as `last_kmsg` or `pstore ramoops` are usually not enabled by default. In addition, please also upload the result of `cat /proc/mounts` when your device is working correctly **WITHOUT MAGISK**.\n\nIf you experience issues during installation, in recovery, upload the recovery logs, or in Magisk, upload the install logs. Please also upload the `boot.img` or `recovery.img` that you are using for patching.\n\nIf you experience a crash of Magisk app, dump the full `logcat` **when the crash happens**.\n\nIf you experience other issues related to Magisk, upload `magisk.log`, and preferably also include a boot `logcat` (start dumping `logcat` when the device boots up)\n\n**DO NOT** open issues regarding root detection.\n\n**DO NOT** ask for instructions.\n\n**DO NOT** report issues if you have any modules installed.\n\nWithout following the rules above, your issue will be closed without explanation.\n\n-->\n\nDevice:\nAndroid version:\nMagisk version name:\nMagisk version code:\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: XDA Community Support\n    url: https://forum.xda-developers.com/f/magisk.5903/\n    about: Please ask and answer questions here.\n\n"
  },
  {
    "path": ".github/actions/setup/action.yml",
    "content": "name: Magisk Setup\ndescription: Set up the build environment for Magisk\ninputs:\n  is-asset-build:\n    required: false\n    default: false\nruns:\n  using: \"composite\"\n  steps:\n    - name: Set up JDK 21\n      uses: actions/setup-java@v5\n      with:\n        distribution: \"temurin\"\n        java-version: \"21\"\n\n    - name: Set up Python 3\n      uses: actions/setup-python@v6\n      with:\n        python-version: \"3.x\"\n\n    - name: Install GNU make\n      if: runner.os == 'macOS'\n      shell: bash\n      run: |\n        brew install make\n        echo 'GNUMAKE=gmake' >> \"$GITHUB_ENV\"\n\n    - name: Cache sccache\n      uses: actions/cache@v5\n      if: ${{ github.event_name != 'pull_request' }}\n      with:\n        path: .sccache\n        key: sccache-${{ runner.os }}-${{ github.sha }}\n        restore-keys: sccache-${{ runner.os }}-\n\n    - name: Restore sccache\n      uses: actions/cache/restore@v5\n      if: ${{ github.event_name == 'pull_request' }}\n      with:\n        path: .sccache\n        key: sccache-${{ runner.os }}-${{ github.sha }}\n        restore-keys: sccache-${{ runner.os }}-\n\n    - name: Set up sccache\n      shell: bash\n      env:\n        SCCACHE_DIRECT: false\n        SCCACHE_DIR: ${{ github.workspace }}/.sccache\n        SCCACHE_CACHE_SIZE: ${{ inputs.is-asset-build == 'true' && '2G' || '300M'  }}\n        SCCACHE_IDLE_TIMEOUT: 0\n      run: |\n        bash $GITHUB_ACTION_PATH/sccache.sh\n        sccache --start-server\n        sccache -z\n\n    - name: Show sccache stats\n      uses: gacts/run-and-post-run@v1\n      with:\n        run: sccache -s\n        post: sccache -s\n\n    - name: Set GRADLE_USER_HOME\n      shell: bash\n      run: echo \"GRADLE_USER_HOME=$GITHUB_WORKSPACE/.gradle\" >> \"$GITHUB_ENV\"\n\n    - name: Cache Gradle dependencies\n      uses: actions/cache@v5\n      if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}\n      with:\n        path: |\n          .gradle/caches\n          .gradle/wrapper\n          !.gradle/caches/build-cache-*\n        key: gradle-cache-${{ hashFiles('app/gradle/**') }}\n        restore-keys: gradle-cache-\n\n    - name: Restore Gradle dependencies\n      uses: actions/cache/restore@v5\n      if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}\n      with:\n        path: |\n          .gradle/caches\n          .gradle/wrapper\n          !.gradle/caches/build-cache-*\n        key: gradle-cache-${{ hashFiles('gradle/**') }}\n        restore-keys: gradle-cache-\n        enableCrossOsArchive: true\n\n    - name: Cache Gradle build cache\n      uses: actions/cache@v5\n      if: ${{ inputs.is-asset-build == 'true' && github.event_name != 'pull_request' }}\n      with:\n        path: .gradle/caches/build-cache-*\n        key: gradle-build-cache-${{ github.sha }}\n        restore-keys: gradle-build-cache-\n\n    - name: Restore Gradle build cache\n      uses: actions/cache/restore@v5\n      if: ${{ inputs.is-asset-build == 'false' || github.event_name == 'pull_request' }}\n      with:\n        path: .gradle/caches/build-cache-*\n        key: gradle-build-cache-${{ github.sha }}\n        restore-keys: gradle-build-cache-\n        enableCrossOsArchive: true\n\n    - name: Set up NDK\n      shell: bash\n      run: python build.py -v ndk\n"
  },
  {
    "path": ".github/actions/setup/sccache.sh",
    "content": "#!/usr/bin/env bash\n\n# Get latest sccache version\nget_sccache_ver() {\n  curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name\n}\n\n# $1=variant\n# $2=install_dir\n# $3=exe\ninstall_from_gh() {\n  local ver=$(curl -sL 'https://api.github.com/repos/mozilla/sccache/releases/latest' | jq -r .name)\n  local url=\"https://github.com/mozilla/sccache/releases/download/${ver}/sccache-${ver}-$1.tar.gz\"\n  local dest=\"$2/$3\"\n  curl -L \"$url\" | tar xz -O --wildcards \"*/$3\" > $dest\n  chmod +x $dest\n}\n\nif [ $RUNNER_OS = \"macOS\" ]; then\n  brew install sccache\nelif [ $RUNNER_OS = \"Linux\" ]; then\n  install_from_gh x86_64-unknown-linux-musl /usr/local/bin sccache\nelif [ $RUNNER_OS = \"Windows\" ]; then\n  install_from_gh x86_64-pc-windows-msvc $USERPROFILE/.cargo/bin sccache.exe\nfi\n"
  },
  {
    "path": ".github/ci.prop",
    "content": "abiList=arm64-v8a\n"
  },
  {
    "path": ".github/kvm.sh",
    "content": "#!/usr/bin/env bash\n\necho 'KERNEL==\"kvm\", GROUP=\"kvm\", MODE=\"0666\", OPTIONS+=\"static_node=kvm\"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules\nsudo udevadm control --reload-rules\nsudo udevadm trigger\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Magisk Build\n\non:\n  push:\n    branches: [master]\n    paths:\n      - \"app/**\"\n      - \"native/**\"\n      - \"build.py\"\n      - \".github/workflows/build.yml\"\n  pull_request:\n    branches: [master]\n  workflow_dispatch:\n\njobs:\n  build:\n    name: Build Magisk artifacts\n    runs-on: macos-26\n    strategy:\n      fail-fast: false\n    steps:\n      - name: Check out\n        uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Setup environment\n        uses: ./.github/actions/setup\n        with:\n          is-asset-build: true\n\n      - name: Build release\n        run: ./build.py -vr all\n\n      - name: Build debug\n        run: ./build.py -v all\n\n      - name: Stop gradle daemon\n        run: ./app/gradlew --stop\n\n      - name: Upload build artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ github.sha }}\n          path: out\n          compression-level: 9\n\n      - name: Upload mapping and native debug symbols\n        uses: actions/upload-artifact@v7\n        with:\n          name: ${{ github.sha }}-symbols\n          path: app/apk/build/outputs\n          compression-level: 9\n\n  test-build:\n    name: Test building on ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [windows-2025, ubuntu-24.04]\n    steps:\n      - name: Check out\n        uses: actions/checkout@v6\n        with:\n          submodules: \"recursive\"\n\n      - name: Setup environment\n        uses: ./.github/actions/setup\n\n      - name: Test build\n        run: python build.py -v -c .github/ci.prop all\n\n      - name: Stop gradle daemon\n        run: ./app/gradlew --stop\n\n  avd-test:\n    name: Test API ${{ matrix.version }} (x86_64)\n    runs-on: ubuntu-24.04\n    needs: build\n    if: ${{ github.event_name != 'push' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36.1, \"CANARY\"]\n        type: [\"\"]\n        include:\n          - version: \"CinnamonBun\"\n            type: \"google_apis_ps16k\"\n\n    steps:\n      - name: Check out\n        uses: actions/checkout@v6\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: ${{ github.sha }}\n          path: out\n\n      - name: Enable KVM group perms\n        run: .github/kvm.sh\n\n      - name: Run AVD test\n        timeout-minutes: 18\n        run: scripts/avd.sh test -l -v ${{ matrix.version }} -t ${{ matrix.type }}\n\n      - name: Upload logs on error\n        if: ${{ failure() }}\n        uses: actions/upload-artifact@v7\n        with:\n          name: \"avd-logs-${{ matrix.version }}\"\n          path: |\n            kernel.log\n            logcat.log\n\n  avd-test-32:\n    name: Test API ${{ matrix.version }} (x86)\n    runs-on: ubuntu-24.04\n    needs: build\n    if: ${{ github.event_name != 'push' }}\n    strategy:\n      fail-fast: false\n      matrix:\n        version: [23, 24, 25, 26, 27, 28, 29, 30]\n\n    steps:\n      - name: Check out\n        uses: actions/checkout@v6\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: ${{ github.sha }}\n          path: out\n\n      - name: Enable KVM group perms\n        run: .github/kvm.sh\n\n      - name: Run AVD test\n        timeout-minutes: 18\n        env:\n          FORCE_32_BIT: 1\n        run: scripts/avd.sh test -l -v ${{ matrix.version }}\n\n      - name: Upload logs on error\n        if: ${{ failure() }}\n        uses: actions/upload-artifact@v7\n        with:\n          name: \"avd32-logs-${{ matrix.version }}\"\n          path: |\n            kernel.log\n            logcat.log\n\n  cf-test:\n    name: Test ${{ matrix.device }}\n    runs-on: ubuntu-24.04\n    needs: build\n    if: ${{ github.event_name != 'push' }}\n    env:\n      CF_HOME: /home/runner/aosp_cf_phone\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - branch: \"aosp-android-latest-release\"\n            device: \"aosp_cf_x86_64_only_phone\"\n\n    steps:\n      - name: Check out\n        uses: actions/checkout@v6\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v8\n        with:\n          name: ${{ github.sha }}\n          path: out\n\n      - name: Enable KVM group perms\n        run: .github/kvm.sh\n\n      - name: Setup Cuttlefish environment\n        run: |\n          scripts/cuttlefish.sh setup\n          scripts/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}\n\n      - name: Run Cuttlefish test\n        timeout-minutes: 18\n        run: sudo -E -u $USER scripts/cuttlefish.sh test\n\n      - name: Upload logs on error\n        if: ${{ failure() }}\n        uses: actions/upload-artifact@v7\n        with:\n          name: \"cvd-logs-${{ matrix.device }}\"\n          path: |\n            /home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs\n            /home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json\n"
  },
  {
    "path": ".gitignore",
    "content": "out\n*.zip\n*.jks\n*.apk\n*.log\n/config.prop\n/notes.md\n\n# Built binaries\nnative/out\n\n# Android Studio\n*.iml\n.idea\n.cursor\nramdisk.img\napp/core/src/debug\napp/core/src/release\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"selinux\"]\n\tpath = native/src/external/selinux\n\turl = https://github.com/topjohnwu/selinux.git\n[submodule \"lz4\"]\n\tpath = native/src/external/lz4\n\turl = https://github.com/lz4/lz4.git\n[submodule \"libcxx\"]\n\tpath = native/src/external/libcxx\n\turl = https://github.com/topjohnwu/libcxx.git\n[submodule \"cxx-rs\"]\n\tpath = native/src/external/cxx-rs\n\turl = https://github.com/topjohnwu/cxx.git\n[submodule \"lsplt\"]\n\tpath = native/src/external/lsplt\n\turl = https://github.com/LSPosed/LSPlt.git\n[submodule \"system_properties\"]\n\tpath = native/src/external/system_properties\n\turl = https://github.com/topjohnwu/system_properties.git\n[submodule \"crt0\"]\n\tpath = native/src/external/crt0\n\turl = https://github.com/topjohnwu/crt0.git\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.MD",
    "content": "![](docs/images/logo.png)\n\n[![Downloads](https://img.shields.io/badge/dynamic/json?color=green&label=Downloads&query=totalString&url=https%3A%2F%2Fraw.githubusercontent.com%2Ftopjohnwu%2Fmagisk-files%2Fcount%2Fcount.json&cacheSeconds=1800)](https://raw.githubusercontent.com/topjohnwu/magisk-files/count/count.json)\n\n#### This is not an officially supported Google product\n\n## Introduction\n\nMagisk is a suite of open source software for customizing Android, supporting devices higher than Android 6.0.<br>\nSome highlight features:\n\n- **MagiskSU**: Provide root access for applications\n- **Magisk Modules**: Modify read-only partitions by installing modules\n- **MagiskBoot**: The most complete tool for unpacking and repacking Android boot images\n- **Zygisk**: Run code in every Android applications' processes\n\n## Downloads\n\n[Github](https://github.com/topjohnwu/Magisk/releases) is the only source where you can get official Magisk information and downloads.\n\n## Useful Links\n\n- [Installation Instruction](https://topjohnwu.github.io/Magisk/install.html)\n- [Building and Development](https://topjohnwu.github.io/Magisk/build.html)\n- [Magisk Documentation](https://topjohnwu.github.io/Magisk/)\n- [Zygisk module sample](https://github.com/topjohnwu/zygisk-module-sample)\n\n## Bug Reports\n\n**Only bug reports from Debug builds will be accepted.**\n\nFor installation issues, upload both boot image and install logs.<br>\nFor Magisk issues, upload boot logcat or dmesg.<br>\nFor Magisk app crashes, record and upload the logcat when the crash occurs.\n\n## Translation Contributions\n\nDefault string resources for the Magisk app and its stub APK are located here:\n\n- `app/core/src/main/res/values/strings.xml`\n- `app/stub/src/main/res/values/strings.xml`\n\nTranslate each and place them in the respective locations (`[module]/src/main/res/values-[lang]/strings.xml`).\n\n## License\n\n    Magisk, including all git submodules are free software:\n    you can redistribute it and/or modify it under the terms of the\n    GNU General Public License as published by the Free Software Foundation,\n    either version 3 of the License, or (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/dict.txt\n\n# Gradle\n.gradle\n.kotlin\nbuild\n/local.properties\n"
  },
  {
    "path": "app/apk/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n    kotlin(\"plugin.parcelize\")\n    alias(libs.plugins.legacy.kapt)\n    alias(libs.plugins.navigation.safeargs)\n}\n\nsetupMainApk()\n\nkapt {\n    correctErrorTypes = true\n    useBuildCache = true\n    mapDiagnosticLocations = true\n    javacOptions {\n        option(\"-Xmaxerrs\", \"1000\")\n    }\n}\n\nandroid {\n    buildFeatures {\n        dataBinding = true\n    }\n\n    compileOptions {\n        isCoreLibraryDesugaringEnabled = true\n    }\n\n    defaultConfig {\n        proguardFile(\"proguard-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n        }\n    }\n}\n\ndependencies {\n    implementation(project(\":core\"))\n    coreLibraryDesugaring(libs.jdk.libs)\n\n    implementation(libs.indeterminate.checkbox)\n    implementation(libs.rikka.layoutinflater)\n    implementation(libs.rikka.insets)\n    implementation(libs.rikka.recyclerview)\n\n    implementation(libs.navigation.fragment.ktx)\n    implementation(libs.navigation.ui.ktx)\n\n    implementation(libs.constraintlayout)\n    implementation(libs.swiperefreshlayout)\n    implementation(libs.recyclerview)\n    implementation(libs.transition)\n    implementation(libs.fragment.ktx)\n    implementation(libs.appcompat)\n    implementation(libs.material)\n\n    // Make sure kapt runs with a proper kotlin-stdlib\n    kapt(kotlin(\"stdlib\"))\n}\n"
  },
  {
    "path": "app/apk/proguard-rules.pro",
    "content": "# Excessive obfuscation\n-flattenpackagehierarchy\n-allowaccessmodification\n"
  },
  {
    "path": "app/apk/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application android:localeConfig=\"@xml/locale_config\">\n        <activity\n            android:name=\".ui.MainActivity\"\n            android:exported=\"true\"\n            android:theme=\"@style/SplashTheme\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.APPLICATION_PREFERENCES\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".ui.surequest.SuRequestActivity\"\n            android:directBootAware=\"true\"\n            android:exported=\"false\"\n            android:taskAffinity=\"\"\n            tools:ignore=\"AppLinkUrlError\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport androidx.annotation.MainThread\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\n\nabstract class AsyncLoadViewModel : BaseViewModel() {\n\n    private var loadingJob: Job? = null\n\n    @MainThread\n    fun startLoading() {\n        if (loadingJob?.isActive == true) {\n            // Prevent multiple jobs from running at the same time\n            return\n        }\n        loadingJob = viewModelScope.launch { doLoadWork() }\n    }\n\n    protected abstract suspend fun doLoadWork()\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseFragment.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.MenuProvider\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.OnRebindCallback\nimport androidx.databinding.ViewDataBinding\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.navigation.NavDirections\nimport com.topjohnwu.magisk.BR\n\nabstract class BaseFragment<Binding : ViewDataBinding> : Fragment(), ViewModelHolder {\n\n    val activity get() = getActivity() as? NavigationActivity<*>\n    protected lateinit var binding: Binding\n    protected abstract val layoutRes: Int\n\n    private val navigation get() = activity?.navigation\n    open val snackbarView: View? get() = null\n    open val snackbarAnchorView: View? get() = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        startObserveLiveData()\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        binding = DataBindingUtil.inflate<Binding>(inflater, layoutRes, container, false).also {\n            it.setVariable(BR.viewModel, viewModel)\n            it.lifecycleOwner = viewLifecycleOwner\n        }\n        if (this is MenuProvider) {\n            activity?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.STARTED)\n        }\n        savedInstanceState?.let { viewModel.onRestoreState(it) }\n        return binding.root\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        viewModel.onSaveState(outState)\n    }\n\n    override fun onStart() {\n        super.onStart()\n        activity?.supportActionBar?.subtitle = null\n    }\n\n    override fun onEventDispatched(event: ViewEvent) = when(event) {\n        is ContextExecutor -> event(requireContext())\n        is ActivityExecutor -> activity?.let { event(it) } ?: Unit\n        is FragmentExecutor -> event(this)\n        else -> Unit\n    }\n\n    open fun onKeyEvent(event: KeyEvent): Boolean {\n        return false\n    }\n\n    open fun onBackPressed(): Boolean = false\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        binding.addOnRebindCallback(object : OnRebindCallback<Binding>() {\n            override fun onPreBind(binding: Binding): Boolean {\n                this@BaseFragment.onPreBind(binding)\n                return true\n            }\n        })\n    }\n\n    override fun onResume() {\n        super.onResume()\n        viewModel.let {\n            if (it is AsyncLoadViewModel)\n                it.startLoading()\n        }\n    }\n\n    protected open fun onPreBind(binding: Binding) {\n        (binding.root as? ViewGroup)?.startAnimations()\n    }\n\n    fun NavDirections.navigate() {\n        navigation?.currentDestination?.getAction(actionId)?.let { navigation!!.navigate(this) }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.Manifest.permission.POST_NOTIFICATIONS\nimport android.Manifest.permission.REQUEST_INSTALL_PACKAGES\nimport android.Manifest.permission.WRITE_EXTERNAL_STORAGE\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport androidx.databinding.PropertyChangeRegistry\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport androidx.navigation.NavDirections\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.databinding.ObservableHost\nimport com.topjohnwu.magisk.events.BackPressEvent\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.events.DialogEvent\nimport com.topjohnwu.magisk.events.NavigationEvent\nimport com.topjohnwu.magisk.events.PermissionEvent\nimport com.topjohnwu.magisk.events.SnackbarEvent\n\nabstract class BaseViewModel : ViewModel(), ObservableHost {\n\n    override var callbacks: PropertyChangeRegistry? = null\n\n    private val _viewEvents = MutableLiveData<ViewEvent>()\n    val viewEvents: LiveData<ViewEvent> get() = _viewEvents\n\n    open fun onSaveState(state: Bundle) {}\n    open fun onRestoreState(state: Bundle) {}\n    open fun onNetworkChanged(network: Boolean) {}\n\n    fun withPermission(permission: String, callback: (Boolean) -> Unit) {\n        PermissionEvent(permission, callback).publish()\n    }\n\n    inline fun withExternalRW(crossinline callback: () -> Unit) {\n        withPermission(WRITE_EXTERNAL_STORAGE) {\n            if (!it) {\n                SnackbarEvent(R.string.external_rw_permission_denied).publish()\n            } else {\n                callback()\n            }\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    inline fun withInstallPermission(crossinline callback: () -> Unit) {\n        withPermission(REQUEST_INSTALL_PACKAGES) {\n            if (!it) {\n                SnackbarEvent(R.string.install_unknown_denied).publish()\n            } else {\n                callback()\n            }\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    inline fun withPostNotificationPermission(crossinline callback: () -> Unit) {\n        withPermission(POST_NOTIFICATIONS) {\n            if (!it) {\n                SnackbarEvent(R.string.post_notifications_denied).publish()\n            } else {\n                callback()\n            }\n        }\n    }\n\n    fun back() = BackPressEvent().publish()\n\n    fun ViewEvent.publish() {\n        _viewEvents.postValue(this)\n    }\n\n    fun DialogBuilder.show() {\n        DialogEvent(this).publish()\n    }\n\n    fun NavDirections.navigate(pop: Boolean = false) {\n        _viewEvents.postValue(NavigationEvent(this, pop))\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/NavigationActivity.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.content.ContentResolver\nimport android.view.KeyEvent\nimport androidx.databinding.ViewDataBinding\nimport androidx.navigation.NavController\nimport androidx.navigation.NavDirections\nimport androidx.navigation.fragment.NavHostFragment\nimport androidx.navigation.navOptions\nimport com.topjohnwu.magisk.utils.AccessibilityUtils\n\nabstract class NavigationActivity<Binding : ViewDataBinding> : UIActivity<Binding>() {\n\n    abstract val navHostId: Int\n\n    private val navHostFragment by lazy {\n        supportFragmentManager.findFragmentById(navHostId) as NavHostFragment\n    }\n\n    protected val currentFragment get() =\n        navHostFragment.childFragmentManager.fragments.getOrNull(0) as? BaseFragment<*>\n\n    val navigation: NavController get() = navHostFragment.navController\n\n    override fun dispatchKeyEvent(event: KeyEvent): Boolean {\n        return if (binded && currentFragment?.onKeyEvent(event) == true) true else super.dispatchKeyEvent(event)\n    }\n\n    override fun onBackPressed() {\n        if (binded) {\n            if (currentFragment?.onBackPressed() == false) {\n                super.onBackPressed()\n            }\n        }\n    }\n\n    companion object {\n        fun navigate(directions: NavDirections, navigation: NavController, cr: ContentResolver) {\n            if (AccessibilityUtils.isAnimationEnabled(cr)) {\n                navigation.navigate(directions)\n            } else {\n                navigation.navigate(directions, navOptions {})\n            }\n        }\n    }\n\n    fun NavDirections.navigate() {\n        navigate(this, navigation, contentResolver)\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/UIActivity.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.content.res.use\nimport androidx.core.view.WindowCompat\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.ViewDataBinding\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.transition.AutoTransition\nimport androidx.transition.TransitionManager\nimport com.google.android.material.snackbar.Snackbar\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.base.ActivityExtension\nimport com.topjohnwu.magisk.core.base.IActivityExtension\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.reflectField\nimport com.topjohnwu.magisk.core.wrap\nimport rikka.insets.WindowInsetsHelper\nimport rikka.layoutinflater.view.LayoutInflaterFactory\n\nabstract class UIActivity<Binding : ViewDataBinding>\n    : AppCompatActivity(), ViewModelHolder, IActivityExtension {\n\n    protected lateinit var binding: Binding\n    protected abstract val layoutRes: Int\n    override val extension = ActivityExtension(this)\n\n    protected val binded get() = ::binding.isInitialized\n\n    open val snackbarView get() = binding.root\n    open val snackbarAnchorView: View? get() = null\n\n    init {\n        AppCompatDelegate.setDefaultNightMode(Config.darkTheme)\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base.wrap())\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        layoutInflater.factory2 = LayoutInflaterFactory(delegate)\n            .addOnViewCreatedListener(WindowInsetsHelper.LISTENER)\n\n        extension.onCreate(savedInstanceState)\n        if (isRunningAsStub) {\n            // Overwrite private members to avoid nasty \"false\" stack traces being logged\n            val delegate = delegate\n            val clz = delegate.javaClass\n            clz.reflectField(\"mActivityHandlesConfigFlagsChecked\").set(delegate, true)\n            clz.reflectField(\"mActivityHandlesConfigFlags\").set(delegate, 0)\n        }\n\n        super.onCreate(savedInstanceState)\n\n        startObserveLiveData()\n\n        // We need to set the window background explicitly since for whatever reason it's not\n        // propagated upstream\n        obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))\n            .use { it.getDrawable(0) }\n            .also { window.setBackgroundDrawable(it) }\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            window?.decorView?.post {\n                // If navigation bar is short enough (gesture navigation enabled), make it transparent\n                if ((window.decorView.rootWindowInsets?.systemWindowInsetBottom\n                        ?: 0) < Resources.getSystem().displayMetrics.density * 40) {\n                    window.navigationBarColor = Color.TRANSPARENT\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                        window.navigationBarDividerColor = Color.TRANSPARENT\n                    }\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                        window.isNavigationBarContrastEnforced = false\n                        window.isStatusBarContrastEnforced = false\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        extension.onSaveInstanceState(outState)\n    }\n\n    fun setContentView() {\n        binding = DataBindingUtil.setContentView<Binding>(this, layoutRes).also {\n            it.setVariable(BR.viewModel, viewModel)\n            it.lifecycleOwner = this\n        }\n    }\n\n    fun setAccessibilityDelegate(delegate: View.AccessibilityDelegate?) {\n        binding.root.rootView.accessibilityDelegate = delegate\n    }\n\n    fun showSnackbar(\n        message: CharSequence,\n        length: Int = Snackbar.LENGTH_SHORT,\n        builder: Snackbar.() -> Unit = {}\n    ) = Snackbar.make(snackbarView, message, length)\n        .setAnchorView(snackbarAnchorView).apply(builder).show()\n\n    override fun onResume() {\n        super.onResume()\n        viewModel.let {\n            if (it is AsyncLoadViewModel)\n                it.startLoading()\n        }\n    }\n\n    override fun onEventDispatched(event: ViewEvent) = when (event) {\n        is ContextExecutor -> event(this)\n        is ActivityExecutor -> event(this)\n        else -> Unit\n    }\n}\n\nfun ViewGroup.startAnimations() {\n    val transition = AutoTransition()\n        .setInterpolator(FastOutSlowInInterpolator())\n        .setDuration(400)\n        .excludeTarget(R.id.main_toolbar, true)\n    TransitionManager.beginDelayedTransition(\n        this,\n        transition\n    )\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewEvent.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.content.Context\n\n/**\n * Class for passing events from ViewModels to Activities/Fragments\n * (see https://medium.com/google-developers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150)\n */\nabstract class ViewEvent\n\ninterface ContextExecutor {\n    operator fun invoke(context: Context)\n}\n\ninterface ActivityExecutor {\n    operator fun invoke(activity: UIActivity<*>)\n}\n\ninterface FragmentExecutor {\n    operator fun invoke(fragment: BaseFragment<*>)\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/arch/ViewModelHolder.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.ViewModelStoreOwner\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.ui.home.HomeViewModel\nimport com.topjohnwu.magisk.ui.install.InstallViewModel\nimport com.topjohnwu.magisk.ui.log.LogViewModel\nimport com.topjohnwu.magisk.ui.superuser.SuperuserViewModel\nimport com.topjohnwu.magisk.ui.surequest.SuRequestViewModel\n\ninterface ViewModelHolder : LifecycleOwner, ViewModelStoreOwner {\n\n    val viewModel: BaseViewModel\n\n    fun startObserveLiveData() {\n        viewModel.viewEvents.observe(this, this::onEventDispatched)\n        Info.isConnected.observe(this, viewModel::onNetworkChanged)\n    }\n\n    /**\n     * Called for all [ViewEvent]s published by associated viewModel.\n     */\n    fun onEventDispatched(event: ViewEvent) {}\n}\n\nobject VMFactory : ViewModelProvider.Factory {\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T : ViewModel> create(modelClass: Class<T>): T {\n        return when (modelClass) {\n            HomeViewModel::class.java -> HomeViewModel(ServiceLocator.networkService)\n            LogViewModel::class.java -> LogViewModel(ServiceLocator.logRepo)\n            SuperuserViewModel::class.java -> SuperuserViewModel(ServiceLocator.policyDB)\n            InstallViewModel::class.java ->\n                InstallViewModel(ServiceLocator.networkService, ServiceLocator.markwon)\n            SuRequestViewModel::class.java ->\n                SuRequestViewModel(ServiceLocator.policyDB, ServiceLocator.timeoutPrefs)\n            else -> modelClass.newInstance()\n        } as T\n    }\n}\n\ninline fun <reified VM : ViewModel> ViewModelHolder.viewModel() =\n    lazy(LazyThreadSafetyMode.NONE) {\n        ViewModelProvider(this, VMFactory)[VM::class.java]\n    }\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/DataBindingAdapters.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport android.animation.ValueAnimator\nimport android.content.res.ColorStateList\nimport android.graphics.Paint\nimport android.graphics.drawable.Drawable\nimport android.text.Spanned\nimport android.util.TypedValue\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.Button\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport android.widget.Spinner\nimport android.widget.TextView\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.widget.Toolbar\nimport androidx.cardview.widget.CardView\nimport androidx.core.view.isGone\nimport androidx.core.view.isInvisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.widget.ImageViewCompat\nimport androidx.databinding.BindingAdapter\nimport androidx.databinding.InverseBindingAdapter\nimport androidx.databinding.InverseBindingListener\nimport androidx.databinding.InverseMethod\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport com.google.android.material.button.MaterialButton\nimport com.google.android.material.card.MaterialCardView\nimport com.google.android.material.chip.Chip\nimport com.google.android.material.slider.Slider\nimport com.google.android.material.textfield.TextInputLayout\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.utils.TextHolder\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport com.topjohnwu.widget.IndeterminateCheckBox\nimport kotlin.math.roundToInt\n\n@BindingAdapter(\"gone\")\nfun setGone(view: View, gone: Boolean) {\n    view.isGone = gone\n}\n\n@BindingAdapter(\"invisible\")\nfun setInvisible(view: View, invisible: Boolean) {\n    view.isInvisible = invisible\n}\n\n@BindingAdapter(\"goneUnless\")\nfun setGoneUnless(view: View, goneUnless: Boolean) {\n    setGone(view, goneUnless.not())\n}\n\n@BindingAdapter(\"invisibleUnless\")\nfun setInvisibleUnless(view: View, invisibleUnless: Boolean) {\n    setInvisible(view, invisibleUnless.not())\n}\n\n@BindingAdapter(\"markdownText\")\nfun setMarkdownText(tv: TextView, markdown: Spanned) {\n    ServiceLocator.markwon.setParsedMarkdown(tv, markdown)\n}\n\n@BindingAdapter(\"onNavigationClick\")\nfun setOnNavigationClickedListener(view: Toolbar, listener: View.OnClickListener) {\n    view.setNavigationOnClickListener(listener)\n}\n\n@BindingAdapter(\"srcCompat\")\nfun setImageResource(view: ImageView, @DrawableRes resId: Int) {\n    view.setImageResource(resId)\n}\n\n@BindingAdapter(\"srcCompat\")\nfun setImageResource(view: ImageView, drawable: Drawable) {\n    view.setImageDrawable(drawable)\n}\n\n@BindingAdapter(\"onTouch\")\nfun setOnTouchListener(view: View, listener: View.OnTouchListener) {\n    view.setOnTouchListener(listener)\n}\n\n@BindingAdapter(\"scrollToLast\")\nfun setScrollToLast(view: RecyclerView, shouldScrollToLast: Boolean) {\n\n    fun scrollToLast() = UiThreadHandler.handler.postDelayed({\n        view.scrollToPosition(view.adapter?.itemCount?.minus(1) ?: 0)\n    }, 30)\n\n    fun wait(callback: () -> Unit) {\n        UiThreadHandler.handler.postDelayed(callback, 1000)\n    }\n\n    fun RecyclerView.Adapter<*>.setListener() {\n        val observer = object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                scrollToLast()\n            }\n        }\n        registerAdapterDataObserver(observer)\n        view.setTag(R.id.recyclerScrollListener, observer)\n    }\n\n    fun RecyclerView.Adapter<*>.removeListener() {\n        val observer =\n            view.getTag(R.id.recyclerScrollListener) as? RecyclerView.AdapterDataObserver ?: return\n        unregisterAdapterDataObserver(observer)\n    }\n\n    fun trySetListener(): Unit = view.adapter?.setListener() ?: wait { trySetListener() }\n\n    if (shouldScrollToLast) {\n        trySetListener()\n    } else {\n        view.adapter?.removeListener()\n    }\n}\n\n@BindingAdapter(\"isEnabled\")\nfun setEnabled(view: View, isEnabled: Boolean) {\n    view.isEnabled = isEnabled\n}\n\n@BindingAdapter(\"error\")\nfun TextInputLayout.setErrorString(error: String) {\n    val newError = error.let { if (it.isEmpty()) null else it }\n    if (this.error == null && newError == null) return\n    this.error = newError\n}\n\n// md2\n\n@BindingAdapter(\n    \"android:layout_marginLeft\",\n    \"android:layout_marginTop\",\n    \"android:layout_marginRight\",\n    \"android:layout_marginBottom\",\n    \"android:layout_marginStart\",\n    \"android:layout_marginEnd\",\n    requireAll = false\n)\nfun View.setMargins(\n    marginLeft: Int?,\n    marginTop: Int?,\n    marginRight: Int?,\n    marginBottom: Int?,\n    marginStart: Int?,\n    marginEnd: Int?\n) = updateLayoutParams<ViewGroup.MarginLayoutParams> {\n    marginLeft?.let { leftMargin = it }\n    marginTop?.let { topMargin = it }\n    marginRight?.let { rightMargin = it }\n    marginBottom?.let { bottomMargin = it }\n    marginStart?.let { this.marginStart = it }\n    marginEnd?.let { this.marginEnd = it }\n}\n\n@BindingAdapter(\"nestedScrollingEnabled\")\nfun RecyclerView.setNestedScrolling(enabled: Boolean) {\n    isNestedScrollingEnabled = enabled\n}\n\n@BindingAdapter(\"isSelected\")\nfun View.isSelected(isSelected: Boolean) {\n    this.isSelected = isSelected\n}\n\n@BindingAdapter(\"dividerVertical\", \"dividerHorizontal\", requireAll = false)\nfun RecyclerView.setDividers(dividerVertical: Drawable?, dividerHorizontal: Drawable?) {\n    if (dividerHorizontal != null) {\n        DividerItemDecoration(context, LinearLayoutManager.HORIZONTAL).apply {\n            setDrawable(dividerHorizontal)\n        }.let { addItemDecoration(it) }\n    }\n    if (dividerVertical != null) {\n        DividerItemDecoration(context, LinearLayoutManager.VERTICAL).apply {\n            setDrawable(dividerVertical)\n        }.let { addItemDecoration(it) }\n    }\n}\n\n@BindingAdapter(\"icon\")\nfun Button.setIconRes(res: Int) {\n    (this as MaterialButton).setIconResource(res)\n}\n\n@BindingAdapter(\"icon\")\nfun Button.setIcon(drawable: Drawable) {\n    (this as MaterialButton).icon = drawable\n}\n\n@BindingAdapter(\"strokeWidth\")\nfun MaterialCardView.setCardStrokeWidthBound(stroke: Float) {\n    strokeWidth = stroke.roundToInt()\n}\n\n@BindingAdapter(\"onMenuClick\")\nfun Toolbar.setOnMenuClickListener(listener: Toolbar.OnMenuItemClickListener) {\n    setOnMenuItemClickListener(listener)\n}\n\n@BindingAdapter(\"onCloseClicked\")\nfun Chip.setOnCloseClickedListenerBinding(listener: View.OnClickListener) {\n    setOnCloseIconClickListener(listener)\n}\n\n@BindingAdapter(\"progressAnimated\")\nfun ProgressBar.setProgressAnimated(newProgress: Int) {\n    val animator = tag as? ValueAnimator\n    animator?.cancel()\n\n    ValueAnimator.ofInt(progress, newProgress).apply {\n        interpolator = FastOutSlowInInterpolator()\n        addUpdateListener { progress = it.animatedValue as Int }\n        tag = this\n    }.start()\n}\n\n@BindingAdapter(\"android:text\")\nfun TextView.setTextSafe(text: Int) {\n    if (text == 0) this.text = null else setText(text)\n}\n\n@BindingAdapter(\"android:onLongClick\")\nfun View.setOnLongClickListenerBinding(listener: () -> Unit) {\n    setOnLongClickListener {\n        listener()\n        true\n    }\n}\n\n@BindingAdapter(\"strikeThrough\")\nfun TextView.setStrikeThroughEnabled(useStrikeThrough: Boolean) {\n    paintFlags = if (useStrikeThrough) {\n        paintFlags or Paint.STRIKE_THRU_TEXT_FLAG\n    } else {\n        paintFlags and Paint.STRIKE_THRU_TEXT_FLAG.inv()\n    }\n}\n\n@BindingAdapter(\"spanCount\")\nfun RecyclerView.setSpanCount(count: Int) {\n    when (val lama = layoutManager) {\n        is GridLayoutManager -> lama.spanCount = count\n        is StaggeredGridLayoutManager -> lama.spanCount = count\n    }\n}\n\n@BindingAdapter(\"state\")\nfun setState(view: IndeterminateCheckBox, state: Boolean?) {\n    if (view.state != state)\n        view.state = state\n}\n\n@InverseBindingAdapter(attribute = \"state\")\nfun getState(view: IndeterminateCheckBox) = view.state\n\n@BindingAdapter(\"stateAttrChanged\")\nfun setListeners(\n    view: IndeterminateCheckBox,\n    attrChange: InverseBindingListener\n) {\n    view.setOnStateChangedListener { _, _ ->\n        attrChange.onChange()\n    }\n}\n\n@BindingAdapter(\"cardBackgroundColorAttr\")\nfun CardView.setCardBackgroundColorAttr(attr: Int) {\n    val tv = TypedValue()\n    context.theme.resolveAttribute(attr, tv, true)\n    setCardBackgroundColor(tv.data)\n}\n\n@BindingAdapter(\"tint\")\nfun ImageView.setTint(color: Int) {\n    ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(color))\n}\n\n@BindingAdapter(\"tintAttr\")\nfun ImageView.setTintAttr(attr: Int) {\n    val tv = TypedValue()\n    context.theme.resolveAttribute(attr, tv, true)\n    ImageViewCompat.setImageTintList(this, ColorStateList.valueOf(tv.data))\n}\n\n@BindingAdapter(\"textColorAttr\")\nfun TextView.setTextColorAttr(attr: Int) {\n    val tv = TypedValue()\n    context.theme.resolveAttribute(attr, tv, true)\n    setTextColor(tv.data)\n}\n\n@BindingAdapter(\"android:text\")\nfun TextView.setText(text: TextHolder) {\n    this.text = text.getText(context.resources)\n}\n\n@BindingAdapter(\"items\", \"layout\")\nfun Spinner.setAdapter(items: Array<Any>, layoutRes: Int) {\n    adapter = ArrayAdapter(context, layoutRes, items)\n}\n\n@BindingAdapter(\"labelFormatter\")\nfun Slider.setLabelFormatter(formatter: (Float) -> Int) {\n    setLabelFormatter { value -> resources.getString(formatter(value)) }\n}\n\n@InverseBindingAdapter(attribute = \"android:value\")\nfun Slider.getValueBinding() = value\n\n@BindingAdapter(\"android:valueAttrChanged\")\nfun Slider.setListener(attrChange: InverseBindingListener) {\n    addOnSliderTouchListener(object : Slider.OnSliderTouchListener {\n        override fun onStartTrackingTouch(slider: Slider) = Unit\n        override fun onStopTrackingTouch(slider: Slider) = attrChange.onChange()\n    })\n}\n\n@InverseMethod(\"sliderValueToPolicy\")\nfun policyToSliderValue(policy: Int): Float {\n    return when (policy) {\n        SuPolicy.DENY -> 1f\n        SuPolicy.RESTRICT -> 2f\n        SuPolicy.ALLOW -> 3f\n        else -> 1f\n    }\n}\n\nfun sliderValueToPolicy(value: Float): Int {\n    return when (value) {\n        1f -> SuPolicy.DENY\n        2f -> SuPolicy.RESTRICT\n        3f -> SuPolicy.ALLOW\n        else -> SuPolicy.DENY\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/DiffObservableList.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport androidx.annotation.MainThread\nimport androidx.annotation.WorkerThread\nimport androidx.databinding.ListChangeRegistry\nimport androidx.databinding.ObservableList\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListUpdateCallback\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.util.AbstractList\n\n// Only expose the immutable List types\ninterface DiffList<T : DiffItem<*>> : List<T> {\n    fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult\n\n    @MainThread\n    fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult)\n\n    @WorkerThread\n    suspend fun update(newItems: List<T>)\n}\n\ninterface FilterList<T : DiffItem<*>> : List<T> {\n    fun filter(filter: (T) -> Boolean)\n\n    @MainThread\n    fun set(newItems: List<T>)\n}\n\nfun <T : DiffItem<*>> diffList(): DiffList<T> = DiffObservableList()\n\nfun <T : DiffItem<*>> filterList(scope: CoroutineScope): FilterList<T> =\n    FilterableDiffObservableList(scope)\n\nprivate open class DiffObservableList<T : DiffItem<*>>\n    : AbstractList<T>(), ObservableList<T>, DiffList<T>, ListUpdateCallback {\n\n    protected var list: List<T> = emptyList()\n    private val listeners = ListChangeRegistry()\n\n    override val size: Int get() = list.size\n\n    override fun get(index: Int) = list[index]\n\n    override fun calculateDiff(newItems: List<T>): DiffUtil.DiffResult {\n        return doCalculateDiff(list, newItems)\n    }\n\n    protected fun doCalculateDiff(oldItems: List<T>, newItems: List<T>): DiffUtil.DiffResult {\n        return DiffUtil.calculateDiff(object : DiffUtil.Callback() {\n            override fun getOldListSize() = oldItems.size\n\n            override fun getNewListSize() = newItems.size\n\n            @Suppress(\"UNCHECKED_CAST\")\n            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n                val oldItem = oldItems[oldItemPosition]\n                val newItem = newItems[newItemPosition]\n                return (oldItem as DiffItem<Any>).itemSameAs(newItem)\n            }\n\n            @Suppress(\"UNCHECKED_CAST\")\n            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n                val oldItem = oldItems[oldItemPosition]\n                val newItem = newItems[newItemPosition]\n                return (oldItem as DiffItem<Any>).contentSameAs(newItem)\n            }\n        }, true)\n    }\n\n    @MainThread\n    override fun update(newItems: List<T>, diffResult: DiffUtil.DiffResult) {\n        list = ArrayList(newItems)\n        diffResult.dispatchUpdatesTo(this)\n    }\n\n    @WorkerThread\n    override suspend fun update(newItems: List<T>) {\n        val diffResult = calculateDiff(newItems)\n        withContext(Dispatchers.Main) {\n            update(newItems, diffResult)\n        }\n    }\n\n    override fun addOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {\n        listeners.add(listener)\n    }\n\n    override fun removeOnListChangedCallback(listener: ObservableList.OnListChangedCallback<out ObservableList<T>>) {\n        listeners.remove(listener)\n    }\n\n    override fun onChanged(position: Int, count: Int, payload: Any?) {\n        listeners.notifyChanged(this, position, count)\n    }\n\n    override fun onMoved(fromPosition: Int, toPosition: Int) {\n        listeners.notifyMoved(this, fromPosition, toPosition, 1)\n    }\n\n    override fun onInserted(position: Int, count: Int) {\n        modCount += 1\n        listeners.notifyInserted(this, position, count)\n    }\n\n    override fun onRemoved(position: Int, count: Int) {\n        modCount += 1\n        listeners.notifyRemoved(this, position, count)\n    }\n}\n\nprivate class FilterableDiffObservableList<T : DiffItem<*>>(\n    private val scope: CoroutineScope\n) : DiffObservableList<T>(), FilterList<T> {\n\n    private var sublist: List<T> = emptyList()\n    private var job: Job? = null\n    private var lastFilter: ((T) -> Boolean)? = null\n\n    // ---\n\n    override fun filter(filter: (T) -> Boolean) {\n        lastFilter = filter\n        job?.cancel()\n        job = scope.launch(Dispatchers.Default) {\n            val oldList = sublist\n            val newList = list.filter(filter)\n            val diff = doCalculateDiff(oldList, newList)\n            withContext(Dispatchers.Main) {\n                sublist = newList\n                diff.dispatchUpdatesTo(this@FilterableDiffObservableList)\n            }\n        }\n    }\n\n    // ---\n\n    override fun get(index: Int): T {\n        return sublist[index]\n    }\n\n    override val size: Int\n        get() = sublist.size\n\n    @MainThread\n    override fun set(newItems: List<T>) {\n        onRemoved(0, sublist.size)\n        list = newItems\n        sublist = emptyList()\n        lastFilter?.let { filter(it) }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/MergeObservableList.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport androidx.databinding.ListChangeRegistry\nimport androidx.databinding.ObservableList\nimport androidx.databinding.ObservableList.OnListChangedCallback\nimport java.util.AbstractList\n\n@Suppress(\"UNCHECKED_CAST\")\nclass MergeObservableList<T> : AbstractList<T>(), ObservableList<T> {\n\n    private val lists: MutableList<List<T>> = mutableListOf()\n    private val listeners = ListChangeRegistry()\n    private val callback = Callback<T>()\n\n    override fun addOnListChangedCallback(callback: OnListChangedCallback<out ObservableList<T>>) {\n        listeners.add(callback)\n    }\n\n    override fun removeOnListChangedCallback(callback: OnListChangedCallback<out ObservableList<T>>) {\n        listeners.remove(callback)\n    }\n\n    override fun get(index: Int): T {\n        if (index < 0)\n            throw IndexOutOfBoundsException()\n        var idx = index\n        for (list in lists) {\n            val size = list.size\n            if (idx < size) {\n                return list[idx]\n            }\n            idx -= size\n        }\n        throw IndexOutOfBoundsException()\n    }\n\n    override val size: Int\n        get() = lists.fold(0) { i, it -> i + it.size }\n\n\n    fun insertItem(obj: T): MergeObservableList<T> {\n        val idx = size\n        lists.add(listOf(obj))\n        ++modCount\n        listeners.notifyInserted(this, idx, 1)\n        return this\n    }\n\n    fun insertList(list: List<T>): MergeObservableList<T> {\n        val idx = size\n        lists.add(list)\n        ++modCount\n        (list as? ObservableList<T>)?.addOnListChangedCallback(callback)\n        if (list.isNotEmpty())\n            listeners.notifyInserted(this, idx, list.size)\n        return this\n    }\n\n    fun removeItem(obj: T): Boolean {\n        var idx = 0\n        for ((i, list) in lists.withIndex()) {\n            if (list !is ObservableList<*>) {\n                if (obj == list[0]) {\n                    lists.removeAt(i)\n                    ++modCount\n                    listeners.notifyRemoved(this, idx, 1)\n                    return true\n                }\n            }\n            idx += list.size\n        }\n        return false\n    }\n\n    fun removeList(listToRemove: List<T>): Boolean {\n        var idx = 0\n        for ((i, list) in lists.withIndex()) {\n            if (listToRemove === list) {\n                (list as? ObservableList<T>)?.removeOnListChangedCallback(callback)\n                lists.removeAt(i)\n                ++modCount\n                listeners.notifyRemoved(this, idx, list.size)\n                return true\n            }\n            idx += list.size\n        }\n        return false\n    }\n\n    override fun clear() {\n        val sz = size\n        for (list in lists) {\n            if (list is ObservableList) {\n                list.removeOnListChangedCallback(callback)\n            }\n        }\n        ++modCount\n        lists.clear()\n        if (sz > 0)\n            listeners.notifyRemoved(this, 0, sz)\n    }\n\n    private fun subIndexToIndex(subList: List<*>, index: Int): Int {\n        if (index < 0)\n            throw IndexOutOfBoundsException()\n        var idx = 0\n        for (list in lists) {\n            if (subList === list) {\n                return idx + index\n            }\n            idx += list.size\n        }\n        throw IllegalArgumentException()\n    }\n\n    inner class Callback<T> : OnListChangedCallback<ObservableList<T>>() {\n        override fun onChanged(sender: ObservableList<T>) {\n            ++modCount\n            listeners.notifyChanged(this@MergeObservableList)\n        }\n\n        override fun onItemRangeChanged(\n            sender: ObservableList<T>,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            listeners.notifyChanged(this@MergeObservableList,\n                subIndexToIndex(sender, positionStart), itemCount)\n        }\n\n        override fun onItemRangeInserted(\n            sender: ObservableList<T>,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            ++modCount\n            listeners.notifyInserted(this@MergeObservableList,\n                subIndexToIndex(sender, positionStart), itemCount)\n        }\n\n        override fun onItemRangeMoved(\n            sender: ObservableList<T>,\n            fromPosition: Int,\n            toPosition: Int,\n            itemCount: Int\n        ) {\n            val idx = subIndexToIndex(sender, 0)\n            listeners.notifyMoved(this@MergeObservableList,\n                idx + fromPosition, idx + toPosition, itemCount)\n        }\n\n        override fun onItemRangeRemoved(\n            sender: ObservableList<T>,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            ++modCount\n            listeners.notifyRemoved(this@MergeObservableList,\n                subIndexToIndex(sender, positionStart), itemCount)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/ObservableHost.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport androidx.databinding.Observable\nimport androidx.databinding.PropertyChangeRegistry\n\n/**\n * Modified from https://github.com/skoumalcz/teanity/blob/1.2/core/src/main/java/com/skoumal/teanity/observable/Notifyable.kt\n *\n * Interface that allows user to be observed via DataBinding or manually by assigning listeners.\n *\n * @see [androidx.databinding.Observable]\n * */\ninterface ObservableHost : Observable {\n\n    var callbacks: PropertyChangeRegistry?\n\n    /**\n     * Notifies all observers that something has changed. By default implementation this method is\n     * synchronous, hence observers will never be notified in undefined order. Observers might\n     * choose to refresh the view completely, which is beyond the scope of this function.\n     * */\n    fun notifyChange() {\n        synchronized(this) {\n            callbacks ?: return\n        }.notifyCallbacks(this, 0, null)\n    }\n\n    /**\n     * Notifies all observers about field with [fieldId] has been changed. This will happen\n     * synchronously before or after [notifyChange] has been called. It will never be called during\n     * the execution of aforementioned method.\n     * */\n    fun notifyPropertyChanged(fieldId: Int) {\n        synchronized(this) {\n            callbacks ?: return\n        }.notifyCallbacks(this, fieldId, null)\n    }\n\n    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {\n        synchronized(this) {\n            callbacks ?: PropertyChangeRegistry().also { callbacks = it }\n        }.add(callback)\n    }\n\n    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback) {\n        synchronized(this) {\n            callbacks ?: return\n        }.remove(callback)\n    }\n}\n\nfun ObservableHost.addOnPropertyChangedCallback(\n    fieldId: Int,\n    removeAfterChanged: Boolean = false,\n    callback: () -> Unit\n) {\n    addOnPropertyChangedCallback(object : Observable.OnPropertyChangedCallback() {\n        override fun onPropertyChanged(sender: Observable?, propertyId: Int) {\n            if (fieldId == propertyId) {\n                callback()\n                if (removeAfterChanged)\n                    removeOnPropertyChangedCallback(this)\n            }\n        }\n    })\n}\n\n/**\n * Injects boilerplate implementation for {@literal @}[androidx.databinding.Bindable] field setters.\n *\n * # Examples:\n * ```kotlin\n * @get:Bindable\n * var myField = defaultValue\n *     set(value) = set(value, field, { field = it }, BR.myField) {\n *         doSomething(it)\n *     }\n * ```\n * */\n\ninline fun <reified T> ObservableHost.set(\n    new: T, old: T, setter: (T) -> Unit, fieldId: Int, afterChanged: (T) -> Unit = {}) {\n    if (old != new) {\n        setter(new)\n        notifyPropertyChanged(fieldId)\n        afterChanged(new)\n    }\n}\n\ninline fun <reified T> ObservableHost.set(\n    new: T, old: T, setter: (T) -> Unit, vararg fieldIds: Int, afterChanged: (T) -> Unit = {}) {\n    if (old != new) {\n        setter(new)\n        fieldIds.forEach { notifyPropertyChanged(it) }\n        afterChanged(new)\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/RecyclerViewItems.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport androidx.databinding.PropertyChangeRegistry\nimport androidx.databinding.ViewDataBinding\nimport androidx.recyclerview.widget.RecyclerView\n\nabstract class RvItem {\n    abstract val layoutRes: Int\n}\n\nabstract class ObservableRvItem : RvItem(), ObservableHost {\n    override var callbacks: PropertyChangeRegistry? = null\n}\n\ninterface ItemWrapper<E> {\n    val item: E\n}\n\ninterface ViewAwareItem {\n    fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView)\n}\n\ninterface DiffItem<T : Any> {\n\n    fun itemSameAs(other: T): Boolean {\n        if (this === other) return true\n        return when (this) {\n            is ItemWrapper<*> -> item == (other as ItemWrapper<*>).item\n            is Comparable<*> -> compareValues(this, other as Comparable<*>) == 0\n            else -> this == other\n        }\n    }\n\n    fun contentSameAs(other: T) = true\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/databinding/RvItemAdapter.kt",
    "content": "package com.topjohnwu.magisk.databinding\n\nimport android.annotation.SuppressLint\nimport android.util.SparseArray\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.databinding.BindingAdapter\nimport androidx.databinding.DataBindingUtil\nimport androidx.databinding.ObservableList\nimport androidx.databinding.ObservableList.OnListChangedCallback\nimport androidx.databinding.ViewDataBinding\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.findViewTreeLifecycleOwner\nimport androidx.recyclerview.widget.RecyclerView\nimport com.topjohnwu.magisk.BR\n\nclass RvItemAdapter<T: RvItem>(\n    val items: List<T>,\n    val extraBindings: SparseArray<*>?\n) : RecyclerView.Adapter<RvItemAdapter.ViewHolder>() {\n\n    private var lifecycleOwner: LifecycleOwner? = null\n    private var recyclerView: RecyclerView? = null\n    private val observer by lazy(LazyThreadSafetyMode.NONE) { ListObserver<T>() }\n\n    override fun onAttachedToRecyclerView(rv: RecyclerView) {\n        lifecycleOwner = rv.findViewTreeLifecycleOwner()\n        recyclerView = rv\n        if (items is ObservableList)\n            items.addOnListChangedCallback(observer)\n    }\n\n    override fun onDetachedFromRecyclerView(rv: RecyclerView) {\n        lifecycleOwner = null\n        recyclerView = null\n        if (items is ObservableList)\n            items.removeOnListChangedCallback(observer)\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, layoutRes: Int): ViewHolder {\n        val inflator = LayoutInflater.from(parent.context)\n        return ViewHolder(DataBindingUtil.inflate(inflator, layoutRes, parent, false))\n    }\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        val item = items[position]\n        holder.binding.setVariable(BR.item, item)\n        extraBindings?.let {\n            for (i in 0 until it.size()) {\n                holder.binding.setVariable(it.keyAt(i), it.valueAt(i))\n            }\n        }\n        holder.binding.lifecycleOwner = lifecycleOwner\n        holder.binding.executePendingBindings()\n        recyclerView?.let {\n            if (item is ViewAwareItem)\n                item.onBind(holder.binding, it)\n        }\n    }\n\n    override fun getItemCount() = items.size\n\n    override fun getItemViewType(position: Int) = items[position].layoutRes\n\n    class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root)\n\n    inner class ListObserver<T: RvItem> : OnListChangedCallback<ObservableList<T>>() {\n\n        @SuppressLint(\"NotifyDataSetChanged\")\n        override fun onChanged(sender: ObservableList<T>) {\n            notifyDataSetChanged()\n        }\n\n        override fun onItemRangeChanged(\n            sender: ObservableList<T>,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            notifyItemRangeChanged(positionStart, itemCount)\n        }\n\n        override fun onItemRangeInserted(\n            sender: ObservableList<T>?,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            notifyItemRangeInserted(positionStart, itemCount)\n        }\n\n        override fun onItemRangeMoved(\n            sender: ObservableList<T>?,\n            fromPosition: Int,\n            toPosition: Int,\n            itemCount: Int\n        ) {\n            for (i in 0 until itemCount) {\n                notifyItemMoved(fromPosition + i, toPosition + i)\n            }\n        }\n\n        override fun onItemRangeRemoved(\n            sender: ObservableList<T>?,\n            positionStart: Int,\n            itemCount: Int\n        ) {\n            notifyItemRangeRemoved(positionStart, itemCount)\n        }\n    }\n}\n\ninline fun bindExtra(body: (SparseArray<Any?>) -> Unit) = SparseArray<Any?>().also(body)\n\n@BindingAdapter(\"items\", \"extraBindings\", requireAll = false)\nfun <T: RvItem> RecyclerView.setAdapter(items: List<T>?, extraBindings: SparseArray<*>?) {\n    if (items != null) {\n        val rva = (adapter as? RvItemAdapter<*>)\n        if (rva == null || rva.items !== items || rva.extraBindings !== extraBindings) {\n            adapter = RvItemAdapter(items, extraBindings)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/DarkThemeDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.app.Activity\nimport androidx.appcompat.app.AppCompatDelegate\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass DarkThemeDialog : DialogBuilder {\n\n    override fun build(dialog: MagiskDialog) {\n        val activity = dialog.ownerActivity!!\n        dialog.apply {\n            setTitle(CoreR.string.settings_dark_mode_title)\n            setMessage(CoreR.string.settings_dark_mode_message)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = CoreR.string.settings_dark_mode_light\n                icon = R.drawable.ic_day\n                onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_NO, activity) }\n            }\n            setButton(MagiskDialog.ButtonType.NEUTRAL) {\n                text = CoreR.string.settings_dark_mode_system\n                icon = R.drawable.ic_day_night\n                onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, activity) }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = CoreR.string.settings_dark_mode_dark\n                icon = R.drawable.ic_night\n                onClick { selectTheme(AppCompatDelegate.MODE_NIGHT_YES, activity) }\n            }\n        }\n    }\n\n    private fun selectTheme(mode: Int, activity: Activity) {\n        Config.darkTheme = mode\n        (activity as UIActivity<*>).delegate.localNightMode = mode\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/EnvFixDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.widget.Toast\nimport androidx.core.os.postDelayed\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.reboot\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.ui.home.HomeViewModel\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport kotlinx.coroutines.launch\n\nclass EnvFixDialog(private val vm: HomeViewModel, private val code: Int) : DialogBuilder {\n\n    override fun build(dialog: MagiskDialog) {\n        dialog.apply {\n            setTitle(R.string.env_fix_title)\n            setMessage(R.string.env_fix_msg)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = android.R.string.ok\n                doNotDismiss = true\n                onClick {\n                    dialog.apply {\n                        setTitle(R.string.setup_title)\n                        setMessage(R.string.setup_msg)\n                        resetButtons()\n                        setCancelable(false)\n                    }\n                    dialog.activity.lifecycleScope.launch {\n                        MagiskInstaller.FixEnv().exec { success ->\n                            dialog.dismiss()\n                            context.toast(\n                                if (success) R.string.reboot_delay_toast else R.string.setup_fail,\n                                Toast.LENGTH_LONG\n                            )\n                            if (success)\n                                UiThreadHandler.handler.postDelayed(5000) { reboot() }\n                        }\n                    }\n                }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = android.R.string.cancel\n            }\n        }\n\n        if (code == 2 || // No rules block, module policy not loaded\n            Info.env.versionCode != BuildConfig.APP_VERSION_CODE ||\n            Info.env.versionString != BuildConfig.APP_VERSION_NAME) {\n            dialog.setMessage(R.string.env_full_fix_msg)\n            dialog.setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = android.R.string.ok\n                onClick {\n                    vm.onMagiskPressed()\n                    dialog.dismiss()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/LocalModuleInstallDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.net.Uri\nimport com.topjohnwu.magisk.MainDirections\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.ui.module.ModuleViewModel\nimport com.topjohnwu.magisk.view.MagiskDialog\n\nclass LocalModuleInstallDialog(\n    private val viewModel: ModuleViewModel,\n    private val uri: Uri,\n    private val displayName: String\n) : DialogBuilder {\n    override fun build(dialog: MagiskDialog) {\n        dialog.apply {\n            setTitle(R.string.confirm_install_title)\n            setMessage(context.getString(R.string.confirm_install, displayName))\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = android.R.string.ok\n                onClick {\n                    viewModel.apply {\n                        MainDirections.actionFlashFragment(Const.Value.FLASH_ZIP, uri).navigate()\n                    }\n                }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = android.R.string.cancel\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/ManagerInstallDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport java.io.File\n\nclass ManagerInstallDialog : MarkDownDialog() {\n\n    override suspend fun getMarkdownText(): String {\n        val text = Info.update.note\n        // Cache the changelog\n        File(AppContext.cacheDir, \"${Info.update.versionCode}.md\").writeText(text)\n        return text\n    }\n\n    override fun build(dialog: MagiskDialog) {\n        super.build(dialog)\n        dialog.apply {\n            setCancelable(true)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = R.string.install\n                onClick { DownloadEngine.startWithActivity(activity, Subject.App()) }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = android.R.string.cancel\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/MarkDownDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.view.LayoutInflater\nimport android.widget.TextView\nimport androidx.annotation.CallSuper\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.IOException\nimport com.topjohnwu.magisk.core.R as CoreR\n\nabstract class MarkDownDialog : DialogBuilder {\n\n    abstract suspend fun getMarkdownText(): String\n\n    @CallSuper\n    override fun build(dialog: MagiskDialog) {\n        with(dialog) {\n            val view = LayoutInflater.from(context).inflate(R.layout.markdown_window_md2, null)\n            setView(view)\n            val tv = view.findViewById<TextView>(R.id.md_txt)\n            activity.lifecycleScope.launch {\n                try {\n                    val text = withContext(Dispatchers.IO) { getMarkdownText() }\n                    ServiceLocator.markwon.setMarkdown(tv, text)\n                } catch (e: IOException) {\n                    Timber.e(e)\n                    tv.setText(CoreR.string.download_file_error)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/OnlineModuleInstallDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.content.Context\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.model.module.OnlineModule\nimport com.topjohnwu.magisk.ui.flash.FlashFragment\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.parcelize.Parcelize\n\nclass OnlineModuleInstallDialog(private val item: OnlineModule) : MarkDownDialog() {\n\n    private val svc get() = ServiceLocator.networkService\n\n    override suspend fun getMarkdownText(): String {\n        val str = svc.fetchString(item.changelog)\n        return if (str.length > 1000) str.substring(0, 1000) else str\n    }\n\n    @Parcelize\n    class Module(\n        override val module: OnlineModule,\n        override val autoLaunch: Boolean,\n        override val notifyId: Int = Notifications.nextId()\n    ) : Subject.Module() {\n        override fun pendingIntent(context: Context) = FlashFragment.installIntent(context, file)\n    }\n\n    override fun build(dialog: MagiskDialog) {\n        super.build(dialog)\n        dialog.apply {\n\n            fun download(install: Boolean) {\n                DownloadEngine.startWithActivity(activity, Module(item, install))\n            }\n\n            val title = context.getString(R.string.repo_install_title,\n                item.name, item.version, item.versionCode)\n\n            setTitle(title)\n            setCancelable(true)\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = R.string.download\n                onClick { download(false) }\n            }\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = R.string.install\n                onClick { download(true) }\n            }\n            setButton(MagiskDialog.ButtonType.NEUTRAL) {\n                text = android.R.string.cancel\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/SecondSlotWarningDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.view.MagiskDialog\n\nclass SecondSlotWarningDialog : DialogBuilder {\n\n    override fun build(dialog: MagiskDialog) {\n        dialog.apply {\n            setTitle(android.R.string.dialog_alert_title)\n            setMessage(R.string.install_inactive_slot_msg)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = android.R.string.ok\n            }\n            setCancelable(true)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/SuperuserRevokeDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.view.MagiskDialog\n\nclass SuperuserRevokeDialog(\n    private val appName: String,\n    private val onSuccess: () -> Unit\n) : DialogBuilder {\n\n    override fun build(dialog: MagiskDialog) {\n        dialog.apply {\n            setTitle(R.string.su_revoke_title)\n            setMessage(R.string.su_revoke_msg, appName)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = android.R.string.ok\n                onClick { onSuccess() }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = android.R.string.cancel\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/dialog/UninstallDialog.kt",
    "content": "package com.topjohnwu.magisk.dialog\n\nimport android.app.ProgressDialog\nimport android.widget.Toast\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.arch.NavigationActivity\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.events.DialogBuilder\nimport com.topjohnwu.magisk.ui.flash.FlashFragment\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport kotlinx.coroutines.launch\n\nclass UninstallDialog : DialogBuilder {\n\n    override fun build(dialog: MagiskDialog) {\n        dialog.apply {\n            setTitle(R.string.uninstall_magisk_title)\n            setMessage(R.string.uninstall_magisk_msg)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = R.string.restore_img\n                onClick { restore(dialog.activity) }\n            }\n            setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                text = R.string.complete_uninstall\n                onClick { completeUninstall(dialog) }\n            }\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private fun restore(activity: UIActivity<*>) {\n        val dialog = ProgressDialog(activity).apply {\n            setMessage(activity.getString(R.string.restore_img_msg))\n            show()\n        }\n\n        activity.lifecycleScope.launch {\n            MagiskInstaller.Restore().exec { success ->\n                dialog.dismiss()\n                if (success) {\n                    activity.toast(R.string.restore_done, Toast.LENGTH_SHORT)\n                } else {\n                    activity.toast(R.string.restore_fail, Toast.LENGTH_LONG)\n                }\n            }\n        }\n    }\n\n    private fun completeUninstall(dialog: MagiskDialog) {\n        (dialog.ownerActivity as NavigationActivity<*>)\n            .navigation.navigate(FlashFragment.uninstall())\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/events/ViewEvents.kt",
    "content": "package com.topjohnwu.magisk.events\n\nimport android.content.Context\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.navigation.NavDirections\nimport com.google.android.material.snackbar.Snackbar\nimport com.topjohnwu.magisk.arch.ActivityExecutor\nimport com.topjohnwu.magisk.arch.ContextExecutor\nimport com.topjohnwu.magisk.arch.NavigationActivity\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.arch.ViewEvent\nimport com.topjohnwu.magisk.core.base.ContentResultCallback\nimport com.topjohnwu.magisk.core.base.relaunch\nimport com.topjohnwu.magisk.utils.TextHolder\nimport com.topjohnwu.magisk.utils.asText\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.magisk.view.Shortcuts\n\nclass PermissionEvent(\n    private val permission: String,\n    private val callback: (Boolean) -> Unit\n) : ViewEvent(), ActivityExecutor {\n\n    override fun invoke(activity: UIActivity<*>) =\n        activity.withPermission(permission, callback)\n}\n\nclass BackPressEvent : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        activity.onBackPressed()\n    }\n}\n\nclass DieEvent : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        activity.finish()\n    }\n}\n\nclass ShowUIEvent(private val delegate: View.AccessibilityDelegate?)\n    : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        activity.setContentView()\n        activity.setAccessibilityDelegate(delegate)\n    }\n}\n\nclass RecreateEvent : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        activity.relaunch()\n    }\n}\n\nclass AuthEvent(\n    private val callback: () -> Unit\n) : ViewEvent(), ActivityExecutor {\n\n    override fun invoke(activity: UIActivity<*>) {\n        activity.withAuthentication { if (it) callback() }\n    }\n}\n\nclass GetContentEvent(\n    private val type: String,\n    private val callback: ContentResultCallback\n) : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        activity.getContent(type, callback)\n    }\n}\n\nclass NavigationEvent(\n    private val directions: NavDirections,\n    private val pop: Boolean\n) : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        (activity as? NavigationActivity<*>)?.apply {\n            if (pop) navigation.popBackStack()\n            directions.navigate()\n        }\n    }\n}\n\nclass AddHomeIconEvent : ViewEvent(), ContextExecutor {\n    override fun invoke(context: Context) {\n        Shortcuts.addHomeIcon(context)\n    }\n}\n\nclass SnackbarEvent(\n    private val msg: TextHolder,\n    private val length: Int = Snackbar.LENGTH_SHORT,\n    private val builder: Snackbar.() -> Unit = {}\n) : ViewEvent(), ActivityExecutor {\n\n    constructor(\n        @StringRes res: Int,\n        length: Int = Snackbar.LENGTH_SHORT,\n        builder: Snackbar.() -> Unit = {}\n    ) : this(res.asText(), length, builder)\n\n    constructor(\n        msg: String,\n        length: Int = Snackbar.LENGTH_SHORT,\n        builder: Snackbar.() -> Unit = {}\n    ) : this(msg.asText(), length, builder)\n\n    override fun invoke(activity: UIActivity<*>) {\n        activity.showSnackbar(msg.getText(activity.resources), length, builder)\n    }\n}\n\nclass DialogEvent(\n    private val builder: DialogBuilder\n) : ViewEvent(), ActivityExecutor {\n    override fun invoke(activity: UIActivity<*>) {\n        MagiskDialog(activity).apply(builder::build).show()\n    }\n}\n\ninterface DialogBuilder {\n    fun build(dialog: MagiskDialog)\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt",
    "content": "package com.topjohnwu.magisk.ui\n\nimport android.Manifest\nimport android.Manifest.permission.REQUEST_INSTALL_PACKAGES\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.ApplicationInfo\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.WindowManager\nimport android.widget.Toast\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.view.forEach\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.lifecycleScope\nimport androidx.navigation.NavDirections\nimport com.topjohnwu.magisk.MainDirections\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.arch.NavigationActivity\nimport com.topjohnwu.magisk.arch.startAnimations\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.base.SplashController\nimport com.topjohnwu.magisk.core.base.SplashScreenHost\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.databinding.ActivityMainMd2Binding\nimport com.topjohnwu.magisk.ui.home.HomeFragmentDirections\nimport com.topjohnwu.magisk.ui.theme.Theme\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.magisk.view.Shortcuts\nimport kotlinx.coroutines.launch\nimport java.io.File\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass MainViewModel : BaseViewModel()\n\nclass MainActivity : NavigationActivity<ActivityMainMd2Binding>(), SplashScreenHost {\n\n    override val layoutRes = R.layout.activity_main_md2\n    override val viewModel by viewModel<MainViewModel>()\n    override val navHostId: Int = R.id.main_nav_host\n    override val splashController = SplashController(this)\n    override val snackbarView: View\n        get() {\n            val fragmentOverride = currentFragment?.snackbarView\n            return fragmentOverride ?: super.snackbarView\n        }\n    override val snackbarAnchorView: View?\n        get() {\n            val fragmentAnchor = currentFragment?.snackbarAnchorView\n            return when {\n                fragmentAnchor?.isVisible == true -> fragmentAnchor\n                binding.mainNavigation.isVisible -> return binding.mainNavigation\n                else -> null\n            }\n        }\n\n    private var isRootFragment = true\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        setTheme(Theme.selected.themeRes)\n        splashController.preOnCreate()\n        super.onCreate(savedInstanceState)\n        splashController.onCreate(savedInstanceState)\n    }\n\n    override fun onResume() {\n        super.onResume()\n        splashController.onResume()\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    override fun onCreateUi(savedInstanceState: Bundle?) {\n        setContentView()\n        showUnsupportedMessage()\n        askForHomeShortcut()\n\n        // Ask permission to post notifications for background update check\n        if (Config.checkUpdate) {\n            withPermission(Manifest.permission.POST_NOTIFICATIONS) {\n                Config.checkUpdate = it\n            }\n        }\n\n        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)\n\n        navigation.addOnDestinationChangedListener { _, destination, _ ->\n            isRootFragment = when (destination.id) {\n                R.id.homeFragment,\n                R.id.modulesFragment,\n                R.id.superuserFragment,\n                R.id.logFragment -> true\n                else -> false\n            }\n\n            setDisplayHomeAsUpEnabled(!isRootFragment)\n            requestNavigationHidden(!isRootFragment)\n\n            binding.mainNavigation.menu.forEach {\n                if (it.itemId == destination.id) {\n                    it.isChecked = true\n                }\n            }\n        }\n\n        setSupportActionBar(binding.mainToolbar)\n\n        binding.mainNavigation.setOnItemSelectedListener {\n            getScreen(it.itemId)?.navigate()\n            true\n        }\n        binding.mainNavigation.setOnItemReselectedListener {\n            // https://issuetracker.google.com/issues/124538620\n        }\n        binding.mainNavigation.menu.apply {\n            findItem(R.id.superuserFragment)?.isEnabled = Info.showSuperUser\n            findItem(R.id.modulesFragment)?.isEnabled = Info.env.isActive && LocalModule.loaded()\n        }\n\n        val section =\n            if (intent.action == Intent.ACTION_APPLICATION_PREFERENCES)\n                Const.Nav.SETTINGS\n            else\n                intent.getStringExtra(Const.Key.OPEN_SECTION)\n\n        getScreen(section)?.navigate()\n\n        if (!isRootFragment) {\n            requestNavigationHidden(requiresAnimation = savedInstanceState == null)\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            android.R.id.home -> onBackPressed()\n            else -> return super.onOptionsItemSelected(item)\n        }\n        return true\n    }\n\n    fun setDisplayHomeAsUpEnabled(isEnabled: Boolean) {\n        binding.mainToolbar.startAnimations()\n        when {\n            isEnabled -> binding.mainToolbar.setNavigationIcon(R.drawable.ic_back_md2)\n            else -> binding.mainToolbar.navigationIcon = null\n        }\n    }\n\n    internal fun requestNavigationHidden(hide: Boolean = true, requiresAnimation: Boolean = true) {\n        val bottomView = binding.mainNavigation\n        if (requiresAnimation) {\n            bottomView.isVisible = true\n            bottomView.isHidden = hide\n        } else {\n            bottomView.isGone = hide\n        }\n    }\n\n    fun invalidateToolbar() {\n        //binding.mainToolbar.startAnimations()\n        binding.mainToolbar.invalidate()\n    }\n\n    private fun getScreen(name: String?): NavDirections? {\n        return when (name) {\n            Const.Nav.SUPERUSER -> MainDirections.actionSuperuserFragment()\n            Const.Nav.MODULES -> MainDirections.actionModuleFragment()\n            Const.Nav.SETTINGS -> HomeFragmentDirections.actionHomeFragmentToSettingsFragment()\n            else -> null\n        }\n    }\n\n    private fun getScreen(id: Int): NavDirections? {\n        return when (id) {\n            R.id.homeFragment -> MainDirections.actionHomeFragment()\n            R.id.modulesFragment -> MainDirections.actionModuleFragment()\n            R.id.superuserFragment -> MainDirections.actionSuperuserFragment()\n            R.id.logFragment -> MainDirections.actionLogFragment()\n            else -> null\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    override fun showInvalidStateMessage(): Unit = runOnUiThread {\n        MagiskDialog(this).apply {\n            setTitle(CoreR.string.unsupport_nonroot_stub_title)\n            setMessage(CoreR.string.unsupport_nonroot_stub_msg)\n            setButton(MagiskDialog.ButtonType.POSITIVE) {\n                text = CoreR.string.install\n                onClick {\n                    withPermission(REQUEST_INSTALL_PACKAGES) {\n                        if (!it) {\n                            toast(CoreR.string.install_unknown_denied, Toast.LENGTH_SHORT)\n                            showInvalidStateMessage()\n                        } else {\n                            lifecycleScope.launch {\n                                AppMigration.restore(this@MainActivity)\n                            }\n                        }\n                    }\n                }\n            }\n            setCancelable(false)\n            show()\n        }\n    }\n\n    private fun showUnsupportedMessage() {\n        if (Info.env.isUnsupported) {\n            MagiskDialog(this).apply {\n                setTitle(CoreR.string.unsupport_magisk_title)\n                setMessage(CoreR.string.unsupport_magisk_msg, Const.Version.MIN_VERSION)\n                setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }\n                setCancelable(false)\n            }.show()\n        }\n\n        if (!Info.isEmulator && Info.env.isActive && System.getenv(\"PATH\")\n                ?.split(':')\n                ?.filterNot { File(\"$it/magisk\").exists() }\n                ?.any { File(\"$it/su\").exists() } == true) {\n            MagiskDialog(this).apply {\n                setTitle(CoreR.string.unsupport_general_title)\n                setMessage(CoreR.string.unsupport_other_su_msg)\n                setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }\n                setCancelable(false)\n            }.show()\n        }\n\n        if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) {\n            MagiskDialog(this).apply {\n                setTitle(CoreR.string.unsupport_general_title)\n                setMessage(CoreR.string.unsupport_system_app_msg)\n                setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }\n                setCancelable(false)\n            }.show()\n        }\n\n        if (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0) {\n            MagiskDialog(this).apply {\n                setTitle(CoreR.string.unsupport_general_title)\n                setMessage(CoreR.string.unsupport_external_storage_msg)\n                setButton(MagiskDialog.ButtonType.POSITIVE) { text = android.R.string.ok }\n                setCancelable(false)\n            }.show()\n        }\n    }\n\n    private fun askForHomeShortcut() {\n        if (isRunningAsStub && !Config.askedHome &&\n            ShortcutManagerCompat.isRequestPinShortcutSupported(this)) {\n            // Ask and show dialog\n            Config.askedHome = true\n            MagiskDialog(this).apply {\n                setTitle(CoreR.string.add_shortcut_title)\n                setMessage(CoreR.string.add_shortcut_msg)\n                setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                    text = android.R.string.cancel\n                }\n                setButton(MagiskDialog.ButtonType.POSITIVE) {\n                    text = android.R.string.ok\n                    onClick {\n                        Shortcuts.addHomeIcon(this@MainActivity)\n                    }\n                }\n                setCancelable(true)\n            }.show()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.ComponentInfo\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.GET_ACTIVITIES\nimport android.content.pm.PackageManager.GET_PROVIDERS\nimport android.content.pm.PackageManager.GET_RECEIVERS\nimport android.content.pm.PackageManager.GET_SERVICES\nimport android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport android.content.pm.ServiceInfo\nimport android.graphics.drawable.Drawable\nimport android.os.Build\nimport android.os.Build.VERSION.SDK_INT\nimport androidx.core.os.ProcessCompat\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport java.util.Locale\nimport java.util.TreeSet\n\nclass CmdlineListItem(line: String) {\n    val packageName: String\n    val process: String\n\n    init {\n        val split = line.split(Regex(\"\\\\|\"), 2)\n        packageName = split[0]\n        process = split.getOrElse(1) { packageName }\n    }\n}\n\nconst val ISOLATED_MAGIC = \"isolated\"\n\n@SuppressLint(\"InlinedApi\")\nclass AppProcessInfo(\n    private val info: ApplicationInfo,\n    pm: PackageManager,\n    denyList: List<CmdlineListItem>\n) : Comparable<AppProcessInfo> {\n\n    private val denyList = denyList.filter {\n        it.packageName == info.packageName || it.packageName == ISOLATED_MAGIC\n    }\n\n    val label = info.getLabel(pm)\n    val iconImage: Drawable = runCatching { info.loadIcon(pm) }.getOrDefault(pm.defaultActivityIcon)\n    val packageName: String get() = info.packageName\n    val processes = fetchProcesses(pm)\n\n    override fun compareTo(other: AppProcessInfo) = comparator.compare(this, other)\n\n    fun isSystemApp() = info.flags and ApplicationInfo.FLAG_SYSTEM != 0\n\n    fun isApp() = ProcessCompat.isApplicationUid(info.uid)\n\n    private fun createProcess(name: String, pkg: String = info.packageName) =\n        ProcessInfo(name, pkg, denyList.any { it.process == name && it.packageName == pkg })\n\n    private fun ComponentInfo.getProcName(): String = processName\n        ?: applicationInfo.processName\n        ?: applicationInfo.packageName\n\n    private val ServiceInfo.isIsolated get() = (flags and ServiceInfo.FLAG_ISOLATED_PROCESS) != 0\n    private val ServiceInfo.useAppZygote get() = (flags and ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0\n\n    private fun Array<out ComponentInfo>?.toProcessList() =\n        orEmpty().map { createProcess(it.getProcName()) }\n\n    private fun Array<ServiceInfo>?.toProcessList() = orEmpty().map {\n        if (it.isIsolated) {\n            if (it.useAppZygote) {\n                val proc = info.processName ?: info.packageName\n                createProcess(\"${proc}_zygote\")\n            } else {\n                val proc = if (SDK_INT >= Build.VERSION_CODES.Q)\n                    \"${it.getProcName()}:${it.name}\" else it.getProcName()\n                createProcess(proc, ISOLATED_MAGIC)\n            }\n        } else {\n            createProcess(it.getProcName())\n        }\n    }\n\n    private fun fetchProcesses(pm: PackageManager): Collection<ProcessInfo> {\n        val flag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES or\n            GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS\n        val packageInfo = try {\n            pm.getPackageInfo(info.packageName, flag)\n        } catch (e: Exception) {\n            // Exceed binder data transfer limit, parse the package locally\n            pm.getPackageArchiveInfo(info.sourceDir, flag) ?: return emptyList()\n        }\n\n        val processSet = TreeSet<ProcessInfo>(compareBy({ it.name }, { it.isIsolated }))\n        processSet += packageInfo.activities.toProcessList()\n        processSet += packageInfo.services.toProcessList()\n        processSet += packageInfo.receivers.toProcessList()\n        processSet += packageInfo.providers.toProcessList()\n        return processSet\n    }\n\n    companion object {\n        private val comparator = compareBy<AppProcessInfo>(\n            { it.label.lowercase(Locale.ROOT) },\n            { it.info.packageName }\n        )\n    }\n}\n\ndata class ProcessInfo(\n    val name: String,\n    val packageName: String,\n    var isEnabled: Boolean\n) {\n    val isIsolated = packageName == ISOLATED_MAGIC\n    val isAppZygote = name.endsWith(\"_zygote\")\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.MenuProvider\nimport androidx.recyclerview.widget.RecyclerView\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.ktx.hideKeyboard\nimport com.topjohnwu.magisk.databinding.FragmentDenyMd2Binding\nimport rikka.recyclerview.addEdgeSpacing\nimport rikka.recyclerview.addItemSpacing\nimport rikka.recyclerview.fixEdgeEffect\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass DenyListFragment : BaseFragment<FragmentDenyMd2Binding>(), MenuProvider {\n\n    override val layoutRes = R.layout.fragment_deny_md2\n    override val viewModel by viewModel<DenyListViewModel>()\n\n    private lateinit var searchView: SearchView\n\n    override fun onStart() {\n        super.onStart()\n        activity?.setTitle(CoreR.string.denylist)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        binding.appList.addOnScrollListener(object : RecyclerView.OnScrollListener() {\n            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n                if (newState != RecyclerView.SCROLL_STATE_IDLE) activity?.hideKeyboard()\n            }\n        })\n\n        binding.appList.apply {\n            addEdgeSpacing(top = R.dimen.l_50, bottom = R.dimen.l1)\n            addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)\n            fixEdgeEffect()\n        }\n    }\n\n    override fun onPreBind(binding: FragmentDenyMd2Binding) = Unit\n\n    override fun onBackPressed(): Boolean {\n        if (searchView.isIconfiedByDefault && !searchView.isIconified) {\n            searchView.isIconified = true\n            return true\n        }\n        return super.onBackPressed()\n    }\n\n    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {\n        inflater.inflate(R.menu.menu_deny_md2, menu)\n        searchView = menu.findItem(R.id.action_search).actionView as SearchView\n        searchView.queryHint = searchView.context.getString(CoreR.string.hide_filter_hint)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                viewModel.query = query ?: \"\"\n                return true\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                viewModel.query = newText ?: \"\"\n                return true\n            }\n        })\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_show_system -> {\n                val check = !item.isChecked\n                viewModel.isShowSystem = check\n                item.isChecked = check\n                return true\n            }\n            R.id.action_show_OS -> {\n                val check = !item.isChecked\n                viewModel.isShowOS = check\n                item.isChecked = check\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    override fun onPrepareMenu(menu: Menu) {\n        val showSystem = menu.findItem(R.id.action_show_system)\n        val showOS = menu.findItem(R.id.action_show_OS)\n        showOS.isEnabled = showSystem.isChecked\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListRvItem.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.startAnimations\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.addOnPropertyChangedCallback\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.superuser.Shell\nimport kotlin.math.roundToInt\n\nclass DenyListRvItem(\n    val info: AppProcessInfo\n) : ObservableRvItem(), DiffItem<DenyListRvItem>, Comparable<DenyListRvItem> {\n\n    override val layoutRes get() = R.layout.item_hide_md2\n\n    val processes = info.processes.map { ProcessRvItem(it) }\n\n    @get:Bindable\n    var isExpanded = false\n        set(value) = set(value, field, { field = it }, BR.expanded)\n\n    var itemsChecked = 0\n        set(value) = set(value, field, { field = it }, BR.checkedPercent)\n\n    val isChecked get() = itemsChecked != 0\n\n    @get:Bindable\n    val checkedPercent get() = (itemsChecked.toFloat() / processes.size * 100).roundToInt()\n\n    private var _state: Boolean? = false\n        set(value) = set(value, field, { field = it }, BR.state)\n\n    @get:Bindable\n    var state: Boolean?\n        get() = _state\n        set(value) = set(value, _state, { _state = it }, BR.state) {\n            if (value == true) {\n                processes\n                    .filterNot { it.isEnabled }\n                    .filter { isExpanded || it.defaultSelection }\n                    .forEach { it.toggle() }\n            } else {\n                Shell.cmd(\"magisk --denylist rm ${info.packageName}\").submit()\n                processes.filter { it.isEnabled }.forEach {\n                    if (it.process.isIsolated) {\n                        it.toggle()\n                    } else {\n                        it.isEnabled = !it.isEnabled\n                        notifyPropertyChanged(BR.enabled)\n                    }\n                }\n            }\n        }\n\n    init {\n        processes.forEach { it.addOnPropertyChangedCallback(BR.enabled) { recalculateChecked() } }\n        addOnPropertyChangedCallback(BR.expanded) { recalculateChecked() }\n        recalculateChecked()\n    }\n\n    fun toggleExpand(v: View) {\n        (v.parent as? ViewGroup)?.startAnimations()\n        isExpanded = !isExpanded\n    }\n\n    private fun recalculateChecked() {\n        itemsChecked = processes.count { it.isEnabled }\n        _state = if (isExpanded) {\n            when (itemsChecked) {\n                0 -> false\n                processes.size -> true\n                else -> null\n            }\n        } else {\n            val defaultProcesses = processes.filter { it.defaultSelection }\n            when (defaultProcesses.count { it.isEnabled }) {\n                0 -> false\n                defaultProcesses.size -> true\n                else -> null\n            }\n        }\n    }\n\n    override fun compareTo(other: DenyListRvItem) = comparator.compare(this, other)\n\n    companion object {\n        private val comparator = compareBy<DenyListRvItem>(\n            { it.itemsChecked == 0 },\n            { it.info }\n        )\n    }\n\n}\n\nclass ProcessRvItem(\n    val process: ProcessInfo\n) : ObservableRvItem(), DiffItem<ProcessRvItem> {\n\n    override val layoutRes get() = R.layout.item_hide_process_md2\n\n    val displayName = if (process.isIsolated) \"(isolated) ${process.name}\" else process.name\n\n    @get:Bindable\n    var isEnabled\n        get() = process.isEnabled\n        set(value) = set(value, process.isEnabled, { process.isEnabled = it }, BR.enabled) {\n            val arg = if (it) \"add\" else \"rm\"\n            val (name, pkg) = process\n            Shell.cmd(\"magisk --denylist $arg $pkg \\'$name\\'\").submit()\n        }\n\n    fun toggle() {\n        isEnabled = !isEnabled\n    }\n\n    val defaultSelection get() =\n        process.isIsolated || process.isAppZygote || process.name == process.packageName\n\n    override fun itemSameAs(other: ProcessRvItem) =\n        process.name == other.process.name && process.packageName == other.process.packageName\n\n    override fun contentSameAs(other: ProcessRvItem) =\n        process.isEnabled == other.process.isEnabled\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport androidx.databinding.Bindable\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.ktx.concurrentMap\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.filterList\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.asFlow\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.toCollection\nimport kotlinx.coroutines.withContext\n\nclass DenyListViewModel : AsyncLoadViewModel() {\n\n    var isShowSystem = false\n        set(value) {\n            field = value\n            doQuery(query)\n        }\n\n    var isShowOS = false\n        set(value) {\n            field = value\n            doQuery(query)\n        }\n\n    var query = \"\"\n        set(value) {\n            field = value\n            doQuery(value)\n        }\n\n    val items = filterList<DenyListRvItem>(viewModelScope)\n    val extraBindings = bindExtra {\n        it.put(BR.viewModel, this)\n    }\n\n    @get:Bindable\n    var loading = true\n        private set(value) = set(value, field, { field = it }, BR.loading)\n\n    @SuppressLint(\"InlinedApi\")\n    override suspend fun doLoadWork() {\n        loading = true\n        val apps = withContext(Dispatchers.Default) {\n            val pm = AppContext.packageManager\n            val denyList = Shell.cmd(\"magisk --denylist ls\").exec().out\n                .map { CmdlineListItem(it) }\n            val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).run {\n                asFlow()\n                    .filter { AppContext.packageName != it.packageName }\n                    .concurrentMap { AppProcessInfo(it, pm, denyList) }\n                    .filter { it.processes.isNotEmpty() }\n                    .concurrentMap { DenyListRvItem(it) }\n                    .toCollection(ArrayList(size))\n            }\n            apps.sort()\n            apps\n        }\n        items.set(apps)\n        doQuery(query)\n    }\n\n    private fun doQuery(s: String) {\n        items.filter {\n            fun filterSystem() = isShowSystem || !it.info.isSystemApp()\n\n            fun filterOS() = (isShowSystem && isShowOS) || it.info.isApp()\n\n            fun filterQuery(): Boolean {\n                fun inName() = it.info.label.contains(s, true)\n                fun inPackage() = it.info.packageName.contains(s, true)\n                fun inProcesses() = it.processes.any { p -> p.process.name.contains(s, true) }\n                return inName() || inPackage() || inProcesses()\n            }\n\n            (it.isChecked || (filterSystem() && filterOS())) && filterQuery()\n        }\n        loading = false\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/ConsoleItem.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.view.updateLayoutParams\nimport androidx.databinding.ViewDataBinding\nimport androidx.recyclerview.widget.RecyclerView\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.databinding.ViewAwareItem\nimport kotlin.math.max\n\nclass ConsoleItem(\n    override val item: String\n) : RvItem(), ViewAwareItem, DiffItem<ConsoleItem>, ItemWrapper<String> {\n    override val layoutRes = R.layout.item_console_md2\n\n    private var parentWidth = -1\n\n    override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {\n        if (parentWidth < 0)\n            parentWidth = (recyclerView.parent as View).width\n\n        val view = binding.root as TextView\n        view.measure(0, 0)\n\n        // We want our recyclerView at least as wide as screen\n        val desiredWidth = max(view.measuredWidth, parentWidth)\n\n        view.updateLayoutParams { width = desiredWidth }\n\n        if (recyclerView.width < desiredWidth) {\n            recyclerView.requestLayout()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.ActivityInfo\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.isVisible\nimport androidx.navigation.NavDeepLinkBuilder\nimport com.topjohnwu.magisk.MainDirections\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.cmp\nimport com.topjohnwu.magisk.databinding.FragmentFlashMd2Binding\nimport com.topjohnwu.magisk.ui.MainActivity\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass FlashFragment : BaseFragment<FragmentFlashMd2Binding>(), MenuProvider {\n\n    override val layoutRes = R.layout.fragment_flash_md2\n    override val viewModel by viewModel<FlashViewModel>()\n    override val snackbarView: View get() = binding.snackbarContainer\n    override val snackbarAnchorView: View?\n        get() = if (binding.restartBtn.isShown) binding.restartBtn else super.snackbarAnchorView\n\n    private var defaultOrientation = -1\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        viewModel.args = FlashFragmentArgs.fromBundle(requireArguments())\n    }\n\n    override fun onStart() {\n        super.onStart()\n        activity?.setTitle(CoreR.string.flash_screen_title)\n\n        viewModel.state.observe(this) {\n            activity?.supportActionBar?.setSubtitle(\n                when (it) {\n                    FlashViewModel.State.FLASHING -> CoreR.string.flashing\n                    FlashViewModel.State.SUCCESS -> CoreR.string.done\n                    FlashViewModel.State.FAILED -> CoreR.string.failure\n                }\n            )\n            if (it == FlashViewModel.State.SUCCESS && viewModel.showReboot) {\n                binding.restartBtn.apply {\n                    if (!this.isVisible) this.show()\n                    if (!this.isFocused) this.requestFocus()\n                }\n            }\n        }\n    }\n\n    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {\n        inflater.inflate(R.menu.menu_flash, menu)\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        return viewModel.onMenuItemClicked(item)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        defaultOrientation = activity?.requestedOrientation ?: -1\n        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED\n        if (savedInstanceState == null) {\n            viewModel.startFlashing()\n        }\n    }\n\n    @SuppressLint(\"WrongConstant\")\n    override fun onDestroyView() {\n        if (defaultOrientation != -1) {\n            activity?.requestedOrientation = defaultOrientation\n        }\n        super.onDestroyView()\n    }\n\n    override fun onKeyEvent(event: KeyEvent): Boolean {\n        return when (event.keyCode) {\n            KeyEvent.KEYCODE_VOLUME_UP,\n            KeyEvent.KEYCODE_VOLUME_DOWN -> true\n            else -> false\n        }\n    }\n\n    override fun onBackPressed(): Boolean {\n        if (viewModel.flashing.value == true)\n            return true\n        return super.onBackPressed()\n    }\n\n    override fun onPreBind(binding: FragmentFlashMd2Binding) = Unit\n\n    companion object {\n\n        private fun createIntent(context: Context, args: FlashFragmentArgs) =\n            NavDeepLinkBuilder(context)\n                .setGraph(R.navigation.main)\n                .setComponentName(MainActivity::class.java.cmp(context.packageName))\n                .setDestination(R.id.flashFragment)\n                .setArguments(args.toBundle())\n                .createPendingIntent()\n\n        private fun flashType(isSecondSlot: Boolean) =\n            if (isSecondSlot) Const.Value.FLASH_INACTIVE_SLOT else Const.Value.FLASH_MAGISK\n\n        /* Flashing is understood as installing / flashing magisk itself */\n\n        fun flash(isSecondSlot: Boolean) = MainDirections.actionFlashFragment(\n            action = flashType(isSecondSlot)\n        )\n\n        /* Patching is understood as injecting img files with magisk */\n\n        fun patch(uri: Uri) = MainDirections.actionFlashFragment(\n            action = Const.Value.PATCH_FILE,\n            additionalData = uri\n        )\n\n        /* Uninstalling is understood as removing magisk entirely */\n\n        fun uninstall() = MainDirections.actionFlashFragment(\n            action = Const.Value.UNINSTALL\n        )\n\n        /* Installing is understood as flashing modules / zips */\n\n        fun installIntent(context: Context, file: Uri) = FlashFragmentArgs(\n            action = Const.Value.FLASH_ZIP,\n            additionalData = file,\n        ).let { createIntent(context, it) }\n\n        fun install(file: Uri) = MainDirections.actionFlashFragment(\n            action = Const.Value.FLASH_ZIP,\n            additionalData = file,\n        )\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport android.view.MenuItem\nimport androidx.databinding.Bindable\nimport androidx.databinding.ObservableArrayList\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.map\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.ktx.reboot\nimport com.topjohnwu.magisk.core.ktx.synchronized\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.tasks.FlashZip\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport com.topjohnwu.superuser.CallbackList\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\nclass FlashViewModel : BaseViewModel() {\n\n    enum class State {\n        FLASHING, SUCCESS, FAILED\n    }\n\n    private val _state = MutableLiveData(State.FLASHING)\n    val state: LiveData<State> get() = _state\n    val flashing = state.map { it == State.FLASHING }\n\n    @get:Bindable\n    var showReboot = Info.isRooted\n        set(value) = set(value, field, { field = it }, BR.showReboot)\n\n    val items = ObservableArrayList<ConsoleItem>()\n    lateinit var args: FlashFragmentArgs\n\n    private val logItems = mutableListOf<String>().synchronized()\n    private val outItems = object : CallbackList<String>() {\n        override fun onAddElement(e: String?) {\n            e ?: return\n            items.add(ConsoleItem(e))\n            logItems.add(e)\n        }\n    }\n\n    fun startFlashing() {\n        val (action, uri) = args\n\n        viewModelScope.launch {\n            val result = when (action) {\n                Const.Value.FLASH_ZIP -> {\n                    uri ?: return@launch\n                    FlashZip(uri, outItems, logItems).exec()\n                }\n                Const.Value.UNINSTALL -> {\n                    showReboot = false\n                    MagiskInstaller.Uninstall(outItems, logItems).exec()\n                }\n                Const.Value.FLASH_MAGISK -> {\n                    if (Info.isEmulator)\n                        MagiskInstaller.Emulator(outItems, logItems).exec()\n                    else\n                        MagiskInstaller.Direct(outItems, logItems).exec()\n                }\n                Const.Value.FLASH_INACTIVE_SLOT -> {\n                    showReboot = false\n                    MagiskInstaller.SecondSlot(outItems, logItems).exec()\n                }\n                Const.Value.PATCH_FILE -> {\n                    uri ?: return@launch\n                    showReboot = false\n                    MagiskInstaller.Patch(uri, outItems, logItems).exec()\n                }\n                else -> {\n                    back()\n                    return@launch\n                }\n            }\n            onResult(result)\n        }\n    }\n\n    private fun onResult(success: Boolean) {\n        _state.value = if (success) State.SUCCESS else State.FAILED\n    }\n\n    fun onMenuItemClicked(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_save -> savePressed()\n        }\n        return true\n    }\n\n    private fun savePressed() = withExternalRW {\n        viewModelScope.launch(Dispatchers.IO) {\n            val name = \"magisk_install_log_%s.log\".format(\n                System.currentTimeMillis().toTime(timeFormatStandard)\n            )\n            val file = MediaStoreUtils.getFile(name)\n            file.uri.outputStream().bufferedWriter().use { writer ->\n                synchronized(logItems) {\n                    logItems.forEach {\n                        writer.write(it)\n                        writer.newLine()\n                    }\n                }\n            }\n            SnackbarEvent(file.toString()).publish()\n        }\n    }\n\n    fun restartPressed() = reboot()\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/home/DeveloperItem.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.core.R as CoreR\n\ninterface Dev {\n    val name: String\n}\n\nprivate interface JohnImpl : Dev {\n    override val name get() = \"topjohnwu\"\n}\n\nprivate interface VvbImpl : Dev {\n    override val name get() = \"vvb2060\"\n}\n\nprivate interface YUImpl : Dev {\n    override val name get() = \"yujincheng08\"\n}\n\nprivate interface RikkaImpl : Dev {\n    override val name get() = \"RikkaW\"\n}\n\nprivate interface CanyieImpl : Dev {\n    override val name get() = \"canyie\"\n}\n\nsealed class DeveloperItem : Dev {\n\n    abstract val items: List<IconLink>\n    val handle get() = \"@${name}\"\n\n    object John : DeveloperItem(), JohnImpl {\n        override val items =\n            listOf(\n                object : IconLink.Twitter(), JohnImpl {},\n                IconLink.Github.Project\n            )\n    }\n\n    object Vvb : DeveloperItem(), VvbImpl {\n        override val items =\n            listOf<IconLink>(\n                object : IconLink.Twitter(), VvbImpl {},\n                object : IconLink.Github.User(), VvbImpl {}\n            )\n    }\n\n    object YU : DeveloperItem(), YUImpl {\n        override val items =\n            listOf<IconLink>(\n                object : IconLink.Twitter() { override val name = \"shanasaimoe\" },\n                object : IconLink.Github.User(), YUImpl {},\n                object : IconLink.Sponsor(), YUImpl {}\n            )\n    }\n\n    object Rikka : DeveloperItem(), RikkaImpl {\n        override val items =\n            listOf<IconLink>(\n                object : IconLink.Twitter() { override val name = \"rikkawww\" },\n                object : IconLink.Github.User(), RikkaImpl {}\n            )\n    }\n\n    object Canyie : DeveloperItem(), CanyieImpl {\n        override val items =\n            listOf<IconLink>(\n                object : IconLink.Twitter() { override val name = \"canyie2977\" },\n                object : IconLink.Github.User(), CanyieImpl {}\n            )\n    }\n}\n\nsealed class IconLink : RvItem() {\n\n    abstract val icon: Int\n    abstract val title: Int\n    abstract val link: String\n\n    override val layoutRes get() = R.layout.item_icon_link\n\n    abstract class PayPal : IconLink(), Dev {\n        override val icon get() = CoreR.drawable.ic_paypal\n        override val title get() = CoreR.string.paypal\n        override val link get() = \"https://paypal.me/$name\"\n\n        object Project : PayPal() {\n            override val name: String get() = \"magiskdonate\"\n        }\n    }\n\n    object Patreon : IconLink() {\n        override val icon get() = CoreR.drawable.ic_patreon\n        override val title get() = CoreR.string.patreon\n        override val link get() = Const.Url.PATREON_URL\n    }\n\n    abstract class Twitter : IconLink(), Dev {\n        override val icon get() = CoreR.drawable.ic_twitter\n        override val title get() = CoreR.string.twitter\n        override val link get() = \"https://twitter.com/$name\"\n    }\n\n    abstract class Github : IconLink() {\n        override val icon get() = CoreR.drawable.ic_github\n        override val title get() = CoreR.string.github\n\n        abstract class User : Github(), Dev {\n            override val link get() = \"https://github.com/$name\"\n        }\n\n        object Project : Github() {\n            override val link get() = Const.Url.SOURCE_CODE_URL\n        }\n    }\n\n    abstract class Sponsor : IconLink(), Dev {\n        override val icon get() = CoreR.drawable.ic_favorite\n        override val title get() = CoreR.string.github\n        override val link get() = \"https://github.com/sponsors/$name\"\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/home/HomeFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.core.view.MenuProvider\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.databinding.FragmentHomeMd2Binding\nimport com.topjohnwu.magisk.core.R as CoreR\nimport androidx.navigation.findNavController\nimport com.topjohnwu.magisk.arch.NavigationActivity\n\nclass HomeFragment : BaseFragment<FragmentHomeMd2Binding>(), MenuProvider {\n\n    override val layoutRes = R.layout.fragment_home_md2\n    override val viewModel by viewModel<HomeViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        activity?.setTitle(CoreR.string.section_home)\n        DownloadEngine.observeProgress(this, viewModel::onProgressUpdate)\n    }\n\n    private fun checkTitle(text: TextView, icon: ImageView) {\n        text.post {\n            if (text.layout?.getEllipsisCount(0) != 0) {\n                with (icon) {\n                    layoutParams.width = 0\n                    layoutParams.height = 0\n                    requestLayout()\n                }\n            }\n        }\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View {\n        super.onCreateView(inflater, container, savedInstanceState)\n\n        // If titles are squished, hide icons\n        with(binding.homeMagiskWrapper) {\n            checkTitle(homeMagiskTitle, homeMagiskIcon)\n        }\n        with(binding.homeManagerWrapper) {\n            checkTitle(homeManagerTitle, homeManagerIcon)\n        }\n\n        return binding.root\n    }\n\n    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {\n        inflater.inflate(R.menu.menu_home_md2, menu)\n        if (!Info.isRooted)\n            menu.removeItem(R.id.action_reboot)\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_settings ->\n                activity?.let {\n                    NavigationActivity.navigate(\n                        HomeFragmentDirections.actionHomeFragmentToSettingsFragment(),\n                        it.findNavController(R.id.main_nav_host),\n                        it.contentResolver,\n                    )\n                }\n            R.id.action_reboot -> activity?.let { RebootMenu.inflate(it).show() }\n            else -> return super.onOptionsItemSelected(item)\n        }\n        return true\n    }\n\n    override fun onResume() {\n        super.onResume()\n        viewModel.stateManagerProgress = 0\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.widget.Toast\nimport androidx.core.net.toUri\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.ActivityExecutor\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.arch.ContextExecutor\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.arch.ViewEvent\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.download.Subject.App\nimport com.topjohnwu.magisk.core.ktx.await\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.dialog.EnvFixDialog\nimport com.topjohnwu.magisk.dialog.ManagerInstallDialog\nimport com.topjohnwu.magisk.dialog.UninstallDialog\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport com.topjohnwu.magisk.utils.asText\nimport com.topjohnwu.superuser.Shell\nimport kotlin.math.roundToInt\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass HomeViewModel(\n    private val svc: NetworkService\n) : AsyncLoadViewModel() {\n\n    enum class State {\n        LOADING, INVALID, OUTDATED, UP_TO_DATE\n    }\n\n    val magiskTitleBarrierIds =\n        intArrayOf(R.id.home_magisk_icon, R.id.home_magisk_title, R.id.home_magisk_button)\n    val appTitleBarrierIds =\n        intArrayOf(R.id.home_manager_icon, R.id.home_manager_title, R.id.home_manager_button)\n\n    @get:Bindable\n    var isNoticeVisible = Config.safetyNotice\n        set(value) = set(value, field, { field = it }, BR.noticeVisible)\n\n    val magiskState\n        get() = when {\n            Info.isRooted && Info.env.isUnsupported -> State.OUTDATED\n            !Info.env.isActive -> State.INVALID\n            Info.env.versionCode < BuildConfig.APP_VERSION_CODE -> State.OUTDATED\n            else -> State.UP_TO_DATE\n        }\n\n    @get:Bindable\n    var appState = State.LOADING\n        set(value) = set(value, field, { field = it }, BR.appState)\n\n    val magiskInstalledVersion\n        get() = Info.env.run {\n            if (isActive)\n                (\"$versionString ($versionCode)\" + if (isDebug) \" (D)\" else \"\").asText()\n            else\n                CoreR.string.not_available.asText()\n        }\n\n    @get:Bindable\n    var managerRemoteVersion = CoreR.string.loading.asText()\n        set(value) = set(value, field, { field = it }, BR.managerRemoteVersion)\n\n    val managerInstalledVersion\n        get() = \"${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\" +\n            if (BuildConfig.DEBUG) \" (D)\" else \"\"\n\n    @get:Bindable\n    var stateManagerProgress = 0\n        set(value) = set(value, field, { field = it }, BR.stateManagerProgress)\n\n    val extraBindings = bindExtra {\n        it.put(BR.viewModel, this)\n    }\n\n    companion object {\n        private var checkedEnv = false\n    }\n\n    override suspend fun doLoadWork() {\n        appState = State.LOADING\n        Info.fetchUpdate(svc)?.apply {\n            appState = when {\n                BuildConfig.APP_VERSION_CODE < versionCode -> State.OUTDATED\n                else -> State.UP_TO_DATE\n            }\n\n            val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL\n            managerRemoteVersion =\n                (\"$version (${versionCode})\" + if (isDebug) \" (D)\" else \"\").asText()\n        } ?: run {\n            appState = State.INVALID\n            managerRemoteVersion = CoreR.string.not_available.asText()\n        }\n        ensureEnv()\n    }\n\n    override fun onNetworkChanged(network: Boolean) = startLoading()\n\n    fun onProgressUpdate(progress: Float, subject: Subject) {\n        if (subject is App)\n            stateManagerProgress = progress.times(100f).roundToInt()\n    }\n\n    fun onLinkPressed(link: String) = object : ViewEvent(), ContextExecutor {\n        override fun invoke(context: Context) {\n            val intent = Intent(Intent.ACTION_VIEW, link.toUri())\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            try {\n                context.startActivity(intent)\n            } catch (e: ActivityNotFoundException) {\n                context.toast(CoreR.string.open_link_failed_toast, Toast.LENGTH_SHORT)\n            }\n        }\n    }.publish()\n\n    fun onDeletePressed() = UninstallDialog().show()\n\n    fun onManagerPressed() = when (appState) {\n        State.LOADING -> SnackbarEvent(CoreR.string.loading).publish()\n        State.INVALID -> SnackbarEvent(CoreR.string.no_connection).publish()\n        else -> withExternalRW {\n            withInstallPermission {\n                ManagerInstallDialog().show()\n            }\n        }\n    }\n\n    fun onMagiskPressed() = withExternalRW {\n        HomeFragmentDirections.actionHomeFragmentToInstallFragment().navigate()\n    }\n\n    fun hideNotice() {\n        Config.safetyNotice = false\n        isNoticeVisible = false\n    }\n\n    private suspend fun ensureEnv() {\n        if (magiskState == State.INVALID || checkedEnv) return\n        val cmd = \"env_check ${Info.env.versionString} ${Info.env.versionCode}\"\n        val code = Shell.cmd(cmd).await().code\n        if (code != 0) {\n            EnvFixDialog(this, code).show()\n        }\n        checkedEnv = true\n    }\n\n    val showTest = false\n    fun onTestPressed() = object : ViewEvent(), ActivityExecutor {\n        override fun invoke(activity: UIActivity<*>) {\n            /* Entry point to trigger test events within the app */\n        }\n    }.publish()\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/home/RebootMenu.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport android.app.Activity\nimport android.os.Build\nimport android.os.PowerManager\nimport android.view.ContextThemeWrapper\nimport android.view.MenuItem\nimport android.widget.PopupMenu\nimport androidx.core.content.getSystemService\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.ktx.reboot as systemReboot\n\nobject RebootMenu {\n\n    private fun reboot(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_reboot_normal -> systemReboot()\n            R.id.action_reboot_userspace -> systemReboot(\"userspace\")\n            R.id.action_reboot_bootloader -> systemReboot(\"bootloader\")\n            R.id.action_reboot_download -> systemReboot(\"download\")\n            R.id.action_reboot_edl -> systemReboot(\"edl\")\n            R.id.action_reboot_recovery -> systemReboot(\"recovery\")\n            R.id.action_reboot_safe_mode -> {\n                val status = !item.isChecked\n                item.isChecked = status\n                Config.bootloop = if (status) 2 else 0\n            }\n            else -> Unit\n        }\n        return true\n    }\n\n    fun inflate(activity: Activity): PopupMenu {\n        val themeWrapper = ContextThemeWrapper(activity, R.style.Foundation_PopupMenu)\n        val menu = PopupMenu(themeWrapper, activity.findViewById(R.id.action_reboot))\n        activity.menuInflater.inflate(R.menu.menu_reboot, menu.menu)\n        menu.setOnMenuItemClickListener(RebootMenu::reboot)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&\n            activity.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true) {\n            menu.menu.findItem(R.id.action_reboot_userspace).isVisible = true\n        }\n        if (Const.Version.atLeast_28_0()) {\n            menu.menu.findItem(R.id.action_reboot_safe_mode).isChecked = Config.bootloop >= 2\n        } else {\n            menu.menu.findItem(R.id.action_reboot_safe_mode).isVisible = false\n        }\n        return menu\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.install\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.databinding.FragmentInstallMd2Binding\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass InstallFragment : BaseFragment<FragmentInstallMd2Binding>() {\n\n    override val layoutRes = R.layout.fragment_install_md2\n    override val viewModel by viewModel<InstallViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        requireActivity().setTitle(CoreR.string.install)\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.install\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.text.Spanned\nimport android.text.SpannedString\nimport android.widget.Toast\nimport androidx.databinding.Bindable\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_CODE\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.base.ContentResultCallback\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.dialog.SecondSlotWarningDialog\nimport com.topjohnwu.magisk.events.GetContentEvent\nimport com.topjohnwu.magisk.ui.flash.FlashFragment\nimport io.noties.markwon.Markwon\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport kotlinx.parcelize.Parcelize\nimport timber.log.Timber\nimport java.io.File\nimport java.io.IOException\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass InstallViewModel(svc: NetworkService, markwon: Markwon) : BaseViewModel() {\n\n    val isRooted get() = Info.isRooted\n    val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk)\n    val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator\n\n    @get:Bindable\n    var step = if (skipOptions) 1 else 0\n        set(value) = set(value, field, { field = it }, BR.step)\n\n    private var methodId = -1\n\n    @get:Bindable\n    var method\n        get() = methodId\n        set(value) = set(value, methodId, { methodId = it }, BR.method) {\n            when (it) {\n                R.id.method_patch -> {\n                    GetContentEvent(\"*/*\", UriCallback()).publish()\n                }\n                R.id.method_inactive_slot -> {\n                    SecondSlotWarningDialog().show()\n                }\n            }\n        }\n\n    val data: LiveData<Uri?> get() = uri\n\n    @get:Bindable\n    var notes: Spanned = SpannedString(\"\")\n        set(value) = set(value, field, { field = it }, BR.notes)\n\n    init {\n        viewModelScope.launch(Dispatchers.IO) {\n            try {\n                val noteFile = File(AppContext.cacheDir, \"${APP_VERSION_CODE}.md\")\n                val noteText = when {\n                    noteFile.exists() -> noteFile.readText()\n                    else -> {\n                        val note = svc.fetchUpdate(APP_VERSION_CODE)?.note.orEmpty()\n                        if (note.isEmpty()) return@launch\n                        noteFile.writeText(note)\n                        note\n                    }\n                }\n                val spanned = markwon.toMarkdown(noteText)\n                withContext(Dispatchers.Main) {\n                    notes = spanned\n                }\n            } catch (e: IOException) {\n                Timber.e(e)\n            }\n        }\n    }\n\n    fun install() {\n        when (method) {\n            R.id.method_patch -> FlashFragment.patch(data.value!!).navigate(true)\n            R.id.method_direct -> FlashFragment.flash(false).navigate(true)\n            R.id.method_inactive_slot -> FlashFragment.flash(true).navigate(true)\n            else -> error(\"Unknown value\")\n        }\n    }\n\n    override fun onSaveState(state: Bundle) {\n        state.putParcelable(\n            INSTALL_STATE_KEY, InstallState(\n                methodId,\n                step,\n                Config.keepVerity,\n                Config.keepEnc,\n                Config.recovery\n            )\n        )\n    }\n\n    override fun onRestoreState(state: Bundle) {\n        state.getParcelable<InstallState>(INSTALL_STATE_KEY)?.let {\n            methodId = it.method\n            step = it.step\n            Config.keepVerity = it.keepVerity\n            Config.keepEnc = it.keepEnc\n            Config.recovery = it.recovery\n        }\n    }\n\n    @Parcelize\n    class UriCallback : ContentResultCallback {\n        override fun onActivityLaunch() {\n            AppContext.toast(CoreR.string.patch_file_msg, Toast.LENGTH_LONG)\n        }\n\n        override fun onActivityResult(result: Uri) {\n            uri.value = result\n        }\n    }\n\n    @Parcelize\n    class InstallState(\n        val method: Int,\n        val step: Int,\n        val keepVerity: Boolean,\n        val keepEnc: Boolean,\n        val recovery: Boolean,\n    ) : Parcelable\n\n    companion object {\n        private const val INSTALL_STATE_KEY = \"install_state\"\n        private val uri = MutableLiveData<Uri?>()\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.HorizontalScrollView\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.isVisible\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.databinding.FragmentLogMd2Binding\nimport com.topjohnwu.magisk.ui.MainActivity\nimport com.topjohnwu.magisk.utils.AccessibilityUtils\nimport com.topjohnwu.magisk.utils.MotionRevealHelper\nimport rikka.recyclerview.addEdgeSpacing\nimport rikka.recyclerview.addItemSpacing\nimport rikka.recyclerview.fixEdgeEffect\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass LogFragment : BaseFragment<FragmentLogMd2Binding>(), MenuProvider {\n\n    override val layoutRes = R.layout.fragment_log_md2\n    override val viewModel by viewModel<LogViewModel>()\n    override val snackbarView: View?\n        get() = if (isMagiskLogVisible) binding.logFilterSuperuser.snackbarContainer\n                else super.snackbarView\n    override val snackbarAnchorView get() = binding.logFilterToggle\n\n    private var actionSave: MenuItem? = null\n    private var isMagiskLogVisible\n        get() = binding.logFilter.isVisible\n        set(value) {\n            MotionRevealHelper.withViews(binding.logFilter, binding.logFilterToggle, value)\n            actionSave?.isVisible = !value\n            with(activity as MainActivity) {\n                invalidateToolbar()\n                requestNavigationHidden(value)\n                setDisplayHomeAsUpEnabled(value)\n            }\n        }\n\n    override fun onStart() {\n        super.onStart()\n        activity?.setTitle(CoreR.string.logs)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        binding.logFilterToggle.setOnClickListener {\n            isMagiskLogVisible = true\n        }\n\n        binding.logFilterSuperuser.logSuperuser.apply {\n            addEdgeSpacing(bottom = R.dimen.l1)\n            addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)\n            fixEdgeEffect()\n        }\n\n        if (!AccessibilityUtils.isAnimationEnabled(requireContext().contentResolver)) {\n            val scrollView = view.findViewById<HorizontalScrollView>(R.id.log_scroll_magisk)\n            scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER)\n        }\n    }\n\n\n    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {\n        inflater.inflate(R.menu.menu_log_md2, menu)\n        actionSave = menu.findItem(R.id.action_save)?.also {\n            it.isVisible = !isMagiskLogVisible\n        }\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_save -> viewModel.saveMagiskLog()\n            R.id.action_clear ->\n                if (!isMagiskLogVisible) viewModel.clearMagiskLog()\n                else viewModel.clearLog()\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n\n    override fun onPreBind(binding: FragmentLogMd2Binding) = Unit\n\n    override fun onBackPressed(): Boolean {\n        if (binding.logFilter.isVisible) {\n            isMagiskLogVisible = false\n            return true\n        }\n        return super.onBackPressed()\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogRvItem.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport androidx.databinding.ViewDataBinding\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.textview.MaterialTextView\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.ViewAwareItem\n\nclass LogRvItem(\n    override val item: String\n) : ObservableRvItem(), DiffItem<LogRvItem>, ItemWrapper<String>, ViewAwareItem {\n\n    override val layoutRes = R.layout.item_log_textview\n\n    override fun onBind(binding: ViewDataBinding, recyclerView: RecyclerView) {\n        val view = binding.root as MaterialTextView\n        view.measure(0, 0)\n        val desiredWidth = view.measuredWidth\n        val layoutParams = view.layoutParams\n        layoutParams.width = desiredWidth\n        if (recyclerView.width < desiredWidth) {\n            recyclerView.requestLayout()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport android.system.Os\nimport androidx.databinding.Bindable\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.repository.LogRepository\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.diffList\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport com.topjohnwu.magisk.view.TextItem\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.FileInputStream\n\nclass LogViewModel(\n    private val repo: LogRepository\n) : AsyncLoadViewModel() {\n    @get:Bindable\n    var loading = true\n        private set(value) = set(value, field, { field = it }, BR.loading)\n\n    // --- empty view\n\n    val itemEmpty = TextItem(R.string.log_data_none)\n    val itemMagiskEmpty = TextItem(R.string.log_data_magisk_none)\n\n    // --- su log\n\n    val items = diffList<SuLogRvItem>()\n    val extraBindings = bindExtra {\n        it.put(BR.viewModel, this)\n    }\n\n    // --- magisk log\n    val logs = diffList<LogRvItem>()\n    var magiskLogRaw = \" \"\n\n    override suspend fun doLoadWork() {\n        loading = true\n\n        val (suLogs, suDiff) = withContext(Dispatchers.Default) {\n            magiskLogRaw = repo.fetchMagiskLogs()\n            val newLogs = magiskLogRaw.split('\\n').map { LogRvItem(it) }\n            logs.update(newLogs)\n            val suLogs = repo.fetchSuLogs().map { SuLogRvItem(it) }\n            suLogs to items.calculateDiff(suLogs)\n        }\n\n        items.firstOrNull()?.isTop = false\n        items.lastOrNull()?.isBottom = false\n        items.update(suLogs, suDiff)\n        items.firstOrNull()?.isTop = true\n        items.lastOrNull()?.isBottom = true\n        loading = false\n    }\n\n    fun saveMagiskLog() = withExternalRW {\n        viewModelScope.launch(Dispatchers.IO) {\n            val filename = \"magisk_log_%s.log\".format(\n                System.currentTimeMillis().toTime(timeFormatStandard))\n            val logFile = MediaStoreUtils.getFile(filename)\n            logFile.uri.outputStream().bufferedWriter().use { file ->\n                file.write(\"---Detected Device Info---\\n\\n\")\n                file.write(\"isAB=${Info.isAB}\\n\")\n                file.write(\"isSAR=${Info.isSAR}\\n\")\n                file.write(\"ramdisk=${Info.ramdisk}\\n\")\n                val uname = Os.uname()\n                file.write(\"kernel=${uname.sysname} ${uname.machine} ${uname.release} ${uname.version}\\n\")\n\n                file.write(\"\\n\\n---System Properties---\\n\\n\")\n                ProcessBuilder(\"getprop\").start()\n                    .inputStream.reader().use { it.copyTo(file) }\n\n                file.write(\"\\n\\n---Environment Variables---\\n\\n\")\n                System.getenv().forEach { (key, value) -> file.write(\"${key}=${value}\\n\") }\n\n                file.write(\"\\n\\n---System MountInfo---\\n\\n\")\n                FileInputStream(\"/proc/self/mountinfo\").reader().use { it.copyTo(file) }\n\n                file.write(\"\\n---Magisk Logs---\\n\")\n                file.write(\"${Info.env.versionString} (${Info.env.versionCode})\\n\\n\")\n                if (Info.env.isActive) file.write(magiskLogRaw)\n\n                file.write(\"\\n---Manager Logs---\\n\")\n                file.write(\"${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\\n\\n\")\n                ProcessBuilder(\"logcat\", \"-d\").start()\n                    .inputStream.reader().use { it.copyTo(file) }\n            }\n            SnackbarEvent(logFile.toString()).publish()\n        }\n    }\n\n    fun clearMagiskLog() = repo.clearMagiskLogs {\n        SnackbarEvent(R.string.logs_cleared).publish()\n        startLoading()\n    }\n\n    fun clearLog() = viewModelScope.launch {\n        repo.clearLogs()\n        SnackbarEvent(R.string.logs_cleared).publish()\n        startLoading()\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/log/SuLogRvItem.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.ktx.timeDateFormat\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass SuLogRvItem(val log: SuLog) : ObservableRvItem(), DiffItem<SuLogRvItem> {\n\n    override val layoutRes = R.layout.item_log_access_md2\n\n    val info = genInfo()\n\n    @get:Bindable\n    var isTop = false\n        set(value) = set(value, field, { field = it }, BR.top)\n\n    @get:Bindable\n    var isBottom = false\n        set(value) = set(value, field, { field = it }, BR.bottom)\n\n    override fun itemSameAs(other: SuLogRvItem) = log.appName == other.log.appName\n\n    private fun genInfo(): String {\n        val res = AppContext.resources\n        val sb = StringBuilder()\n        val date = log.time.toTime(timeDateFormat)\n        val toUid = res.getString(CoreR.string.target_uid, log.toUid)\n        val fromPid = res.getString(CoreR.string.pid, log.fromPid)\n        sb.append(\"$date\\n$toUid  $fromPid\")\n        if (log.target != -1) {\n            val pid = if (log.target == 0) \"magiskd\" else log.target.toString()\n            val target = res.getString(CoreR.string.target_pid, pid)\n            sb.append(\"  $target\")\n        }\n        if (log.context.isNotEmpty()) {\n            val context = res.getString(CoreR.string.selinux_context, log.context)\n            sb.append(\"\\n$context\")\n        }\n        if (log.gids.isNotEmpty()) {\n            val gids = res.getString(CoreR.string.supp_group, log.gids)\n            sb.append(\"\\n$gids\")\n        }\n        sb.append(\"\\n${log.command}\")\n        return sb.toString()\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ActivityInfo\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewTreeObserver\nimport android.widget.Toast\nimport androidx.core.view.MenuProvider\nimport androidx.core.view.isVisible\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.databinding.FragmentActionMd2Binding\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass ActionFragment : BaseFragment<FragmentActionMd2Binding>(), MenuProvider {\n\n    override val layoutRes = R.layout.fragment_action_md2\n    override val viewModel by viewModel<ActionViewModel>()\n    override val snackbarView: View get() = binding.snackbarContainer\n\n    private var defaultOrientation = -1\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        viewModel.args = ActionFragmentArgs.fromBundle(requireArguments())\n    }\n\n    override fun onStart() {\n        super.onStart()\n        activity?.setTitle(viewModel.args.name)\n        binding.closeBtn.setOnClickListener {\n            activity?.onBackPressed()\n        }\n\n        viewModel.state.observe(this) {\n            if (it != ActionViewModel.State.RUNNING) {\n                binding.closeBtn.apply {\n                    if (!this.isVisible) this.show()\n                    if (!this.isFocused) this.requestFocus()\n                }\n            }\n            if (it != ActionViewModel.State.SUCCESS) return@observe\n            view?.viewTreeObserver?.addOnWindowFocusChangeListener(\n                object : ViewTreeObserver.OnWindowFocusChangeListener {\n                    override fun onWindowFocusChanged(hasFocus: Boolean) {\n                        if (hasFocus) return\n                        view?.viewTreeObserver?.removeOnWindowFocusChangeListener(this)\n                        view?.context?.apply {\n                            toast(\n                                getString(CoreR.string.done_action, viewModel.args.name),\n                                Toast.LENGTH_SHORT\n                            )\n                        }\n                        viewModel.back()\n                    }\n                }\n            )\n        }\n    }\n\n    override fun onCreateMenu(menu: Menu, inflater: MenuInflater) {\n        inflater.inflate(R.menu.menu_flash, menu)\n    }\n\n    override fun onMenuItemSelected(item: MenuItem): Boolean {\n        return viewModel.onMenuItemClicked(item)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        defaultOrientation = activity?.requestedOrientation ?: -1\n        activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED\n        if (savedInstanceState == null) {\n            viewModel.startRunAction()\n        }\n    }\n\n    @SuppressLint(\"WrongConstant\")\n    override fun onDestroyView() {\n        if (defaultOrientation != -1) {\n            activity?.requestedOrientation = defaultOrientation\n        }\n        super.onDestroyView()\n    }\n\n    override fun onKeyEvent(event: KeyEvent): Boolean {\n        return when (event.keyCode) {\n            KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> true\n\n            else -> false\n        }\n    }\n\n    override fun onBackPressed(): Boolean {\n        if (viewModel.state.value == ActionViewModel.State.RUNNING) return true\n        return super.onBackPressed()\n    }\n\n    override fun onPreBind(binding: FragmentActionMd2Binding) = Unit\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.view.MenuItem\nimport androidx.databinding.ObservableArrayList\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.ktx.synchronized\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport com.topjohnwu.magisk.ui.flash.ConsoleItem\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.IOException\n\nclass ActionViewModel : BaseViewModel() {\n\n    enum class State {\n        RUNNING, SUCCESS, FAILED\n    }\n\n    private val _state = MutableLiveData(State.RUNNING)\n    val state: LiveData<State> get() = _state\n\n    val items = ObservableArrayList<ConsoleItem>()\n    lateinit var args: ActionFragmentArgs\n\n    private val logItems = mutableListOf<String>().synchronized()\n    private val outItems = object : CallbackList<String>() {\n        override fun onAddElement(e: String?) {\n            e ?: return\n            items.add(ConsoleItem(e))\n            logItems.add(e)\n        }\n    }\n\n    fun startRunAction() = viewModelScope.launch {\n        onResult(withContext(Dispatchers.IO) {\n            try {\n                Shell.cmd(\"run_action \\'${args.id}\\'\")\n                    .to(outItems, logItems)\n                    .exec().isSuccess\n            } catch (e: IOException) {\n                Timber.e(e)\n                false\n            }\n        })\n    }\n\n    private fun onResult(success: Boolean) {\n        _state.value = if (success) State.SUCCESS else State.FAILED\n    }\n\n    fun onMenuItemClicked(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_save -> savePressed()\n        }\n        return true\n    }\n\n    private fun savePressed() = withExternalRW {\n        viewModelScope.launch(Dispatchers.IO) {\n            val name = \"%s_action_log_%s.log\".format(\n                args.name,\n                System.currentTimeMillis().toTime(timeFormatStandard)\n            )\n            val file = MediaStoreUtils.getFile(name)\n            file.uri.outputStream().bufferedWriter().use { writer ->\n                synchronized(logItems) {\n                    logItems.forEach {\n                        writer.write(it)\n                        writer.newLine()\n                    }\n                }\n            }\n            SnackbarEvent(file.toString()).publish()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.os.Bundle\nimport android.view.View\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName\nimport com.topjohnwu.magisk.databinding.FragmentModuleMd2Binding\nimport rikka.recyclerview.addEdgeSpacing\nimport rikka.recyclerview.addInvalidateItemDecorationsObserver\nimport rikka.recyclerview.addItemSpacing\nimport rikka.recyclerview.fixEdgeEffect\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass ModuleFragment : BaseFragment<FragmentModuleMd2Binding>() {\n\n    override val layoutRes = R.layout.fragment_module_md2\n    override val viewModel by viewModel<ModuleViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        activity?.title = resources.getString(CoreR.string.modules)\n        viewModel.data.observe(this) {\n            it ?: return@observe\n            val displayName = runCatching { it.displayName }.getOrNull() ?: return@observe\n            viewModel.requestInstallLocalModule(it, displayName)\n            viewModel.data.value = null\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        binding.moduleList.apply {\n            addEdgeSpacing(top = R.dimen.l_50, bottom = R.dimen.l1)\n            addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)\n            fixEdgeEffect()\n            post { addInvalidateItemDecorationsObserver() }\n        }\n    }\n\n    override fun onPreBind(binding: FragmentModuleMd2Binding) = Unit\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleRvItem.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.utils.TextHolder\nimport com.topjohnwu.magisk.utils.asText\nimport com.topjohnwu.magisk.core.R as CoreR\n\nobject InstallModule : RvItem(), DiffItem<InstallModule> {\n    override val layoutRes = R.layout.item_module_download\n}\n\nclass LocalModuleRvItem(\n    override val item: LocalModule\n) : ObservableRvItem(), DiffItem<LocalModuleRvItem>, ItemWrapper<LocalModule> {\n\n    override val layoutRes = R.layout.item_module_md2\n\n    val showNotice: Boolean\n    val showAction: Boolean\n    val noticeText: TextHolder\n\n    init {\n        val isZygisk = item.isZygisk\n        val isRiru = item.isRiru\n        val zygiskUnloaded = isZygisk && item.zygiskUnloaded\n\n        showNotice = zygiskUnloaded ||\n            (Info.isZygiskEnabled && isRiru) ||\n            (!Info.isZygiskEnabled && isZygisk)\n        showAction = item.hasAction && !showNotice\n        noticeText =\n            when {\n                zygiskUnloaded -> CoreR.string.zygisk_module_unloaded.asText()\n                isRiru -> CoreR.string.suspend_text_riru.asText(CoreR.string.zygisk.asText())\n                else -> CoreR.string.suspend_text_zygisk.asText(CoreR.string.zygisk.asText())\n            }\n    }\n\n    @get:Bindable\n    var isEnabled = item.enable\n        set(value) = set(value, field, { field = it }, BR.enabled, BR.updateReady) {\n            item.enable = value\n        }\n\n    @get:Bindable\n    var isRemoved = item.remove\n        set(value) = set(value, field, { field = it }, BR.removed, BR.updateReady) {\n            item.remove = value\n        }\n\n    @get:Bindable\n    val showUpdate get() = item.updateInfo != null\n\n    @get:Bindable\n    val updateReady get() = item.outdated && !isRemoved && isEnabled\n\n    val isUpdated = item.updated\n\n    fun fetchedUpdateInfo() {\n        notifyPropertyChanged(BR.showUpdate)\n        notifyPropertyChanged(BR.updateReady)\n    }\n\n    fun delete() {\n        isRemoved = !isRemoved\n    }\n\n    override fun itemSameAs(other: LocalModuleRvItem): Boolean = item.id == other.item.id\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.net.Uri\nimport androidx.databinding.Bindable\nimport androidx.lifecycle.MutableLiveData\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.MainDirections\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.base.ContentResultCallback\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.core.model.module.OnlineModule\nimport com.topjohnwu.magisk.databinding.MergeObservableList\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.diffList\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.dialog.LocalModuleInstallDialog\nimport com.topjohnwu.magisk.dialog.OnlineModuleInstallDialog\nimport com.topjohnwu.magisk.events.GetContentEvent\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport kotlinx.parcelize.Parcelize\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass ModuleViewModel : AsyncLoadViewModel() {\n\n    val bottomBarBarrierIds = intArrayOf(R.id.module_update, R.id.module_remove)\n\n    private val itemsInstalled = diffList<LocalModuleRvItem>()\n\n    val items = MergeObservableList<RvItem>()\n    val extraBindings = bindExtra {\n        it.put(BR.viewModel, this)\n    }\n\n    val data get() = uri\n\n    @get:Bindable\n    var loading = true\n        private set(value) = set(value, field, { field = it }, BR.loading)\n\n    override suspend fun doLoadWork() {\n        loading = true\n        val moduleLoaded = Info.env.isActive &&\n                withContext(Dispatchers.IO) { LocalModule.loaded() }\n        if (moduleLoaded) {\n            loadInstalled()\n            if (items.isEmpty()) {\n                items.insertItem(InstallModule)\n                    .insertList(itemsInstalled)\n            }\n        }\n        loading = false\n        loadUpdateInfo()\n    }\n\n    override fun onNetworkChanged(network: Boolean) = startLoading()\n\n    private suspend fun loadInstalled() {\n        withContext(Dispatchers.Default) {\n            val installed = LocalModule.installed().map { LocalModuleRvItem(it) }\n            itemsInstalled.update(installed)\n        }\n    }\n\n    private suspend fun loadUpdateInfo() {\n        withContext(Dispatchers.IO) {\n            itemsInstalled.forEach {\n                if (it.item.fetch())\n                    it.fetchedUpdateInfo()\n            }\n        }\n    }\n\n    fun downloadPressed(item: OnlineModule?) =\n        if (item != null && Info.isConnected.value == true) {\n            withExternalRW { OnlineModuleInstallDialog(item).show() }\n        } else {\n            SnackbarEvent(CoreR.string.no_connection).publish()\n        }\n\n    fun installPressed() = withExternalRW {\n        GetContentEvent(\"application/zip\", UriCallback()).publish()\n    }\n\n    fun requestInstallLocalModule(uri: Uri, displayName: String) {\n        LocalModuleInstallDialog(this, uri, displayName).show()\n    }\n\n    @Parcelize\n    class UriCallback : ContentResultCallback {\n        override fun onActivityResult(result: Uri) {\n            uri.value = result\n        }\n    }\n\n    fun runAction(id: String, name: String) {\n        MainDirections.actionActionFragment(id, name).navigate()\n    }\n\n    companion object {\n        private val uri = MutableLiveData<Uri?>()\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/BaseSettingsItem.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.view.View\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.ktx.activity\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.utils.TextHolder\nimport com.topjohnwu.magisk.view.MagiskDialog\n\nsealed class BaseSettingsItem : ObservableRvItem() {\n\n    interface Handler {\n        fun onItemPressed(view: View, item: BaseSettingsItem, andThen: () -> Unit)\n        fun onItemAction(view: View, item: BaseSettingsItem)\n    }\n\n    override val layoutRes get() = R.layout.item_settings\n\n    open val icon: Int get() = 0\n    open val title: TextHolder get() = TextHolder.EMPTY\n    @get:Bindable\n    open val description: TextHolder get() = TextHolder.EMPTY\n    @get:Bindable\n    var isEnabled = true\n        set(value) = set(value, field, { field = it }, BR.enabled, BR.description)\n\n    open fun onPressed(view: View, handler: Handler) {\n        handler.onItemPressed(view, this) {\n            handler.onItemAction(view, this)\n        }\n    }\n    open fun refresh() {}\n\n    // Only for toggle\n    open val showSwitch get() = false\n    @get:Bindable\n    open val isChecked get() = false\n    fun onToggle(view: View, handler: Handler, checked: Boolean) =\n        set(checked, isChecked, { onPressed(view, handler) })\n\n    abstract class Value<T> : BaseSettingsItem() {\n\n        /**\n         * Represents last agreed-upon value by the validation process and the user for current\n         * child. Be very aware that this shouldn't be **set** unless both sides agreed that _that_\n         * is the new value.\n         * */\n        abstract var value: T\n            protected set\n    }\n\n    abstract class Toggle : Value<Boolean>() {\n\n        override val showSwitch get() = true\n        override val isChecked get() = value\n\n        override fun onPressed(view: View, handler: Handler) {\n            // Make sure the checked state is synced\n            notifyPropertyChanged(BR.checked)\n            handler.onItemPressed(view, this) {\n                value = !value\n                notifyPropertyChanged(BR.checked)\n                handler.onItemAction(view, this)\n            }\n        }\n    }\n\n    abstract class Input : Value<String>() {\n\n        @get:Bindable\n        abstract val inputResult: String?\n\n        override fun onPressed(view: View, handler: Handler) {\n            handler.onItemPressed(view, this) {\n                MagiskDialog(view.activity).apply {\n                    setTitle(title.getText(view.resources))\n                    setView(getView(view.context))\n                    setButton(MagiskDialog.ButtonType.POSITIVE) {\n                        text = android.R.string.ok\n                        onClick {\n                            inputResult?.let { result ->\n                                doNotDismiss = false\n                                value = result\n                                handler.onItemAction(view, this@Input)\n                                return@onClick\n                            }\n                            doNotDismiss = true\n                        }\n                    }\n                    setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                        text = android.R.string.cancel\n                    }\n                }.show()\n            }\n        }\n\n        abstract fun getView(context: Context): View\n    }\n\n    abstract class Selector : Value<Int>() {\n\n        open val entryRes get() = -1\n        open val descriptionRes get() = entryRes\n        open fun entries(res: Resources) = res.getArrayOrEmpty(entryRes)\n        open fun descriptions(res: Resources) = res.getArrayOrEmpty(descriptionRes)\n\n        override val description = object : TextHolder() {\n            override fun getText(resources: Resources): CharSequence {\n                return descriptions(resources).getOrElse(value) { \"\" }\n            }\n        }\n\n        private fun Resources.getArrayOrEmpty(id: Int): Array<String> =\n            runCatching { getStringArray(id) }.getOrDefault(emptyArray())\n\n        override fun onPressed(view: View, handler: Handler) {\n            handler.onItemPressed(view, this) {\n                MagiskDialog(view.activity).apply {\n                    setTitle(title.getText(view.resources))\n                    setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                        text = android.R.string.cancel\n                    }\n                    setListItems(entries(view.resources)) {\n                        if (value != it) {\n                            value = it\n                            notifyPropertyChanged(BR.description)\n                            handler.onItemAction(view, this@Selector)\n                        }\n                    }\n                }.show()\n            }\n        }\n    }\n\n    abstract class Blank : BaseSettingsItem()\n\n    abstract class Section : BaseSettingsItem() {\n        override val layoutRes = R.layout.item_settings_section\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.os.Bundle\nimport android.view.View\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.databinding.FragmentSettingsMd2Binding\nimport rikka.recyclerview.addEdgeSpacing\nimport rikka.recyclerview.addItemSpacing\nimport rikka.recyclerview.fixEdgeEffect\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass SettingsFragment : BaseFragment<FragmentSettingsMd2Binding>() {\n\n    override val layoutRes = R.layout.fragment_settings_md2\n    override val viewModel by viewModel<SettingsViewModel>()\n    override val snackbarView: View get() = binding.snackbarContainer\n\n    override fun onStart() {\n        super.onStart()\n\n        activity?.title = resources.getString(CoreR.string.settings)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        binding.settingsList.apply {\n            addEdgeSpacing(bottom = R.dimen.l1)\n            addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)\n            fixEdgeEffect()\n        }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        viewModel.items.forEach { it.refresh() }\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsItems.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.os.Build\nimport android.view.LayoutInflater\nimport android.view.View\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.ktx.activity\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding\nimport com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding\nimport com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.utils.TextHolder\nimport com.topjohnwu.magisk.utils.asText\nimport com.topjohnwu.magisk.view.MagiskDialog\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.magisk.core.R as CoreR\n\n// --- Customization\n\nobject Customization : BaseSettingsItem.Section() {\n    override val title = CoreR.string.settings_customization.asText()\n}\n\nobject Language : BaseSettingsItem.Selector() {\n    private val names: Array<String> get() = LocaleSetting.available.names\n    private val tags: Array<String> get() = LocaleSetting.available.tags\n\n    override var value\n        get() = tags.indexOf(Config.locale)\n        set(value) {\n            Config.locale = tags[value]\n        }\n\n    override val title = CoreR.string.language.asText()\n\n    override fun entries(res: Resources) = names\n    override fun descriptions(res: Resources) = names\n}\n\nobject LanguageSystem : BaseSettingsItem.Blank() {\n    override val title = CoreR.string.language.asText()\n    override val description: TextHolder\n        get() {\n            val locale = LocaleSetting.instance.appLocale\n            return locale?.getDisplayName(locale)?.asText() ?: CoreR.string.system_default.asText()\n        }\n}\n\nobject Theme : BaseSettingsItem.Blank() {\n    override val icon = R.drawable.ic_paint\n    override val title = CoreR.string.section_theme.asText()\n}\n\n// --- App\n\nobject AppSettings : BaseSettingsItem.Section() {\n    override val title = CoreR.string.home_app_title.asText()\n}\n\nobject Hide : BaseSettingsItem.Input() {\n    override val title = CoreR.string.settings_hide_app_title.asText()\n    override val description = CoreR.string.settings_hide_app_summary.asText()\n    override var value = \"\"\n\n    override val inputResult\n        get() = if (isError) null else result\n\n    @get:Bindable\n    var result = \"Settings\"\n        set(value) = set(value, field, { field = it }, BR.result, BR.error)\n\n    val maxLength\n        get() = AppMigration.MAX_LABEL_LENGTH\n\n    @get:Bindable\n    val isError\n        get() = result.length > maxLength || result.isBlank()\n\n    override fun getView(context: Context) = DialogSettingsAppNameBinding\n        .inflate(LayoutInflater.from(context)).also { it.data = this }.root\n}\n\nobject Restore : BaseSettingsItem.Blank() {\n    override val title = CoreR.string.settings_restore_app_title.asText()\n    override val description = CoreR.string.settings_restore_app_summary.asText()\n\n    override fun onPressed(view: View, handler: Handler) {\n        handler.onItemPressed(view, this) {\n            MagiskDialog(view.activity).apply {\n                setTitle(CoreR.string.settings_restore_app_title)\n                setMessage(CoreR.string.restore_app_confirmation)\n                setButton(MagiskDialog.ButtonType.POSITIVE) {\n                    text = android.R.string.ok\n                    onClick {\n                        handler.onItemAction(view, this@Restore)\n                    }\n                }\n                setButton(MagiskDialog.ButtonType.NEGATIVE) {\n                    text = android.R.string.cancel\n                }\n                setCancelable(true)\n                show()\n            }\n        }\n    }\n}\n\nobject AddShortcut : BaseSettingsItem.Blank() {\n    override val title = CoreR.string.add_shortcut_title.asText()\n    override val description = CoreR.string.setting_add_shortcut_summary.asText()\n}\n\nobject DownloadPath : BaseSettingsItem.Input() {\n    override var value\n        get() = Config.downloadDir\n        set(value) {\n            Config.downloadDir = value\n            notifyPropertyChanged(BR.description)\n        }\n\n    override val title = CoreR.string.settings_download_path_title.asText()\n    override val description get() = MediaStoreUtils.fullPath(value).asText()\n\n    override var inputResult: String = value\n        set(value) = set(value, field, { field = it }, BR.inputResult, BR.path)\n\n    @get:Bindable\n    val path get() = MediaStoreUtils.fullPath(inputResult)\n\n    override fun getView(context: Context) = DialogSettingsDownloadPathBinding\n        .inflate(LayoutInflater.from(context)).also { it.data = this }.root\n}\n\nobject UpdateChannel : BaseSettingsItem.Selector() {\n    override var value\n        get() = Config.updateChannel\n        set(value) {\n            Config.updateChannel = value\n            Info.resetUpdate()\n        }\n\n    override val title = CoreR.string.settings_update_channel_title.asText()\n    override val entryRes = CoreR.array.update_channel\n}\n\nobject UpdateChannelUrl : BaseSettingsItem.Input() {\n    override val title = CoreR.string.settings_update_custom.asText()\n    override val description get() = value.asText()\n    override var value\n        get() = Config.customChannelUrl\n        set(value) {\n            Config.customChannelUrl = value\n            Info.resetUpdate()\n            notifyPropertyChanged(BR.description)\n        }\n\n    override var inputResult: String = value\n        set(value) = set(value, field, { field = it }, BR.inputResult)\n\n    override fun refresh() {\n        isEnabled = UpdateChannel.value == Config.Value.CUSTOM_CHANNEL\n    }\n\n    override fun getView(context: Context) = DialogSettingsUpdateChannelBinding\n        .inflate(LayoutInflater.from(context)).also { it.data = this }.root\n}\n\nobject UpdateChecker : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_check_update_title.asText()\n    override val description = CoreR.string.settings_check_update_summary.asText()\n    override var value by Config::checkUpdate\n}\n\nobject DoHToggle : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_doh_title.asText()\n    override val description = CoreR.string.settings_doh_description.asText()\n    override var value by Config::doh\n}\n\nobject SystemlessHosts : BaseSettingsItem.Blank() {\n    override val title = CoreR.string.settings_hosts_title.asText()\n    override val description = CoreR.string.settings_hosts_summary.asText()\n}\n\nobject RandNameToggle : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_random_name_title.asText()\n    override val description = CoreR.string.settings_random_name_description.asText()\n    override var value by Config::randName\n}\n\n// --- Magisk\n\nobject Magisk : BaseSettingsItem.Section() {\n    override val title = CoreR.string.magisk.asText()\n}\n\nobject Zygisk : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.zygisk.asText()\n    override val description get() =\n        if (mismatch) CoreR.string.reboot_apply_change.asText()\n        else CoreR.string.settings_zygisk_summary.asText()\n    override var value\n        get() = Config.zygisk\n        set(value) {\n            Config.zygisk = value\n            notifyPropertyChanged(BR.description)\n        }\n    val mismatch get() = value != Info.isZygiskEnabled\n}\n\nobject DenyList : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_denylist_title.asText()\n    override val description get() = CoreR.string.settings_denylist_summary.asText()\n\n    override var value = Config.denyList\n        set(value) {\n            field = value\n            val cmd = if (value) \"enable\" else \"disable\"\n            Shell.cmd(\"magisk --denylist $cmd\").submit { result ->\n                if (result.isSuccess) {\n                    Config.denyList = value\n                } else {\n                    field = !value\n                    notifyPropertyChanged(BR.checked)\n                }\n            }\n        }\n}\n\nobject DenyListConfig : BaseSettingsItem.Blank() {\n    override val title = CoreR.string.settings_denylist_config_title.asText()\n    override val description = CoreR.string.settings_denylist_config_summary.asText()\n}\n\n// --- Superuser\n\nobject Tapjack : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_su_tapjack_title.asText()\n    override val description = CoreR.string.settings_su_tapjack_summary.asText()\n    override var value by Config::suTapjack\n}\n\nobject Authentication : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_su_auth_title.asText()\n    override var description = CoreR.string.settings_su_auth_summary.asText()\n    override var value by Config::suAuth\n\n    override fun refresh() {\n        isEnabled = Info.isDeviceSecure\n        if (!isEnabled) {\n            description = CoreR.string.settings_su_auth_insecure.asText()\n        }\n    }\n}\n\nobject Superuser : BaseSettingsItem.Section() {\n    override val title = CoreR.string.superuser.asText()\n}\n\nobject AccessMode : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.superuser_access.asText()\n    override val entryRes = CoreR.array.su_access\n    override var value by Config::rootMode\n}\n\nobject MultiuserMode : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.multiuser_mode.asText()\n    override val entryRes = CoreR.array.multiuser_mode\n    override val descriptionRes = CoreR.array.multiuser_summary\n    override var value by Config::suMultiuserMode\n\n    override fun refresh() {\n        isEnabled = Const.USER_ID == 0\n    }\n}\n\nobject MountNamespaceMode : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.mount_namespace_mode.asText()\n    override val entryRes = CoreR.array.namespace\n    override val descriptionRes = CoreR.array.namespace_summary\n    override var value by Config::suMntNamespaceMode\n}\n\nobject AutomaticResponse : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.auto_response.asText()\n    override val entryRes = CoreR.array.auto_response\n    override var value by Config::suAutoResponse\n}\n\nobject RequestTimeout : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.request_timeout.asText()\n    override val entryRes = CoreR.array.request_timeout\n\n    private val entryValues = listOf(10, 15, 20, 30, 45, 60)\n    override var value = entryValues.indexOfFirst { it == Config.suDefaultTimeout }\n        set(value) {\n            field = value\n            Config.suDefaultTimeout = entryValues[value]\n        }\n}\n\nobject SUNotification : BaseSettingsItem.Selector() {\n    override val title = CoreR.string.superuser_notification.asText()\n    override val entryRes = CoreR.array.su_notification\n    override var value by Config::suNotification\n}\n\nobject Reauthenticate : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_su_reauth_title.asText()\n    override val description = CoreR.string.settings_su_reauth_summary.asText()\n    override var value by Config::suReAuth\n\n    override fun refresh() {\n        isEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.O\n    }\n}\n\nobject Restrict : BaseSettingsItem.Toggle() {\n    override val title = CoreR.string.settings_su_restrict_title.asText()\n    override val description = CoreR.string.settings_su_restrict_summary.asText()\n    override var value by Config::suRestrict\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.provider.Settings\nimport android.view.View\nimport android.widget.Toast\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.activity\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.events.AddHomeIconEvent\nimport com.topjohnwu.magisk.events.AuthEvent\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport kotlinx.coroutines.launch\n\nclass SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {\n\n    val items = createItems()\n    val extraBindings = bindExtra {\n        it.put(BR.handler, this)\n    }\n\n    private fun createItems(): List<BaseSettingsItem> {\n        val context = AppContext\n        val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME\n\n        // Customization\n        val list = mutableListOf(\n            Customization,\n            Theme, if (LocaleSetting.useLocaleManager) LanguageSystem else Language\n        )\n        if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context))\n            list.add(AddShortcut)\n\n        // Manager\n        list.addAll(listOf(\n            AppSettings,\n            UpdateChannel, UpdateChannelUrl, DoHToggle, UpdateChecker, DownloadPath, RandNameToggle\n        ))\n        if (Info.env.isActive && Const.USER_ID == 0) {\n            if (hidden) list.add(Restore) else list.add(Hide)\n        }\n\n        // Magisk\n        if (Info.env.isActive) {\n            list.addAll(listOf(\n                Magisk,\n                SystemlessHosts\n            ))\n            if (Const.Version.atLeast_24_0()) {\n                list.addAll(listOf(Zygisk, DenyList, DenyListConfig))\n            }\n        }\n\n        // Superuser\n        if (Info.showSuperUser) {\n            list.addAll(listOf(\n                Superuser,\n                Tapjack, Authentication, AccessMode, MultiuserMode, MountNamespaceMode,\n                AutomaticResponse, RequestTimeout, SUNotification\n            ))\n            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n                // Re-authenticate is not feasible on 8.0+\n                list.add(Reauthenticate)\n            }\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                // Can hide overlay windows on 12.0+\n                list.remove(Tapjack)\n            }\n            if (Const.Version.atLeast_30_1()) {\n                list.add(Restrict)\n            }\n        }\n\n        return list\n    }\n\n    override fun onItemPressed(view: View, item: BaseSettingsItem, doAction: () -> Unit) {\n        when (item) {\n            DownloadPath -> withExternalRW(doAction)\n            UpdateChecker -> withPostNotificationPermission(doAction)\n            Authentication -> AuthEvent(doAction).publish()\n            AutomaticResponse -> if (Config.suAuth) AuthEvent(doAction).publish() else doAction()\n            else -> doAction()\n        }\n    }\n\n    override fun onItemAction(view: View, item: BaseSettingsItem) {\n        when (item) {\n            Theme -> SettingsFragmentDirections.actionSettingsFragmentToThemeFragment().navigate()\n            LanguageSystem -> view.activity.startActivity(LocaleSetting.localeSettingsIntent)\n            AddShortcut -> AddHomeIconEvent().publish()\n            SystemlessHosts -> createHosts()\n            DenyListConfig -> SettingsFragmentDirections.actionSettingsFragmentToDenyFragment().navigate()\n            UpdateChannel -> openUrlIfNecessary(view)\n            is Hide -> viewModelScope.launch { AppMigration.hide(view.activity, item.value) }\n            Restore -> viewModelScope.launch { AppMigration.restore(view.activity) }\n            Zygisk -> if (Zygisk.mismatch) SnackbarEvent(R.string.reboot_apply_change).publish()\n            else -> Unit\n        }\n    }\n\n    private fun openUrlIfNecessary(view: View) {\n        UpdateChannelUrl.refresh()\n        if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) {\n            UpdateChannelUrl.onPressed(view, this)\n        }\n    }\n\n    private fun createHosts() {\n        viewModelScope.launch {\n            RootUtils.addSystemlessHosts()\n            AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/PolicyRvItem.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport android.graphics.drawable.Drawable\nimport androidx.databinding.Bindable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.ObservableRvItem\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass PolicyRvItem(\n    private val viewModel: SuperuserViewModel,\n    override val item: SuPolicy,\n    val packageName: String,\n    private val isSharedUid: Boolean,\n    val icon: Drawable,\n    val appName: String\n) : ObservableRvItem(), DiffItem<PolicyRvItem>, ItemWrapper<SuPolicy> {\n\n    override val layoutRes = R.layout.item_policy_md2\n\n    val title get() = if (isSharedUid) \"[SharedUID] $appName\" else appName\n\n    private inline fun <reified T> setImpl(new: T, old: T, setter: (T) -> Unit) {\n        if (old != new) {\n            setter(new)\n        }\n    }\n\n    @get:Bindable\n    var isExpanded = false\n        set(value) = set(value, field, { field = it }, BR.expanded)\n\n    val showSlider = Config.suRestrict || item.policy == SuPolicy.RESTRICT\n\n    @get:Bindable\n    var isEnabled\n        get() = item.policy >= SuPolicy.ALLOW\n        set(value) = setImpl(value, isEnabled) {\n            notifyPropertyChanged(BR.enabled)\n            viewModel.updatePolicy(this, if (it) SuPolicy.ALLOW else SuPolicy.DENY)\n        }\n\n    @get:Bindable\n    var sliderValue\n        get() = item.policy\n        set(value) = setImpl(value, sliderValue) {\n            notifyPropertyChanged(BR.sliderValue)\n            notifyPropertyChanged(BR.enabled)\n            viewModel.updatePolicy(this, it)\n        }\n\n    val sliderValueToPolicyString: (Float) -> Int = { value ->\n        when (value.toInt()) {\n            1 -> CoreR.string.deny\n            2 -> CoreR.string.restrict\n            3 -> CoreR.string.grant\n            else -> CoreR.string.deny\n        }\n    }\n\n    @get:Bindable\n    var shouldNotify\n        get() = item.notification\n        private set(value) = setImpl(value, shouldNotify) {\n            item.notification = it\n            viewModel.updateNotify(this)\n        }\n\n    @get:Bindable\n    var shouldLog\n        get() = item.logging\n        private set(value) = setImpl(value, shouldLog) {\n            item.logging = it\n            viewModel.updateLogging(this)\n        }\n\n    fun toggleExpand() {\n        isExpanded = !isExpanded\n    }\n\n    fun toggleNotify() {\n        shouldNotify = !shouldNotify\n    }\n\n    fun toggleLog() {\n        shouldLog = !shouldLog\n    }\n\n    fun revoke() {\n        viewModel.deletePressed(this)\n    }\n\n    override fun itemSameAs(other: PolicyRvItem) = packageName == other.packageName\n\n    override fun contentSameAs(other: PolicyRvItem) = item.policy == other.item.policy\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport android.os.Bundle\nimport android.view.View\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.databinding.FragmentSuperuserMd2Binding\nimport rikka.recyclerview.addEdgeSpacing\nimport rikka.recyclerview.addItemSpacing\nimport rikka.recyclerview.fixEdgeEffect\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass SuperuserFragment : BaseFragment<FragmentSuperuserMd2Binding>() {\n\n    override val layoutRes = R.layout.fragment_superuser_md2\n    override val viewModel by viewModel<SuperuserViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        activity?.title = resources.getString(CoreR.string.superuser)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        binding.superuserList.apply {\n            addEdgeSpacing(top = R.dimen.l_50, bottom = R.dimen.l1)\n            addItemSpacing(R.dimen.l1, R.dimen.l_50, R.dimen.l1)\n            fixEdgeEffect()\n        }\n    }\n\n    override fun onPreBind(binding: FragmentSuperuserMd2Binding) {}\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport android.os.Process\nimport androidx.databinding.Bindable\nimport androidx.databinding.ObservableArrayList\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.databinding.MergeObservableList\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.diffList\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.dialog.SuperuserRevokeDialog\nimport com.topjohnwu.magisk.events.AuthEvent\nimport com.topjohnwu.magisk.events.SnackbarEvent\nimport com.topjohnwu.magisk.utils.asText\nimport com.topjohnwu.magisk.view.TextItem\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.util.Locale\n\nclass SuperuserViewModel(\n    private val db: PolicyDao\n) : AsyncLoadViewModel() {\n\n    private val itemNoData = TextItem(R.string.superuser_policy_none)\n\n    private val itemsHelpers = ObservableArrayList<TextItem>()\n    private val itemsPolicies = diffList<PolicyRvItem>()\n\n    val items = MergeObservableList<RvItem>()\n        .insertList(itemsHelpers)\n        .insertList(itemsPolicies)\n    val extraBindings = bindExtra {\n        it.put(BR.listener, this)\n    }\n\n    @get:Bindable\n    var loading = true\n        private set(value) = set(value, field, { field = it }, BR.loading)\n\n    @SuppressLint(\"InlinedApi\")\n    override suspend fun doLoadWork() {\n        if (!Info.showSuperUser) {\n            loading = false\n            return\n        }\n        loading = true\n        withContext(Dispatchers.IO) {\n            db.deleteOutdated()\n            db.delete(AppContext.applicationInfo.uid)\n            val policies = ArrayList<PolicyRvItem>()\n            val pm = AppContext.packageManager\n            for (policy in db.fetchAll()) {\n                val pkgs =\n                    if (policy.uid == Process.SYSTEM_UID) arrayOf(\"android\")\n                    else pm.getPackagesForUid(policy.uid)\n                if (pkgs == null) {\n                    db.delete(policy.uid)\n                    continue\n                }\n                val map = pkgs.mapNotNull { pkg ->\n                    try {\n                        val info = pm.getPackageInfo(pkg, MATCH_UNINSTALLED_PACKAGES)\n                        PolicyRvItem(\n                            this@SuperuserViewModel, policy,\n                            info.packageName,\n                            info.sharedUserId != null,\n                            info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,\n                            info.applicationInfo?.getLabel(pm) ?: info.packageName\n                        )\n                    } catch (e: PackageManager.NameNotFoundException) {\n                        null\n                    }\n                }\n                if (map.isEmpty()) {\n                    db.delete(policy.uid)\n                    continue\n                }\n                policies.addAll(map)\n            }\n            policies.sortWith(compareBy(\n                { it.appName.lowercase(Locale.ROOT) },\n                { it.packageName }\n            ))\n            itemsPolicies.update(policies)\n        }\n        if (itemsPolicies.isNotEmpty())\n            itemsHelpers.clear()\n        else if (itemsHelpers.isEmpty())\n            itemsHelpers.add(itemNoData)\n        loading = false\n    }\n\n    // ---\n\n    fun deletePressed(item: PolicyRvItem) {\n        fun updateState() = viewModelScope.launch {\n            db.delete(item.item.uid)\n            val list = ArrayList(itemsPolicies)\n            list.removeAll { it.item.uid == item.item.uid }\n            itemsPolicies.update(list)\n            if (list.isEmpty() && itemsHelpers.isEmpty()) {\n                itemsHelpers.add(itemNoData)\n            }\n        }\n\n        if (Config.suAuth) {\n            AuthEvent { updateState() }.publish()\n        } else {\n            SuperuserRevokeDialog(item.title) { updateState() }.show()\n        }\n    }\n\n    fun updateNotify(item: PolicyRvItem) {\n        viewModelScope.launch {\n            db.update(item.item)\n            val res = when {\n                item.item.notification -> R.string.su_snack_notif_on\n                else -> R.string.su_snack_notif_off\n            }\n            itemsPolicies.forEach {\n                if (it.item.uid == item.item.uid) {\n                    it.notifyPropertyChanged(BR.shouldNotify)\n                }\n            }\n            SnackbarEvent(res.asText(item.appName)).publish()\n        }\n    }\n\n    fun updateLogging(item: PolicyRvItem) {\n        viewModelScope.launch {\n            db.update(item.item)\n            val res = when {\n                item.item.logging -> R.string.su_snack_log_on\n                else -> R.string.su_snack_log_off\n            }\n            itemsPolicies.forEach {\n                if (it.item.uid == item.item.uid) {\n                    it.notifyPropertyChanged(BR.shouldLog)\n                }\n            }\n            SnackbarEvent(res.asText(item.appName)).publish()\n        }\n    }\n\n    fun updatePolicy(item: PolicyRvItem, policy: Int) {\n        val items = itemsPolicies.filter { it.item.uid == item.item.uid }\n        fun updateState() {\n            viewModelScope.launch {\n                val res = if (policy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny\n                item.item.policy = policy\n                db.update(item.item)\n                items.forEach {\n                    it.notifyPropertyChanged(BR.enabled)\n                    it.notifyPropertyChanged(BR.sliderValue)\n                }\n                SnackbarEvent(res.asText(item.appName)).publish()\n            }\n        }\n\n        if (Config.suAuth) {\n            AuthEvent { updateState() }.publish()\n        } else {\n            updateState()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt",
    "content": "package com.topjohnwu.magisk.ui.surequest\n\nimport android.content.Intent\nimport android.content.pm.ActivityInfo\nimport android.content.res.Resources\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Window\nimport android.view.WindowManager\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.core.base.UntrackedActivity\nimport com.topjohnwu.magisk.core.su.SuCallbackHandler\nimport com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST\nimport com.topjohnwu.magisk.databinding.ActivityRequestBinding\nimport com.topjohnwu.magisk.ui.theme.Theme\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nopen class SuRequestActivity : UIActivity<ActivityRequestBinding>(), UntrackedActivity {\n\n    override val layoutRes: Int = R.layout.activity_request\n    override val viewModel: SuRequestViewModel by viewModel()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        supportRequestWindowFeature(Window.FEATURE_NO_TITLE)\n        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED\n        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        window.addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            window.setHideOverlayWindows(true)\n        }\n        setTheme(Theme.selected.themeRes)\n        super.onCreate(savedInstanceState)\n\n        if (intent.action == Intent.ACTION_VIEW) {\n            val action = intent.getStringExtra(\"action\")\n            if (action == REQUEST) {\n                viewModel.handleRequest(intent)\n            } else {\n                lifecycleScope.launch {\n                    withContext(Dispatchers.IO) {\n                        SuCallbackHandler.run(this@SuRequestActivity, action, intent.extras)\n                    }\n                    finish()\n                }\n            }\n        } else {\n            finish()\n        }\n    }\n\n    override fun getTheme(): Resources.Theme {\n        val theme = super.getTheme()\n        theme.applyStyle(R.style.Foundation_Floating, true)\n        return theme\n    }\n\n    override fun onBackPressed() {\n        viewModel.denyPressed()\n    }\n\n    override fun finish() {\n        super.finishAndRemoveTask()\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.surequest\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.content.res.Resources\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.os.CountDownTimer\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.accessibility.AccessibilityEvent\nimport android.view.accessibility.AccessibilityNodeInfo\nimport android.view.accessibility.AccessibilityNodeProvider\nimport android.widget.Toast\nimport androidx.databinding.Bindable\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW\nimport com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY\nimport com.topjohnwu.magisk.core.su.SuRequestHandler\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.events.AuthEvent\nimport com.topjohnwu.magisk.events.DieEvent\nimport com.topjohnwu.magisk.events.ShowUIEvent\nimport com.topjohnwu.magisk.utils.TextHolder\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.TimeUnit.SECONDS\n\nclass SuRequestViewModel(\n    policyDB: PolicyDao,\n    private val timeoutPrefs: SharedPreferences\n) : BaseViewModel() {\n\n    lateinit var icon: Drawable\n    lateinit var title: String\n    lateinit var packageName: String\n\n    @get:Bindable\n    val denyText = DenyText()\n\n    @get:Bindable\n    var selectedItemPosition = 0\n        set(value) = set(value, field, { field = it }, BR.selectedItemPosition)\n\n    @get:Bindable\n    var grantEnabled = false\n        set(value) = set(value, field, { field = it }, BR.grantEnabled)\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    val grantTouchListener = View.OnTouchListener { _: View, event: MotionEvent ->\n        // Filter obscured touches by consuming them.\n        if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0\n            || event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0) {\n            if (event.action == MotionEvent.ACTION_UP) {\n                AppContext.toast(R.string.touch_filtered_warning, Toast.LENGTH_SHORT)\n            }\n            return@OnTouchListener Config.suTapjack\n        }\n        false\n    }\n\n    private val handler = SuRequestHandler(AppContext.packageManager, policyDB)\n    private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())\n    private var timer = SuTimer(millis, 1000)\n    private var initialized = false\n\n    fun grantPressed() {\n        cancelTimer()\n        if (Config.suAuth) {\n            AuthEvent { respond(ALLOW) }.publish()\n        } else {\n            respond(ALLOW)\n        }\n    }\n\n    fun denyPressed() {\n        respond(DENY)\n    }\n\n    fun spinnerTouched(): Boolean {\n        cancelTimer()\n        return false\n    }\n\n    fun handleRequest(intent: Intent) {\n        viewModelScope.launch(Dispatchers.Default) {\n            if (handler.start(intent))\n                showDialog()\n            else\n                DieEvent().publish()\n        }\n    }\n\n    private fun showDialog() {\n        val pm = handler.pm\n        val info = handler.pkgInfo\n        val app = info.applicationInfo\n\n        if (app == null) {\n            // The request is not coming from an app process, and the UID is a\n            // shared UID. We have no way to know where this request comes from.\n            icon = pm.defaultActivityIcon\n            title = \"[SharedUID] ${info.sharedUserId}\"\n            packageName = info.sharedUserId.toString()\n        } else {\n            val prefix = if (info.sharedUserId == null) \"\" else \"[SharedUID] \"\n            icon = app.loadIcon(pm)\n            title = \"$prefix${app.getLabel(pm)}\"\n            packageName = info.packageName\n        }\n\n        selectedItemPosition = timeoutPrefs.getInt(packageName, 0)\n\n        // Set timer\n        timer.start()\n\n        // Actually show the UI\n        ShowUIEvent(if (Config.suTapjack) EmptyAccessibilityDelegate else null).publish()\n        initialized = true\n    }\n\n    private fun respond(action: Int) {\n        if (!initialized) {\n            // ignore the response until showDialog done\n            return\n        }\n\n        timer.cancel()\n\n        val pos = selectedItemPosition\n        timeoutPrefs.edit().putInt(packageName, pos).apply()\n\n        viewModelScope.launch {\n            handler.respond(action, Config.Value.TIMEOUT_LIST[pos])\n            // Kill activity after response\n            DieEvent().publish()\n        }\n    }\n\n    private fun cancelTimer() {\n        timer.cancel()\n        denyText.seconds = 0\n    }\n\n    private inner class SuTimer(\n        private val millis: Long,\n        interval: Long\n    ) : CountDownTimer(millis, interval) {\n\n        override fun onTick(remains: Long) {\n            if (!grantEnabled && remains <= millis - 1000) {\n                grantEnabled = true\n            }\n            denyText.seconds = (remains / 1000).toInt() + 1\n        }\n\n        override fun onFinish() {\n            denyText.seconds = 0\n            respond(DENY)\n        }\n\n    }\n\n    inner class DenyText : TextHolder() {\n        var seconds = 0\n            set(value) = set(value, field, { field = it }, BR.denyText)\n\n        override fun getText(resources: Resources): CharSequence {\n            return if (seconds != 0)\n                \"${resources.getString(R.string.deny)} ($seconds)\"\n            else\n                resources.getString(R.string.deny)\n        }\n    }\n\n    // Invisible for accessibility services\n    object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {\n        override fun sendAccessibilityEvent(host: View, eventType: Int) {}\n        override fun performAccessibilityAction(host: View, action: Int, args: Bundle?) = true\n        override fun sendAccessibilityEventUnchecked(host: View, event: AccessibilityEvent) {}\n        override fun dispatchPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) = true\n        override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) {}\n        override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) {}\n        override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}\n        override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}\n        override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean = false\n        override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider? = null\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/theme/Theme.kt",
    "content": "package com.topjohnwu.magisk.ui.theme\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Config\n\nenum class Theme(\n    val themeName: String,\n    val themeRes: Int\n) {\n\n    Piplup(\n        themeName = \"Piplup\",\n        themeRes = R.style.ThemeFoundationMD2_Piplup\n    ),\n    PiplupAmoled(\n        themeName = \"AMOLED\",\n        themeRes = R.style.ThemeFoundationMD2_Amoled\n    ),\n    Rayquaza(\n        themeName = \"Rayquaza\",\n        themeRes = R.style.ThemeFoundationMD2_Rayquaza\n    ),\n    Zapdos(\n        themeName = \"Zapdos\",\n        themeRes = R.style.ThemeFoundationMD2_Zapdos\n    ),\n    Charmeleon(\n        themeName = \"Charmeleon\",\n        themeRes = R.style.ThemeFoundationMD2_Charmeleon\n    ),\n    Mew(\n        themeName = \"Mew\",\n        themeRes = R.style.ThemeFoundationMD2_Mew\n    ),\n    Salamence(\n        themeName = \"Salamence\",\n        themeRes = R.style.ThemeFoundationMD2_Salamence\n    ),\n    Fraxure(\n        themeName = \"Fraxure (Legacy)\",\n        themeRes = R.style.ThemeFoundationMD2_Fraxure\n    );\n\n    val isSelected get() = Config.themeOrdinal == ordinal\n\n    companion object {\n        val selected get() = values().getOrNull(Config.themeOrdinal) ?: Piplup\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/theme/ThemeFragment.kt",
    "content": "package com.topjohnwu.magisk.ui.theme\n\nimport android.os.Bundle\nimport android.view.ContextThemeWrapper\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.BaseFragment\nimport com.topjohnwu.magisk.arch.viewModel\nimport com.topjohnwu.magisk.databinding.FragmentThemeMd2Binding\nimport com.topjohnwu.magisk.databinding.ItemThemeBindingImpl\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass ThemeFragment : BaseFragment<FragmentThemeMd2Binding>() {\n\n    override val layoutRes = R.layout.fragment_theme_md2\n    override val viewModel by viewModel<ThemeViewModel>()\n\n    private fun <T> Array<T>.paired(): List<Pair<T, T?>> {\n        val iterator = iterator()\n        if (!iterator.hasNext()) return emptyList()\n        val result = mutableListOf<Pair<T, T?>>()\n        while (iterator.hasNext()) {\n            val a = iterator.next()\n            val b = if (iterator.hasNext()) iterator.next() else null\n            result.add(a to b)\n        }\n        return result\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View {\n        super.onCreateView(inflater, container, savedInstanceState)\n\n        for ((a, b) in Theme.values().paired()) {\n            val c = inflater.inflate(R.layout.item_theme_container, null, false)\n            val left = c.findViewById<FrameLayout>(R.id.left)\n            val right = c.findViewById<FrameLayout>(R.id.right)\n\n            for ((theme, view) in listOf(a to left, b to right)) {\n                theme ?: continue\n                val themed = ContextThemeWrapper(activity, theme.themeRes)\n                ItemThemeBindingImpl.inflate(LayoutInflater.from(themed), view, true).also {\n                    it.setVariable(BR.viewModel, viewModel)\n                    it.setVariable(BR.theme, theme)\n                    it.lifecycleOwner = viewLifecycleOwner\n                }\n            }\n\n            binding.themeContainer.addView(c)\n        }\n\n        return binding.root\n    }\n\n    override fun onStart() {\n        super.onStart()\n\n        activity?.title = getString(CoreR.string.section_theme)\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/ui/theme/ThemeViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.theme\n\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.dialog.DarkThemeDialog\nimport com.topjohnwu.magisk.events.RecreateEvent\nimport com.topjohnwu.magisk.view.TappableHeadlineItem\n\nclass ThemeViewModel : BaseViewModel(), TappableHeadlineItem.Listener {\n\n    val themeHeadline = TappableHeadlineItem.ThemeMode\n\n    override fun onItemPressed(item: TappableHeadlineItem) = when (item) {\n        is TappableHeadlineItem.ThemeMode -> DarkThemeDialog().show()\n    }\n\n    fun saveTheme(theme: Theme) {\n        if (!theme.isSelected) {\n            Config.themeOrdinal = theme.ordinal\n            RecreateEvent().publish()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/utils/AccessibilityUtils.kt",
    "content": "package com.topjohnwu.magisk.utils\n\nimport android.content.ContentResolver\nimport android.provider.Settings\n\nclass AccessibilityUtils {\n    companion object {\n        fun isAnimationEnabled(cr: ContentResolver): Boolean {\n            return !(Settings.Global.getFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f\n                && Settings.Global.getFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) == 0.0f\n                && Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) == 0.0f)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/utils/MotionRevealHelper.kt",
    "content": "package com.topjohnwu.magisk.utils\n\nimport android.animation.Animator\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnimator\nimport android.view.View\nimport androidx.core.animation.addListener\nimport androidx.core.text.layoutDirection\nimport androidx.core.view.isInvisible\nimport androidx.core.view.isVisible\nimport androidx.core.view.marginBottom\nimport androidx.core.view.marginEnd\nimport androidx.interpolator.view.animation.FastOutSlowInInterpolator\nimport com.google.android.material.circularreveal.CircularRevealCompat\nimport com.google.android.material.circularreveal.CircularRevealWidget\nimport com.google.android.material.floatingactionbutton.FloatingActionButton\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport kotlin.math.hypot\n\nobject MotionRevealHelper {\n\n    fun <CV> withViews(\n        revealable: CV,\n        fab: FloatingActionButton,\n        expanded: Boolean\n    ) where CV : CircularRevealWidget, CV : View {\n        revealable.revealInfo = revealable.createRevealInfo(!expanded)\n\n        val revealInfo = revealable.createRevealInfo(expanded)\n        val revealAnim = revealable.createRevealAnim(revealInfo)\n        val moveAnim = fab.createMoveAnim(revealInfo)\n\n        AnimatorSet().also {\n            if (expanded) {\n                it.play(revealAnim).after(moveAnim)\n            } else {\n                it.play(moveAnim).after(revealAnim)\n            }\n        }.start()\n    }\n\n    private fun <CV> CV.createRevealAnim(\n        revealInfo: CircularRevealWidget.RevealInfo\n    ): Animator where CV : CircularRevealWidget, CV : View =\n        CircularRevealCompat.createCircularReveal(\n            this,\n            revealInfo.centerX,\n            revealInfo.centerY,\n            revealInfo.radius\n        ).apply {\n            addListener(onStart = {\n                isVisible = true\n            }, onEnd = {\n                if (revealInfo.radius == 0f) {\n                    isInvisible = true\n                }\n            })\n        }\n\n    private fun FloatingActionButton.createMoveAnim(\n        revealInfo: CircularRevealWidget.RevealInfo\n    ): Animator = AnimatorSet().also {\n        it.interpolator = FastOutSlowInInterpolator()\n        it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() })\n\n        val rtlMod =\n            if (LocaleSetting.instance.currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL)\n                1f else -1f\n        val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f\n        val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod\n        val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX)\n\n        val maxY = revealInfo.centerY - marginBottom - measuredHeight / 2f\n        val targetY = if (revealInfo.radius == 0f) 0f else -maxY\n        val moveY = ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, targetY)\n\n        it.playTogether(moveX, moveY)\n    }\n\n    private fun View.createRevealInfo(expanded: Boolean): CircularRevealWidget.RevealInfo {\n        val cX = measuredWidth / 2f\n        val cY = measuredHeight / 2f - paddingBottom\n        return CircularRevealWidget.RevealInfo(cX, cY, if (expanded) hypot(cX, cY) else 0f)\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/utils/TextHolder.kt",
    "content": "package com.topjohnwu.magisk.utils\n\nimport android.content.res.Resources\n\nabstract class TextHolder {\n\n    open val isEmpty: Boolean get() = false\n    abstract fun getText(resources: Resources): CharSequence\n\n    // ---\n\n    class String(\n        private val value: CharSequence\n    ) : TextHolder() {\n        override val isEmpty get() = value.isEmpty()\n        override fun getText(resources: Resources) = value\n    }\n\n    open class Resource(\n        protected val value: Int\n    ) : TextHolder() {\n        override val isEmpty get() = value == 0\n        override fun getText(resources: Resources) = resources.getString(value)\n    }\n\n    class ResourceArgs(\n        value: Int,\n        private vararg val params: Any\n    ) : Resource(value) {\n        override fun getText(resources: Resources): kotlin.String {\n            // Replace TextHolder with strings\n            val args = params.map { if (it is TextHolder) it.getText(resources) else it }\n            return resources.getString(value, *args.toTypedArray())\n        }\n    }\n\n    // ---\n\n    companion object {\n        val EMPTY = String(\"\")\n    }\n}\n\nfun Int.asText(): TextHolder = TextHolder.Resource(this)\nfun Int.asText(vararg params: Any): TextHolder = TextHolder.ResourceArgs(this, *params)\nfun CharSequence.asText(): TextHolder = TextHolder.String(this)\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/view/MagiskDialog.kt",
    "content": "package com.topjohnwu.magisk.view\n\nimport android.app.Activity\nimport android.content.DialogInterface\nimport android.content.res.ColorStateList\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.InsetDrawable\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AppCompatDialog\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.databinding.Bindable\nimport androidx.databinding.PropertyChangeRegistry\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.color.MaterialColors\nimport com.google.android.material.shape.MaterialShapeDrawable\nimport com.topjohnwu.magisk.BR\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.UIActivity\nimport com.topjohnwu.magisk.databinding.DialogMagiskBaseBinding\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.ObservableHost\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.databinding.bindExtra\nimport com.topjohnwu.magisk.databinding.set\nimport com.topjohnwu.magisk.databinding.setAdapter\nimport com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener\n\ntypealias DialogButtonClickListener = (DialogInterface) -> Unit\n\nclass MagiskDialog(\n    context: Activity, theme: Int = 0\n) : AppCompatDialog(context, theme) {\n\n    private val binding: DialogMagiskBaseBinding =\n        DialogMagiskBaseBinding.inflate(LayoutInflater.from(context))\n    private val data = Data()\n\n    val activity: UIActivity<*> get() = ownerActivity as UIActivity<*>\n\n    init {\n        binding.setVariable(BR.data, data)\n        setCancelable(true)\n        setOwnerActivity(context)\n    }\n\n    inner class Data : ObservableHost {\n        override var callbacks: PropertyChangeRegistry? = null\n\n        @get:Bindable\n        var icon: Drawable? = null\n            set(value) = set(value, field, { field = it }, BR.icon)\n\n        @get:Bindable\n        var title: CharSequence = \"\"\n            set(value) = set(value, field, { field = it }, BR.title)\n\n        @get:Bindable\n        var message: CharSequence = \"\"\n            set(value) = set(value, field, { field = it }, BR.message)\n\n        val buttonPositive = ButtonViewModel()\n        val buttonNeutral = ButtonViewModel()\n        val buttonNegative = ButtonViewModel()\n    }\n\n    enum class ButtonType {\n        POSITIVE, NEUTRAL, NEGATIVE\n    }\n\n    interface Button {\n        var icon: Int\n        var text: Any\n        var isEnabled: Boolean\n        var doNotDismiss: Boolean\n\n        fun onClick(listener: DialogButtonClickListener)\n    }\n\n    inner class ButtonViewModel : Button, ObservableHost {\n        override var callbacks: PropertyChangeRegistry? = null\n\n        @get:Bindable\n        override var icon = 0\n            set(value) = set(value, field, { field = it }, BR.icon, BR.gone)\n\n        @get:Bindable\n        var message: String = \"\"\n            set(value) = set(value, field, { field = it }, BR.message, BR.gone)\n\n        override var text: Any\n            get() = message\n            set(value) {\n                message = when (value) {\n                    is Int -> context.getText(value)\n                    else -> value\n                }.toString()\n            }\n\n        @get:Bindable\n        val gone get() = icon == 0 && message.isEmpty()\n\n        @get:Bindable\n        override var isEnabled = true\n            set(value) = set(value, field, { field = it }, BR.enabled)\n\n        override var doNotDismiss = false\n\n        private var onClickAction: DialogButtonClickListener = {}\n\n        override fun onClick(listener: DialogButtonClickListener) {\n            onClickAction = listener\n        }\n\n        fun clicked() {\n            onClickAction(this@MagiskDialog)\n            if (!doNotDismiss) {\n                dismiss()\n            }\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        super.setContentView(binding.root)\n\n        val default = MaterialColors.getColor(context, com.google.android.material.R.attr.colorSurface, javaClass.canonicalName)\n        val surfaceColor = MaterialColors.getColor(context, R.attr.colorSurfaceSurfaceVariant, default)\n        val materialShapeDrawable = MaterialShapeDrawable(context, null, androidx.appcompat.R.attr.alertDialogStyle, com.google.android.material.R.style.MaterialAlertDialog_MaterialComponents)\n        materialShapeDrawable.initializeElevationOverlay(context)\n        materialShapeDrawable.fillColor = ColorStateList.valueOf(surfaceColor)\n        materialShapeDrawable.elevation = context.resources.getDimension(R.dimen.margin_generic)\n        materialShapeDrawable.setCornerSize(context.resources.getDimension(R.dimen.l_50))\n\n        val inset = context.resources.getDimensionPixelSize(com.google.android.material.R.dimen.appcompat_dialog_background_inset)\n        window?.apply {\n            setBackgroundDrawable(InsetDrawable(materialShapeDrawable, inset, inset, inset, inset))\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun setTitle(@StringRes titleId: Int) { data.title = context.getString(titleId) }\n\n    override fun setTitle(title: CharSequence?) { data.title = title ?: \"\" }\n\n    fun setMessage(@StringRes msgId: Int, vararg args: Any) {\n        data.message = context.getString(msgId, *args)\n    }\n\n    fun setMessage(message: CharSequence) { data.message = message }\n\n    fun setIcon(@DrawableRes drawableRes: Int) {\n        data.icon = AppCompatResources.getDrawable(context, drawableRes)\n    }\n\n    fun setIcon(drawable: Drawable) { data.icon = drawable }\n\n    fun setButton(buttonType: ButtonType, builder: Button.() -> Unit) {\n        val button = when (buttonType) {\n            ButtonType.POSITIVE -> data.buttonPositive\n            ButtonType.NEUTRAL -> data.buttonNeutral\n            ButtonType.NEGATIVE -> data.buttonNegative\n        }\n        button.apply(builder)\n    }\n\n    class DialogItem(\n        override val item: CharSequence,\n        val position: Int\n    ) : RvItem(), DiffItem<DialogItem>, ItemWrapper<CharSequence> {\n        override val layoutRes = R.layout.item_list_single_line\n    }\n\n    fun interface DialogClickListener {\n        fun onClick(position: Int)\n    }\n\n    fun setListItems(\n        list: Array<out CharSequence>,\n        listener: DialogClickListener\n    ) = setView(\n        RecyclerView(context).also {\n            it.isNestedScrollingEnabled = false\n            it.layoutManager = LinearLayoutManager(context)\n\n            val items = list.mapIndexed { i, cs -> DialogItem(cs, i) }\n            val extraBindings = bindExtra { sa ->\n                sa.put(BR.listener, DialogClickListener { pos ->\n                    listener.onClick(pos)\n                    dismiss()\n                })\n            }\n            it.setAdapter(items, extraBindings)\n        }\n    )\n\n    fun setView(view: View) {\n        binding.dialogBaseContainer.removeAllViews()\n        binding.dialogBaseContainer.addView(\n            view,\n            ViewGroup.LayoutParams.MATCH_PARENT,\n            ViewGroup.LayoutParams.WRAP_CONTENT\n        )\n    }\n\n    fun resetButtons() {\n        ButtonType.values().forEach {\n            setButton(it) {\n                text = \"\"\n                icon = 0\n                isEnabled = true\n                doNotDismiss = false\n                onClick {}\n            }\n        }\n    }\n\n    // Prevent calling setContentView\n\n    @Deprecated(\"Please use setView(view)\", level = DeprecationLevel.ERROR)\n    override fun setContentView(layoutResID: Int) {}\n    @Deprecated(\"Please use setView(view)\", level = DeprecationLevel.ERROR)\n    override fun setContentView(view: View) {}\n    @Deprecated(\"Please use setView(view)\", level = DeprecationLevel.ERROR)\n    override fun setContentView(view: View, params: ViewGroup.LayoutParams?) {}\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/view/TappableHeadlineItem.kt",
    "content": "package com.topjohnwu.magisk.view\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.RvItem\nimport com.topjohnwu.magisk.core.R as CoreR\n\nsealed class TappableHeadlineItem : RvItem(), DiffItem<TappableHeadlineItem> {\n\n    abstract val title: Int\n    abstract val icon: Int\n\n    override val layoutRes = R.layout.item_tappable_headline\n\n    // --- listener\n\n    interface Listener {\n\n        fun onItemPressed(item: TappableHeadlineItem)\n\n    }\n\n    // --- objects\n\n    object ThemeMode : TappableHeadlineItem() {\n        override val title = CoreR.string.settings_dark_mode_title\n        override val icon = R.drawable.ic_day_night\n    }\n\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/view/TextItem.kt",
    "content": "package com.topjohnwu.magisk.view\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.databinding.DiffItem\nimport com.topjohnwu.magisk.databinding.ItemWrapper\nimport com.topjohnwu.magisk.databinding.RvItem\n\nclass TextItem(override val item: Int) : RvItem(), DiffItem<TextItem>, ItemWrapper<Int> {\n    override val layoutRes = R.layout.item_text\n}\n"
  },
  {
    "path": "app/apk/src/main/java/com/topjohnwu/magisk/widget/ConcealableBottomNavigationView.java",
    "content": "package com.topjohnwu.magisk.widget;\n\nimport android.animation.Animator;\nimport android.animation.ObjectAnimator;\nimport android.animation.StateListAnimator;\nimport android.content.Context;\nimport android.os.Parcel;\nimport android.os.Parcelable;\nimport android.util.AttributeSet;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport androidx.customview.view.AbsSavedState;\nimport androidx.interpolator.view.animation.FastOutLinearInInterpolator;\n\nimport com.google.android.material.bottomnavigation.BottomNavigationView;\nimport com.topjohnwu.magisk.R;\n\npublic class ConcealableBottomNavigationView extends BottomNavigationView {\n\n    private static final int[] STATE_SET = {\n            R.attr.state_hidden\n    };\n\n    private boolean isHidden;\n    public ConcealableBottomNavigationView(@NonNull Context context) {\n        this(context, null);\n    }\n\n    public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs) {\n        this(context, attrs, com.google.android.material.R.attr.bottomNavigationStyle);\n    }\n\n    public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {\n        this(context, attrs, defStyleAttr, com.google.android.material.R.style.Widget_Design_BottomNavigationView);\n    }\n\n    public ConcealableBottomNavigationView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {\n        super(context, attrs, defStyleAttr, defStyleRes);\n    }\n\n    private void recreateAnimator(int height) {\n        Animator toHidden = ObjectAnimator.ofFloat(this, \"translationY\", height);\n        toHidden.setDuration(175);\n        toHidden.setInterpolator(new FastOutLinearInInterpolator());\n        Animator toUnhidden = ObjectAnimator.ofFloat(this, \"translationY\", 0);\n        toUnhidden.setDuration(225);\n        toUnhidden.setInterpolator(new FastOutLinearInInterpolator());\n\n        StateListAnimator animator = new StateListAnimator();\n\n        animator.addState(STATE_SET, toHidden);\n        animator.addState(new int[]{}, toUnhidden);\n\n        setStateListAnimator(animator);\n    }\n\n    @Override\n    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec);\n\n        recreateAnimator(getMeasuredHeight());\n    }\n\n    @Override\n    protected int[] onCreateDrawableState(int extraSpace) {\n        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);\n        if (isHidden()) {\n            mergeDrawableStates(drawableState, STATE_SET);\n        }\n        return drawableState;\n    }\n\n    public boolean isHidden() {\n        return isHidden;\n    }\n\n    public void setHidden(boolean raised) {\n        if (isHidden != raised) {\n            isHidden = raised;\n            refreshDrawableState();\n        }\n    }\n\n    @NonNull\n    @Override\n    protected Parcelable onSaveInstanceState() {\n        SavedState state = new SavedState(super.onSaveInstanceState());\n        state.isHidden = isHidden();\n        return state;\n    }\n\n    @Override\n    protected void onRestoreInstanceState(Parcelable state) {\n        final SavedState ss = (SavedState) state;\n        super.onRestoreInstanceState(ss.getSuperState());\n\n        if (ss.isHidden) {\n            setHidden(isHidden);\n        }\n    }\n\n    static class SavedState extends AbsSavedState {\n\n        public boolean isHidden;\n\n        public SavedState(Parcel source) {\n            super(source, ConcealableBottomNavigationView.class.getClassLoader());\n            isHidden = source.readByte() != 0;\n        }\n\n        public SavedState(Parcelable superState) {\n            super(superState);\n        }\n\n        @Override\n        public void writeToParcel(Parcel out, int flags) {\n            super.writeToParcel(out, flags);\n            out.writeByte(isHidden ? (byte) 1 : (byte) 0);\n        }\n\n        public static final Creator<SavedState> CREATOR = new Creator<>() {\n\n            @Override\n            public SavedState createFromParcel(Parcel source) {\n                return new SavedState(source);\n            }\n\n            @Override\n            public SavedState[] newArray(int size) {\n                return new SavedState[size];\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "app/apk/src/main/res/anim/fragment_enter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"0\"\n        android:toAlpha=\"1\" />\n    <scale\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"0.9\"\n        android:fromYScale=\"0.9\"\n        android:pivotX=\"50%p\"\n        android:pivotY=\"50%p\"\n        android:toXScale=\"1\"\n        android:toYScale=\"1\" />\n</set>"
  },
  {
    "path": "app/apk/src/main/res/anim/fragment_enter_pop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"0\"\n        android:toAlpha=\"1\" />\n    <scale\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"1.1\"\n        android:fromYScale=\"1.1\"\n        android:pivotX=\"50%p\"\n        android:pivotY=\"50%p\"\n        android:toXScale=\"1\"\n        android:toYScale=\"1\" />\n</set>"
  },
  {
    "path": "app/apk/src/main/res/anim/fragment_exit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"1\"\n        android:toAlpha=\"0\" />\n    <scale\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"1\"\n        android:fromYScale=\"1\"\n        android:pivotX=\"50%p\"\n        android:pivotY=\"50%p\"\n        android:toXScale=\"1.1\"\n        android:toYScale=\"1.1\" />\n</set>"
  },
  {
    "path": "app/apk/src/main/res/anim/fragment_exit_pop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <alpha\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromAlpha=\"1\"\n        android:toAlpha=\"0\" />\n    <scale\n        android:duration=\"@android:integer/config_shortAnimTime\"\n        android:fromXScale=\"1\"\n        android:fromYScale=\"1\"\n        android:pivotX=\"50%p\"\n        android:pivotY=\"50%p\"\n        android:toXScale=\"0.9\"\n        android:toYScale=\"0.9\" />\n</set>"
  },
  {
    "path": "app/apk/src/main/res/color/color_card_background_color_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorSurfaceVariant\" android:state_enabled=\"true\" />\n    <item android:alpha=\"0.68\" android:color=\"?colorSurfaceVariant\" />\n</selector>\n"
  },
  {
    "path": "app/apk/src/main/res/color/color_error_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorError\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_menu_tint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabledVariant\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorSecondary\" android:state_checked=\"true\" />\n    <item android:color=\"?colorOnSurfaceVariant\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_on_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_primary_error_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorError\" android:state_selected=\"true\" />\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_state_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorPrimary\" android:state_selected=\"true\" />\n    <item android:color=\"?colorPrimary\" android:state_checked=\"true\" />\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnSurfaceVariant\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/color/color_text_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnSurface\" />\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_bug_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 Z M 14 16 L 10 16 L 10 14 L 14 14 L 14 16 Z M 14 12 L 10 12 L 10 10 L 14 10 L 14 12 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15\"\n                android:valueTo=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_bug_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\"\n                android:valueTo=\"M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_circle_check_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:pathData=\"M 12 2 C 6.5 2 2 6.5 2 12 C 2 17.5 6.5 22 12 22 C 17.5 22 22 17.5 22 12 C 22 6.5 17.5 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13 L 10 17 L 18 9 L 16.59 7.58 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"500\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.744 3.12 17.24 4.927 19.052 C 6.74 20.87 9.244 22 12 22 C 13.911 22 15.701 21.457 17.224 20.517 C 18.628 19.651 19.804 18.448 20.638 17.024 C 21.503 15.545 22 13.828 22 12 C 22 10.2 21.518 8.507 20.677 7.044 C 19.755 5.441 18.402 4.114 16.779 3.224 C 15.357 2.444 13.728 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13\"\n                android:valueTo=\"M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_circle_check_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:pathData=\"M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 7 13 L 17 13 L 17 11 L 7 11\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"500\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11\"\n                android:valueTo=\"M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.856 3.213 17.442 5.149 19.268 C 6.942 20.96 9.356 22 12 22 C 14.061 22 15.982 21.368 17.578 20.288 C 19.114 19.249 20.349 17.796 21.119 16.092 C 21.685 14.841 22 13.456 22 12 C 22 10.122 21.475 8.361 20.566 6.856 C 19.691 5.408 18.46 4.197 16.997 3.347 C 15.524 2.491 13.817 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_home_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_3\"\n                android:fillColor=\"#000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 L 9 21 L 4 21 L 4 9 L 12 3 Z\"\n                android:strokeWidth=\"1\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_3\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\"\n                android:valueTo=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_home_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_3\"\n                android:fillColor=\"#000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:strokeWidth=\"1\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_3\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:valueTo=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_module_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"outlined\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"outlined\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\"\n                android:valueTo=\"M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_module_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"outlined\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 22 13.5 C 22 15.26 20.7 16.72 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.28 3.3 8.74 2 10.5 2 C 12.26 2 13.72 3.3 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 20.7 10.28 22 11.74 22 13.5 M 17 15 L 18.5 15 C 18.898 15 19.279 14.842 19.561 14.561 C 19.842 14.279 20 13.898 20 13.5 C 20 13.102 19.842 12.721 19.561 12.439 C 19.279 12.158 18.898 12 18.5 12 L 17 12 L 17 7 L 12 7 L 12 5.5 C 12 5.102 11.842 4.721 11.561 4.439 C 11.279 4.158 10.898 4 10.5 4 C 10.102 4 9.721 4.158 9.439 4.439 C 9.158 4.721 9 5.102 9 5.5 L 9 7 L 4 7 L 4 9.12 C 5.76 9.8 7 11.5 7 13.5 C 7 15.5 5.75 17.2 4 17.88 L 4 20 L 6.12 20 C 6.8 18.25 8.5 17 10.5 17 C 12.5 17 14.2 18.25 14.88 20 L 17 20 L 17 15 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"outlined\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12\"\n                android:valueTo=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_settings_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 M 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12\"\n                android:valueTo=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_settings_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\"\n                android:valueTo=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_superuser_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 1 L 3 5 L 3 11 C 3 16.55 6.84 21.74 12 23 C 17.16 21.74 21 16.55 21 11 L 21 5 L 12 1 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18\"\n                android:valueTo=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/avd_superuser_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\"\n                android:valueTo=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/bg_line_bottom_rounded.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@android:color/black\" />\n            <corners android:bottomLeftRadius=\"2dp\" android:bottomRightRadius=\"2dp\" />\n        </shape>\n    </item>\n\n</selector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/bg_line_top_rounded.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@android:color/black\" />\n            <corners android:topLeftRadius=\"2dp\" android:topRightRadius=\"2dp\" />\n        </shape>\n    </item>\n\n</selector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/bg_selection_circle_green.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#43A047\" />\n        </shape>\n    </item>\n</selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_action_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:tint=\"#000000\" android:viewportHeight=\"24\" android:viewportWidth=\"24\" android:width=\"24dp\">\n\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M8,5v14l11,-7z\"/>\n\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_back_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,11H6.83l2.88,-2.88c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L3.71,11.3c-0.39,0.39 -0.39,1.02 0,1.41L8.3,17.3c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L6.83,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_bug_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_bug_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_bug_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_bug_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_bug_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_bug_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_bug_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_check_circle_checked_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_check_circle_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"NewApi\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_check_circle_checked_md2\"\n        android:state_selected=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_check_circle_unchecked_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_circle_check_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_circle_check_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_check_circle_unchecked_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_check_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"\n        tools:fillColor=\"#000\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_close_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\"\n        tools:fillColor=\"#000\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_day.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M3.55,18.54L4.96,19.95L6.76,18.16L5.34,16.74M11,22.45C11.32,22.45 13,22.45 13,22.45V19.5H11M12,5.5A6,6 0 0,0 6,11.5A6,6 0 0,0 12,17.5A6,6 0 0,0 18,11.5C18,8.18 15.31,5.5 12,5.5M20,12.5H23V10.5H20M17.24,18.16L19.04,19.95L20.45,18.54L18.66,16.74M20.45,4.46L19.04,3.05L17.24,4.84L18.66,6.26M13,0.55H11V3.5H13M4,10.5H1V12.5H4M6.76,4.84L4.96,3.05L3.55,4.46L5.34,6.26L6.76,4.84Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_day_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M7.5,2C5.71,3.15 4.5,5.18 4.5,7.5C4.5,9.82 5.71,11.85 7.53,13C4.46,13 2,10.54 2,7.5A5.5,5.5 0 0,1 7.5,2M19.07,3.5L20.5,4.93L4.93,20.5L3.5,19.07L19.07,3.5M12.89,5.93L11.41,5L9.97,6L10.39,4.3L9,3.24L10.75,3.12L11.33,1.47L12,3.1L13.73,3.13L12.38,4.26L12.89,5.93M9.59,9.54L8.43,8.81L7.31,9.59L7.65,8.27L6.56,7.44L7.92,7.35L8.37,6.06L8.88,7.33L10.24,7.36L9.19,8.23L9.59,9.54M19,13.5A5.5,5.5 0 0,1 13.5,19C12.28,19 11.15,18.6 10.24,17.93L17.93,10.24C18.6,11.15 19,12.28 19,13.5M14.6,20.08L17.37,18.93L17.13,22.28L14.6,20.08M18.93,17.38L20.08,14.61L22.28,17.15L18.93,17.38M20.08,12.42L18.94,9.64L22.28,9.88L20.08,12.42M9.63,18.93L12.4,20.08L9.87,22.27L9.63,18.93Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_delete_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"?colorOnSurface\"\n      android:pathData=\"M18,4h-2.5l-0.7,-0.7C14.6,3.2 14.4,3 14.1,3H9.9C9.6,3 9.4,3.2 9.2,3.3L8.5,4H6C5.4,4 5,4.5 5,5s0.4,1 1,1h12c0.5,0 1,-0.4 1,-1S18.5,4 18,4z\" />\n  <path\n      android:fillColor=\"?colorOnSurface\"\n      android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V9c0,-1.1 -0.9,-2 -2,-2H8C6.9,7 6,7.9 6,9V19z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_download_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z\"\n        tools:fillColor=\"#000\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_folder_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,18H4V8H20M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M15,16H6V14H15V16M18,12H6V10H18V12Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_forth_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <group\n        android:name=\"forth\"\n        android:pivotX=\"12\"\n        android:pivotY=\"12\"\n        android:rotation=\"180\">\n        <path\n            android:fillColor=\"#000\"\n            android:pathData=\"M20,11H6.83l2.88,-2.88c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.39 -1.02,-0.39 -1.41,0L3.71,11.3c-0.39,0.39 -0.39,1.02 0,1.41L8.3,17.3c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L6.83,13H20c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1z\" />\n    </group>\n\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_home_filled_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_home_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_home_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_home_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_home_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_home_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_home_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_install.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,18L7,13H10V9H14V13H17L12,18M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2M14,6V4H10V6H14M4,8V19H20V8H4Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_manager.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M7,3v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1L9,4h10v16L9,20v-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L21,3c0,-1.1 -0.9,-2 -2,-2L9,1c-1.1,0 -2,0.9 -2,2zM9.5,15.5c0.29,-0.12 0.55,-0.29 0.8,-0.48l-0.02,0.03 1.01,0.39c0.23,0.09 0.49,0 0.61,-0.22l0.84,-1.46c0.12,-0.21 0.07,-0.49 -0.12,-0.64l-0.85,-0.68 -0.02,0.03c0.02,-0.16 0.05,-0.32 0.05,-0.48s-0.03,-0.32 -0.05,-0.48l0.02,0.03 0.85,-0.68c0.19,-0.15 0.24,-0.43 0.12,-0.64l-0.84,-1.46c-0.12,-0.21 -0.38,-0.31 -0.61,-0.22l-1.01,0.39 0.02,0.03c-0.25,-0.17 -0.51,-0.34 -0.8,-0.46l-0.17,-1.08C9.3,7.18 9.09,7 8.84,7L7.16,7c-0.25,0 -0.46,0.18 -0.49,0.42L6.5,8.5c-0.29,0.12 -0.55,0.29 -0.8,0.48l0.02,-0.03 -1.02,-0.39c-0.23,-0.09 -0.49,0 -0.61,0.22l-0.84,1.46c-0.12,0.21 -0.07,0.49 0.12,0.64l0.85,0.68 0.02,-0.03c-0.02,0.15 -0.05,0.31 -0.05,0.47s0.03,0.32 0.05,0.48l-0.02,-0.03 -0.85,0.68c-0.19,0.15 -0.24,0.43 -0.12,0.64l0.84,1.46c0.12,0.21 0.38,0.31 0.61,0.22l1.01,-0.39 -0.01,-0.04c0.25,0.19 0.51,0.36 0.8,0.48l0.17,1.07c0.03,0.25 0.24,0.43 0.49,0.43h1.68c0.25,0 0.46,-0.18 0.49,-0.42l0.17,-1.08zM6,12c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.9,2 -2,2 -2,-0.9 -2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_module_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20.5,11H19V7C19,5.89 18.1,5 17,5H13V3.5A2.5,2.5 0 0,0 10.5,1A2.5,2.5 0 0,0 8,3.5V5H4A2,2 0 0,0 2,7V10.8H3.5C5,10.8 6.2,12 6.2,13.5C6.2,15 5,16.2 3.5,16.2H2V20A2,2 0 0,0 4,22H7.8V20.5C7.8,19 9,17.8 10.5,17.8C12,17.8 13.2,19 13.2,20.5V22H17A2,2 0 0,0 19,20V16H20.5A2.5,2.5 0 0,0 23,13.5A2.5,2.5 0 0,0 20.5,11Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_module_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_module_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_module_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_module_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_module_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_module_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_module_storage_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M17.75,4.09L15.22,6.03L16.13,9.09L13.5,7.28L10.87,9.09L11.78,6.03L9.25,4.09L12.44,4L13.5,1L14.56,4L17.75,4.09M21.25,11L19.61,12.25L20.2,14.23L18.5,13.06L16.8,14.23L17.39,12.25L15.75,11L17.81,10.95L18.5,9L19.19,10.95L21.25,11M18.97,15.95C19.8,15.87 20.69,17.05 20.16,17.8C19.84,18.25 19.5,18.67 19.08,19.07C15.17,23 8.84,23 4.94,19.07C1.03,15.17 1.03,8.83 4.94,4.93C5.34,4.53 5.76,4.17 6.21,3.85C6.96,3.32 8.14,4.21 8.06,5.04C7.79,7.9 8.75,10.87 10.95,13.06C13.14,15.26 16.1,16.22 18.97,15.95M17.33,17.97C14.5,17.81 11.7,16.64 9.53,14.5C7.36,12.31 6.2,9.5 6.04,6.68C3.23,9.82 3.34,14.64 6.35,17.66C9.37,20.67 14.19,20.78 17.33,17.97Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_notifications_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M16,17H7V10.5C7,8 9,6 11.5,6C14,6 16,8 16,10.5M18,16V10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18M11.5,22A2,2 0 0,0 13.5,20H9.5A2,2 0 0,0 11.5,22Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_paint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20.71,4.63L19.37,3.29C19,2.9 18.35,2.9 17.96,3.29L9,12.25L11.75,15L20.71,6.04C21.1,5.65 21.1,5 20.71,4.63M7,14A3,3 0 0,0 4,17C4,18.31 2.84,19 2,19C2.92,20.22 4.5,21 6,21A4,4 0 0,0 10,17A3,3 0 0,0 7,14Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_restart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,4C14.1,4 16.1,4.8 17.6,6.3C20.7,9.4 20.7,14.5 17.6,17.6C15.8,19.5 13.3,20.2 10.9,19.9L11.4,17.9C13.1,18.1 14.9,17.5 16.2,16.2C18.5,13.9 18.5,10.1 16.2,7.7C15.1,6.6 13.5,6 12,6V10.6L7,5.6L12,0.6V4M6.3,17.6C3.7,15 3.3,11 5.1,7.9L6.6,9.4C5.5,11.6 5.9,14.4 7.8,16.2C8.3,16.7 8.9,17.1 9.6,17.4L9,19.4C8,19 7.1,18.4 6.3,17.6Z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_save_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_search_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_settings_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_settings_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_settings_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_settings_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_settings_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_settings_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_settings_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_superuser_filled_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:width=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z\" />\n</vector>\n"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_superuser_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_superuser_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_superuser_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_superuser_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_superuser_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_superuser_outlined_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:width=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/drawable/ic_update_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M17,1H7A2,2 0 0,0 5,3V21A2,2 0 0,0 7,23H17A2,2 0 0,0 19,21V3A2,2 0 0,0 17,1M17,19H7V5H17V19M16,13H13V8H11V13H8L12,17L16,13Z\" />\n</vector>"
  },
  {
    "path": "app/apk/src/main/res/layout/activity_main_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        app:consumeSystemWindowsInsets=\"start|end\"\n        app:edgeToEdge=\"true\"\n        app:fitsSystemWindowsInsets=\"start|end\"\n        tools:ignore=\"RtlHardcoded\">\n\n        <androidx.fragment.app.FragmentContainerView\n            android:id=\"@+id/main_nav_host\"\n            android:name=\"androidx.navigation.fragment.NavHostFragment\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:defaultNavHost=\"true\"\n            app:navGraph=\"@navigation/main\" />\n\n        <com.google.android.material.appbar.AppBarLayout\n            android:id=\"@+id/main_toolbar_wrapper\"\n            style=\"@style/WidgetFoundation.Appbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:fitsSystemWindowsInsets=\"top\">\n\n            <com.google.android.material.appbar.MaterialToolbar\n                android:id=\"@+id/main_toolbar\"\n                style=\"@style/WidgetFoundation.Toolbar\"\n                android:layout_width=\"match_parent\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_scrollFlags=\"noScroll\"\n                tools:layout_marginTop=\"24dp\"\n                tools:title=\"Home\" />\n\n        </com.google.android.material.appbar.AppBarLayout>\n\n        <com.topjohnwu.magisk.widget.ConcealableBottomNavigationView\n            android:id=\"@+id/main_navigation\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|center_horizontal\"\n            android:fitsSystemWindows=\"false\"\n            android:paddingBottom=\"0dp\"\n            app:fitsSystemWindowsInsets=\"start|end|bottom\"\n            app:itemHorizontalTranslationEnabled=\"false\"\n            app:itemIconTint=\"@color/color_menu_tint\"\n            app:itemRippleColor=\"?colorPrimary\"\n            app:itemTextAppearanceActive=\"@style/AppearanceFoundation.Tiny.Bold\"\n            app:itemTextAppearanceInactive=\"@style/AppearanceFoundation.Tiny.Bold\"\n            app:itemTextColor=\"@color/color_menu_tint\"\n            app:labelVisibilityMode=\"labeled\"\n            app:menu=\"@menu/menu_bottom_nav\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/activity_request.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.surequest.SuRequestViewModel\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card.Elevated\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\">\n\n        <LinearLayout\n            android:id=\"@+id/su_popup\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:minWidth=\"350dp\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/request_title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginBottom=\"@dimen/l_50\"\n                android:gravity=\"center_horizontal\"\n                android:text=\"@string/su_request_title\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\" />\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"@dimen/l_50\"\n                android:layout_marginBottom=\"@dimen/l_50\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"10dp\"\n                android:paddingEnd=\"10dp\">\n\n                <ImageView\n                    android:id=\"@+id/app_icon\"\n                    style=\"@style/WidgetFoundation.Icon\"\n                    android:layout_gravity=\"center_vertical\"\n                    android:layout_marginStart=\"@dimen/l_50\"\n                    android:layout_marginEnd=\"@dimen/l1\"\n                    android:layout_weight=\"0\"\n                    android:padding=\"0dp\"\n                    android:src=\"@{viewModel.icon}\"\n                    app:tint=\"@null\"\n                    tools:src=\"@drawable/ic_delete_md2\" />\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_vertical\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center_vertical\"\n                    android:orientation=\"vertical\">\n\n                    <TextView\n                        android:id=\"@+id/app_name\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:ellipsize=\"end\"\n                        android:maxWidth=\"300dp\"\n                        android:maxLines=\"1\"\n                        android:minWidth=\"200dp\"\n                        android:text=\"@{viewModel.title}\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                        android:textStyle=\"bold\"\n                        tools:text=\"Magisk\" />\n\n                    <TextView\n                        android:id=\"@+id/package_name\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:ellipsize=\"end\"\n                        android:maxWidth=\"300dp\"\n                        android:maxLines=\"1\"\n                        android:minWidth=\"200dp\"\n                        android:text=\"@{viewModel.packageName}\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                        tools:text=\"com.topjohnwu.magisk\" />\n                </LinearLayout>\n            </LinearLayout>\n\n            <Spinner\n                android:id=\"@+id/timeout\"\n                onTouch=\"@{() -> viewModel.spinnerTouched()}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:enabled=\"@{viewModel.grantEnabled}\"\n                app:items=\"@{@stringArray/allow_timeout}\"\n                app:layout=\"@{@layout/item_spinner}\"\n                android:selection=\"@={viewModel.selectedItemPosition}\" />\n\n            <TextView\n                android:id=\"@+id/warning\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_margin=\"@dimen/l1\"\n                android:gravity=\"center\"\n                android:text=\"@string/su_warning\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textColor=\"?colorError\"\n                android:textStyle=\"bold\"\n                tools:text=\"@string/su_warning\" />\n\n            <LinearLayout\n                style=\"?android:buttonBarStyle\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:gravity=\"bottom\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"@dimen/l2\"\n                android:paddingEnd=\"@dimen/l2\">\n\n                <Button\n                    android:id=\"@+id/deny_btn\"\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:onClick=\"@{() -> viewModel.denyPressed()}\"\n                    android:text=\"@{viewModel.denyText}\"\n                    android:textColor=\"?colorOnSurfaceVariant\"\n                    tools:text=\"@string/deny\" />\n\n                <Button\n                    android:id=\"@+id/grant_btn\"\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    onTouch=\"@{viewModel.grantTouchListener}\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:onClick=\"@{() -> viewModel.grantPressed()}\"\n                    android:enabled=\"@{viewModel.grantEnabled}\"\n                    android:text=\"@string/grant\" />\n\n                <requestFocus />\n            </LinearLayout>\n\n        </LinearLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/dialog_magisk_base.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"data\"\n            type=\"com.topjohnwu.magisk.view.MagiskDialog.Data\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        tools:layout_width=\"match_parent\">\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/dialog_base_start\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_begin=\"16dp\" />\n\n        <androidx.constraintlayout.widget.Guideline\n            android:id=\"@+id/dialog_base_end\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            app:layout_constraintGuide_end=\"16dp\" />\n\n        <ImageView\n            android:id=\"@+id/dialog_base_icon\"\n            style=\"@style/WidgetFoundation.Image.Big\"\n            gone=\"@{data.icon == null}\"\n            android:layout_gravity=\"center\"\n            android:layout_marginTop=\"@dimen/l1\"\n            android:src=\"@{data.icon}\"\n            app:layout_constraintBottom_toTopOf=\"@+id/dialog_base_title\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:src=\"@drawable/ic_delete_md2\" />\n\n        <TextView\n            android:id=\"@+id/dialog_base_title\"\n            gone=\"@{data.title.length == 0}\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/l1\"\n            android:gravity=\"center\"\n            android:text=\"@{data.title}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Title\"\n            app:layout_constraintEnd_toEndOf=\"@+id/dialog_base_end\"\n            app:layout_constraintStart_toStartOf=\"@+id/dialog_base_start\"\n            app:layout_constraintTop_toBottomOf=\"@+id/dialog_base_icon\"\n            tools:lines=\"1\"\n            tools:text=\"@tools:sample/lorem/random\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/l_50\"\n            android:overScrollMode=\"ifContentScrolls\"\n            app:layout_constrainedHeight=\"true\"\n            app:layout_constraintBottom_toTopOf=\"@+id/dialog_base_space\"\n            app:layout_constraintEnd_toEndOf=\"@+id/dialog_base_end\"\n            app:layout_constraintStart_toStartOf=\"@+id/dialog_base_start\"\n            app:layout_constraintTop_toBottomOf=\"@+id/dialog_base_title\">\n\n            <FrameLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <TextView\n                    android:id=\"@+id/dialog_base_message\"\n                    gone=\"@{data.message.length == 0}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:text=\"@{data.message}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                    tools:lines=\"3\"\n                    tools:text=\"@tools:sample/lorem/random\" />\n\n                <FrameLayout\n                    android:id=\"@+id/dialog_base_container\"\n                    gone=\"@{data.message.length != 0}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n\n            </FrameLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n        <Space\n            android:id=\"@+id/dialog_base_space\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"@dimen/l_50\"\n            app:layout_constraintBottom_toTopOf=\"@+id/dialog_base_buttons\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\" />\n\n        <androidx.appcompat.widget.ButtonBarLayout\n            android:id=\"@+id/dialog_base_buttons\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"bottom|center_horizontal\"\n            android:layoutDirection=\"locale\"\n            android:orientation=\"horizontal\"\n            android:paddingStart=\"12dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingEnd=\"12dp\"\n            android:paddingBottom=\"4dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\">\n\n            <Button\n                android:id=\"@+id/dialog_base_button_2\"\n                style=\"@style/WidgetFoundation.Button.Text\"\n                gone=\"@{data.buttonNeutral.gone}\"\n                isEnabled=\"@{data.buttonNeutral.isEnabled}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:clickable=\"@{data.buttonNeutral.isEnabled}\"\n                android:focusable=\"@{data.buttonNeutral.isEnabled}\"\n                android:onClick=\"@{() -> data.buttonNeutral.clicked()}\"\n                android:text=\"@{data.buttonNeutral.message}\"\n                app:icon=\"@{data.buttonNeutral.icon}\"\n                app:iconGravity=\"textStart\"\n                tools:icon=\"@drawable/ic_bug_md2\"\n                tools:text=\"Button 1\" />\n\n            <Space\n                android:id=\"@+id/spacer\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:visibility=\"invisible\" />\n\n            <Button\n                android:id=\"@+id/dialog_base_button_3\"\n                style=\"@style/WidgetFoundation.Button.Text\"\n                gone=\"@{data.buttonNegative.gone}\"\n                isEnabled=\"@{data.buttonNegative.isEnabled}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:clickable=\"@{data.buttonNegative.isEnabled}\"\n                android:focusable=\"@{data.buttonNegative.isEnabled}\"\n                android:onClick=\"@{() -> data.buttonNegative.clicked()}\"\n                android:text=\"@{data.buttonNegative.message}\"\n                app:icon=\"@{data.buttonNegative.icon}\"\n                tools:icon=\"@drawable/ic_bug_md2\"\n                tools:text=\"Button 1\" />\n\n            <Button\n                android:id=\"@+id/dialog_base_button_1\"\n                style=\"@style/WidgetFoundation.Button.Text\"\n                gone=\"@{data.buttonPositive.gone}\"\n                isEnabled=\"@{data.buttonPositive.isEnabled}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:clickable=\"@{data.buttonPositive.isEnabled}\"\n                android:focusable=\"@{data.buttonPositive.isEnabled}\"\n                android:onClick=\"@{() -> data.buttonPositive.clicked()}\"\n                android:text=\"@{data.buttonPositive.message}\"\n                app:icon=\"@{data.buttonPositive.icon}\"\n                app:iconGravity=\"textStart\"\n                tools:icon=\"@drawable/ic_bug_md2\"\n                tools:text=\"Button 1\" />\n\n        </androidx.appcompat.widget.ButtonBarLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/dialog_settings_app_name.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"data\"\n            type=\"com.topjohnwu.magisk.ui.settings.Hide\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"@dimen/margin_generic\">\n\n        <com.google.android.material.textfield.TextInputLayout\n            style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/margin_generic\"\n            android:hint=\"@string/settings_app_name_hint\"\n            app:boxStrokeColor=\"?colorOnSurfaceVariant\"\n            app:counterEnabled=\"true\"\n            app:counterMaxLength=\"@{data.maxLength}\"\n            app:counterOverflowTextColor=\"?colorError\"\n            app:error=\"@{data.error ? @string/settings_app_name_error : @string/empty}\"\n            app:errorEnabled=\"true\"\n            app:errorTextColor=\"?colorError\"\n            app:helperText=\"@string/settings_app_name_helper\"\n            app:hintEnabled=\"true\"\n            app:hintTextAppearance=\"@style/AppearanceFoundation.Tiny\"\n            app:hintTextColor=\"?colorOnSurfaceVariant\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/dialog_custom_download_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textCapWords\"\n                android:text=\"@={data.result}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textColor=\"?colorOnSurface\"\n                tools:text=\"@tools:sample/lorem\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/dialog_settings_download_path.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"data\"\n            type=\"com.topjohnwu.magisk.ui.settings.DownloadPath\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"@dimen/margin_generic\">\n\n        <TextView\n            android:id=\"@+id/dialog_custom_download_title\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{@string/settings_download_path_message(data.path)}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n            tools:text=\"@string/settings_download_path_message\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/margin_generic\"\n            android:hint=\"@string/settings_download_path_title\"\n            app:boxStrokeColor=\"?colorOnSurfaceVariant\"\n            app:errorTextColor=\"?colorError\"\n            app:hintEnabled=\"true\"\n            app:hintTextAppearance=\"@style/AppearanceFoundation.Tiny\"\n            app:hintTextColor=\"?colorOnSurfaceVariant\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/dialog_custom_download_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textUri\"\n                android:text=\"@={data.inputResult}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textColor=\"?colorOnSurface\"\n                tools:text=\"@tools:sample/lorem\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/dialog_settings_update_channel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"data\"\n            type=\"com.topjohnwu.magisk.ui.settings.UpdateChannelUrl\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"@dimen/margin_generic\">\n\n        <com.google.android.material.textfield.TextInputLayout\n            style=\"@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"@dimen/margin_generic\"\n            android:hint=\"@string/settings_update_custom_msg\"\n            app:boxStrokeColor=\"?colorOnSurfaceVariant\"\n            app:errorTextColor=\"?colorError\"\n            app:hintEnabled=\"true\"\n            app:hintTextAppearance=\"@style/AppearanceFoundation.Tiny\"\n            app:hintTextColor=\"?colorOnSurfaceVariant\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/dialog_custom_download_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textUri\"\n                android:text=\"@={data.inputResult}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textColor=\"?colorOnSurface\"\n                tools:text=\"@tools:sample/lorem\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_action_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.module.ActionViewModel\" />\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <HorizontalScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginTop=\"@dimen/internal_action_bar_size\"\n            app:layout_fitsSystemWindowsInsets=\"top\"\n            tools:layout_marginTop=\"@dimen/internal_action_bar_size\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/flash_content\"\n                scrollToLast=\"@{true}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:clipToPadding=\"false\"\n                android:orientation=\"vertical\"\n                app:fitsSystemWindowsInsets=\"start|end|bottom\"\n                app:items=\"@{viewModel.items}\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n                tools:listitem=\"@layout/item_console_md2\" />\n\n        </HorizontalScrollView>\n\n        <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n            android:id=\"@+id/close_btn\"\n            android:visibility=\"gone\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|end\"\n            android:layout_margin=\"@dimen/l1\"\n            android:layout_marginBottom=\"@dimen/l1\"\n            android:clickable=\"true\"\n            android:enabled=\"true\"\n            android:focusable=\"true\"\n            android:text=\"@string/close\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"?colorOnPrimary\"\n            android:textStyle=\"bold\"\n            app:backgroundTint=\"?colorPrimary\"\n            app:icon=\"@drawable/ic_close_md2\"\n            app:iconTint=\"?colorOnPrimary\"\n            app:layout_fitsSystemWindowsInsets=\"bottom\" />\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:id=\"@+id/snackbar_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:fitsSystemWindowsInsets=\"top|bottom\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_deny_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.deny.DenyListViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/app_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/internal_action_bar_size\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            app:invisible=\"@{viewModel.loading}\"\n            app:items=\"@{viewModel.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            tools:listitem=\"@layout/item_hide_md2\"\n            tools:paddingTop=\"40dp\" />\n\n        <LinearLayout\n            goneUnless=\"@{viewModel.loading}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            tools:visibility=\"gone\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/loading\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textStyle=\"bold\" />\n\n            <ProgressBar\n                style=\"@style/WidgetFoundation.ProgressBar.Indeterminate\"\n                android:layout_marginTop=\"@dimen/l1\" />\n\n        </LinearLayout>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_flash_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.flash.FlashViewModel\" />\n\n    </data>\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <HorizontalScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginTop=\"@dimen/internal_action_bar_size\"\n            app:layout_fitsSystemWindowsInsets=\"top\"\n            tools:layout_marginTop=\"@dimen/internal_action_bar_size\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/flash_content\"\n                scrollToLast=\"@{true}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:clipToPadding=\"false\"\n                android:orientation=\"vertical\"\n                app:fitsSystemWindowsInsets=\"start|end|bottom\"\n                app:items=\"@{viewModel.items}\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n                app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n                tools:listitem=\"@layout/item_console_md2\" />\n\n        </HorizontalScrollView>\n\n        <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton\n            android:id=\"@+id/restart_btn\"\n            android:visibility=\"gone\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|end\"\n            android:layout_margin=\"@dimen/l1\"\n            android:layout_marginBottom=\"@dimen/l1\"\n            android:clickable=\"@{!viewModel.flashing}\"\n            android:enabled=\"@{!viewModel.flashing}\"\n            android:focusable=\"true\"\n            android:onClick=\"@{() -> viewModel.restartPressed()}\"\n            android:text=\"@string/reboot\"\n            android:textAllCaps=\"false\"\n            android:textColor=\"?colorOnPrimary\"\n            android:textStyle=\"bold\"\n            app:backgroundTint=\"?colorPrimary\"\n            app:icon=\"@drawable/ic_restart\"\n            app:iconTint=\"?colorOnPrimary\"\n            app:layout_fitsSystemWindowsInsets=\"bottom\" />\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:id=\"@+id/snackbar_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:fitsSystemWindowsInsets=\"top|bottom\" />\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_home_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.core.Info\" />\n\n        <import type=\"com.topjohnwu.magisk.ui.home.DeveloperItem\" />\n\n        <import type=\"com.topjohnwu.magisk.ui.home.IconLink\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.home.HomeViewModel\" />\n\n    </data>\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:fillViewport=\"true\"\n        android:paddingTop=\"@dimen/internal_action_bar_size\"\n        android:paddingBottom=\"@dimen/l3\"\n        app:fitsSystemWindowsInsets=\"top|bottom\"\n        tools:layout_marginTop=\"24dp\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/l1\">\n\n            <Button\n                style=\"@style/WidgetFoundation.Button\"\n                gone=\"@{!viewModel.showTest}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:onClick=\"@{() -> viewModel.onTestPressed()}\"\n                android:text=\"TEST\"\n                android:textAllCaps=\"false\"\n                tools:visibility=\"gone\" />\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card.Primary\"\n                goneUnless=\"@{viewModel.noticeVisible}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:layout_marginBottom=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <androidx.constraintlayout.widget.ConstraintLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <TextView\n                        android:id=\"@+id/home_notice_content\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:padding=\"@dimen/l1\"\n                        android:text=\"@string/home_notice_content\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Caption.OnPrimary\"\n                        app:layout_constraintEnd_toStartOf=\"@+id/home_notice_hide\"\n                        app:layout_constraintStart_toStartOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\" />\n\n                    <Button\n                        android:id=\"@+id/home_notice_hide\"\n                        style=\"@style/WidgetFoundation.Button.Text.OnPrimary\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:onClick=\"@{() -> viewModel.hideNotice()}\"\n                        android:text=\"@string/hide\"\n                        android:textAllCaps=\"false\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintEnd_toEndOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\" />\n\n                </androidx.constraintlayout.widget.ConstraintLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <include\n                android:id=\"@+id/home_magisk_wrapper\"\n                layout=\"@layout/include_home_magisk\"\n                viewModel=\"@{viewModel}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                app:layout_constraintTop_toBottomOf=\"@+id/home_device_wrapper\" />\n\n            <include\n                android:id=\"@+id/home_manager_wrapper\"\n                layout=\"@layout/include_home_manager\"\n                viewModel=\"@{viewModel}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                app:layout_constraintTop_toBottomOf=\"@+id/home_magisk_wrapper\" />\n\n            <Space\n                goneUnless=\"@{Info.env.isActive}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"@dimen/l1\" />\n\n            <Button\n                style=\"@style/WidgetFoundation.Button.Outlined.Error\"\n                goneUnless=\"@{Info.env.isActive}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:onClick=\"@{() -> viewModel.onDeletePressed()}\"\n                android:text=\"@string/uninstall_magisk_title\"\n                android:textAllCaps=\"false\"\n                android:textSize=\"12sp\"\n                app:cornerRadius=\"@dimen/r1\"\n                app:icon=\"@drawable/ic_delete_md2\" />\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"bottom\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:paddingTop=\"@dimen/l1\"\n                    android:paddingBottom=\"@dimen/l1\">\n\n                    <TextView\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:text=\"@string/home_support_title\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Title\" />\n\n                    <TextView\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginTop=\"@dimen/l_50\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:text=\"@string/home_support_content\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\" />\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"horizontal\"\n                        android:paddingTop=\"@dimen/l_50\" >\n\n                        <include\n                            item=\"@{IconLink.Patreon.INSTANCE}\"\n                            layout=\"@layout/item_icon_link\"\n                            viewModel=\"@{viewModel}\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_weight=\"1\" />\n\n                        <include\n                            item=\"@{IconLink.PayPal.Project.INSTANCE}\"\n                            layout=\"@layout/item_icon_link\"\n                            viewModel=\"@{viewModel}\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_weight=\"1\" />\n\n                    </LinearLayout>\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"bottom\"\n                android:layout_margin=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:paddingTop=\"@dimen/l1\">\n\n                    <TextView\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:text=\"@string/home_follow_title\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Title\" />\n\n                    <include\n                        item=\"@{DeveloperItem.John.INSTANCE}\"\n                        layout=\"@layout/item_developer\"\n                        viewModel=\"@{viewModel}\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"@dimen/l_50\" />\n\n                    <include\n                        item=\"@{DeveloperItem.Vvb.INSTANCE}\"\n                        layout=\"@layout/item_developer\"\n                        viewModel=\"@{viewModel}\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"@dimen/l_50\" />\n\n                    <include\n                        item=\"@{DeveloperItem.YU.INSTANCE}\"\n                        layout=\"@layout/item_developer\"\n                        viewModel=\"@{viewModel}\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"@dimen/l_50\" />\n\n                    <include\n                        item=\"@{DeveloperItem.Rikka.INSTANCE}\"\n                        layout=\"@layout/item_developer\"\n                        viewModel=\"@{viewModel}\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"@dimen/l_50\" />\n\n                    <include\n                        item=\"@{DeveloperItem.Canyie.INSTANCE}\"\n                        layout=\"@layout/item_developer\"\n                        viewModel=\"@{viewModel}\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"@dimen/l_50\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_install_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.core.Info\" />\n\n        <import type=\"com.topjohnwu.magisk.core.Config\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.install.InstallViewModel\" />\n\n    </data>\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:fillViewport=\"true\"\n        android:paddingTop=\"@dimen/internal_action_bar_size\"\n        android:paddingBottom=\"@dimen/l2\"\n        app:fitsSystemWindowsInsets=\"top|bottom\"\n        tools:paddingTop=\"24dp\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/l_50\">\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card\"\n                gone=\"@{viewModel.skipOptions}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"horizontal\">\n\n                        <ImageView\n                            style=\"@style/WidgetFoundation.Icon\"\n                            isSelected=\"@{viewModel.step > 0}\"\n                            android:layout_marginStart=\"@dimen/l_25\"\n                            app:srcCompat=\"@drawable/ic_check_circle_md2\" />\n\n                        <TextView\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_marginStart=\"@dimen/l1\"\n                            android:layout_weight=\"1\"\n                            android:gravity=\"center_vertical\"\n                            android:text=\"@string/install_options_title\"\n                            android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                            android:textStyle=\"bold\" />\n\n                        <Button\n                            style=\"@style/WidgetFoundation.Button.Text\"\n                            gone=\"@{viewModel.step != 0}\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:onClick=\"@{() -> viewModel.setStep(1)}\"\n                            android:text=\"@string/install_next\" />\n\n                    </LinearLayout>\n\n                    <LinearLayout\n                        gone=\"@{viewModel.step != 0}\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginTop=\"@dimen/l_50\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:layout_marginBottom=\"@dimen/l_50\"\n                        android:orientation=\"vertical\"\n                        android:paddingStart=\"3dp\"\n                        android:paddingEnd=\"3dp\"\n                        tools:layout_gravity=\"center\">\n\n                        <CheckBox\n                            style=\"@style/WidgetFoundation.Checkbox\"\n                            gone=\"@{Info.isSAR}\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:checked=\"@={Config.keepVerity}\"\n                            android:text=\"@string/keep_dm_verity\"\n                            tools:checked=\"true\" />\n\n                        <CheckBox\n                            style=\"@style/WidgetFoundation.Checkbox\"\n                            goneUnless=\"@{Info.isFDE}\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:checked=\"@={Config.keepEnc}\"\n                            android:text=\"@string/keep_force_encryption\"\n                            app:tint=\"?colorPrimary\" />\n\n                        <CheckBox\n                            style=\"@style/WidgetFoundation.Checkbox\"\n                            gone=\"@{Info.ramdisk}\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:checked=\"@={Config.recovery}\"\n                            android:text=\"@string/recovery_mode\"\n                            app:tint=\"?colorPrimary\" />\n\n                    </LinearLayout>\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"horizontal\">\n\n                        <ImageView\n                            style=\"@style/WidgetFoundation.Icon\"\n                            isSelected=\"@{viewModel.step > 1}\"\n                            android:layout_marginStart=\"@dimen/l_25\"\n                            app:srcCompat=\"@drawable/ic_check_circle_md2\" />\n\n                        <TextView\n                            android:layout_width=\"0dp\"\n                            android:layout_height=\"match_parent\"\n                            android:layout_marginStart=\"@dimen/l1\"\n                            android:layout_weight=\"1\"\n                            android:gravity=\"center_vertical\"\n                            android:text=\"@string/install_method_title\"\n                            android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                            android:textStyle=\"bold\" />\n\n                        <Button\n                            style=\"@style/WidgetFoundation.Button.Text\"\n                            gone=\"@{viewModel.step != 1}\"\n                            isEnabled=\"@{viewModel.method == @id/method_patch ? viewModel.data != null : viewModel.method != -1}\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:onClick=\"@{() -> viewModel.install()}\"\n                            android:text=\"@string/install_start\"\n                            app:icon=\"@drawable/ic_forth_md2\"\n                            app:iconGravity=\"textEnd\" />\n\n                    </LinearLayout>\n\n                    <RadioGroup\n                        gone=\"@{viewModel.step != 1}\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginTop=\"@dimen/l_50\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:layout_marginBottom=\"@dimen/l_50\"\n                        android:checkedButton=\"@={viewModel.method}\"\n                        android:paddingStart=\"3dp\"\n                        android:paddingEnd=\"3dp\">\n\n                        <RadioButton\n                            android:id=\"@+id/method_patch\"\n                            style=\"@style/WidgetFoundation.RadioButton\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:text=\"@string/select_patch_file\" />\n\n                        <RadioButton\n                            android:id=\"@+id/method_direct\"\n                            style=\"@style/WidgetFoundation.RadioButton\"\n                            gone=\"@{!viewModel.rooted}\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:text=\"@string/direct_install\" />\n\n                        <RadioButton\n                            android:id=\"@+id/method_inactive_slot\"\n                            style=\"@style/WidgetFoundation.RadioButton\"\n                            gone=\"@{viewModel.noSecondSlot}\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:text=\"@string/install_inactive_slot\" />\n\n                    </RadioGroup>\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <com.google.android.material.card.MaterialCardView\n                style=\"@style/WidgetFoundation.Card\"\n                gone=\"@{viewModel.notes.length == 0}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                android:focusable=\"false\">\n\n                <TextView\n                    android:id=\"@+id/release_notes\"\n                    markdownText=\"@{viewModel.notes}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_margin=\"15dp\"\n                    android:breakStrategy=\"simple\"\n                    android:hyphenationFrequency=\"none\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n                    tools:ignore=\"UnusedAttribute\"\n                    tools:maxLines=\"5\"\n                    tools:text=\"@tools:sample/lorem/random\"\n                    tools:visibility=\"visible\" />\n\n            </com.google.android.material.card.MaterialCardView>\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_log_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.log.LogViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <include\n            android:id=\"@+id/log_filter_magisk\"\n            layout=\"@layout/include_log_magisk\"\n            viewModel=\"@{viewModel}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <com.google.android.material.floatingactionbutton.FloatingActionButton\n            gone=\"@{viewModel.loading}\"\n            android:id=\"@+id/log_filter_toggle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom|end\"\n            android:layout_marginStart=\"@dimen/l1\"\n            android:layout_marginEnd=\"@dimen/l1\"\n            android:layout_marginBottom=\"72dp\"\n            app:layout_fitsSystemWindowsInsets=\"bottom\"\n            app:backgroundTint=\"?colorSurfaceSurfaceVariant\"\n            app:srcCompat=\"@drawable/ic_folder_list\"\n            app:tint=\"?colorPrimary\"\n            tools:layout_marginBottom=\"64dp\" />\n\n        <com.google.android.material.circularreveal.cardview.CircularRevealCardView\n            android:id=\"@+id/log_filter\"\n            style=\"@style/WidgetFoundation.Card\"\n            app:cardBackgroundColor=\"?colorSurface\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"bottom\"\n            android:visibility=\"invisible\"\n            app:cardCornerRadius=\"0dp\">\n\n            <include\n                android:id=\"@+id/log_filter_superuser\"\n                layout=\"@layout/include_log_superuser\"\n                viewModel=\"@{viewModel}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\" />\n\n        </com.google.android.material.circularreveal.cardview.CircularRevealCardView>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_module_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.module.ModuleViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/module_list\"\n            gone=\"@{viewModel.loading}\"\n            app:items=\"@{viewModel.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/internal_action_bar_size\"\n            android:paddingBottom=\"@dimen/internal_action_bar_size\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:listitem=\"@layout/item_module_md2\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                goneUnless=\"@{viewModel.loading}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/loading\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textStyle=\"bold\" />\n\n            <ProgressBar\n                style=\"@style/WidgetFoundation.ProgressBar.Indeterminate\"\n                goneUnless=\"@{viewModel.loading}\"\n                android:layout_marginTop=\"@dimen/l1\" />\n\n            <TextView\n                gone=\"@{viewModel.loading || viewModel.items.size() != 1 }\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/module_empty\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textStyle=\"bold\" />\n        </LinearLayout>\n\n    </FrameLayout>\n\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_settings_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.settings.SettingsViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            app:items=\"@{viewModel.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            android:id=\"@+id/settings_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:focusableInTouchMode=\"false\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/internal_action_bar_size\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:layout_marginTop=\"24dp\"\n            tools:listitem=\"@layout/item_settings\"\n            tools:paddingTop=\"@dimen/l1\" />\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:id=\"@+id/snackbar_container\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"/>\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_superuser_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.superuser.SuperuserViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/superuser_list\"\n            gone=\"@{viewModel.loading}\"\n            app:items=\"@{viewModel.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/internal_action_bar_size\"\n            android:paddingBottom=\"56dp\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:listitem=\"@layout/item_policy_md2\" />\n\n        <LinearLayout\n            goneUnless=\"@{viewModel.loading}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            tools:visibility=\"gone\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/loading\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textStyle=\"bold\" />\n\n            <ProgressBar\n                style=\"@style/WidgetFoundation.ProgressBar.Indeterminate\"\n                android:layout_marginTop=\"@dimen/l1\" />\n\n        </LinearLayout>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/fragment_theme_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.ui.theme.Theme\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.theme.ThemeViewModel\" />\n\n    </data>\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:fillViewport=\"true\"\n        android:paddingStart=\"@dimen/l1\"\n        android:paddingTop=\"@dimen/internal_action_bar_size\"\n        android:paddingEnd=\"@dimen/l1\"\n        android:paddingBottom=\"@dimen/l1\"\n        app:fitsSystemWindowsInsets=\"top|bottom\">\n\n        <LinearLayout\n            android:id=\"@+id/theme_container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/l1\"\n            android:useDefaultMargins=\"true\">\n\n            <include\n                android:id=\"@+id/theme_card_dark\"\n                item=\"@{viewModel.themeHeadline}\"\n                layout=\"@layout/item_tappable_headline\"\n                listener=\"@{viewModel}\" />\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/include_home_magisk.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.core.Info\" />\n\n        <import type=\"com.topjohnwu.magisk.ui.home.HomeViewModel.State\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.home.HomeViewModel\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:focusable=\"false\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingStart=\"@dimen/l1\"\n            android:paddingTop=\"@dimen/l_50\"\n            android:paddingEnd=\"@dimen/l1\"\n            android:paddingBottom=\"@dimen/l_50\"\n            tools:layout_gravity=\"center\">\n\n            <ImageView\n                android:id=\"@+id/home_magisk_icon\"\n                style=\"@style/WidgetFoundation.Icon.Primary\"\n                android:padding=\"@dimen/l_25\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:srcCompat=\"@drawable/ic_magisk_outline\" />\n\n            <TextView\n                android:id=\"@+id/home_magisk_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:text=\"@string/magisk\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textColor=\"?colorPrimary\"\n                android:maxLines=\"1\"\n                android:ellipsize=\"end\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/home_magisk_icon\"\n                app:layout_constraintEnd_toStartOf=\"@+id/home_magisk_button\"\n                app:layout_constraintStart_toEndOf=\"@+id/home_magisk_icon\"\n                app:layout_constraintTop_toTopOf=\"@+id/home_magisk_icon\" />\n\n            <FrameLayout\n                android:id=\"@+id/home_magisk_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/home_magisk_title\"\n                app:layout_constraintTop_toTopOf=\"@+id/home_magisk_title\">\n\n                <Button\n                    style=\"@style/WidgetFoundation.Button\"\n                    gone=\"@{viewModel.magiskState != State.OUTDATED}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{() -> viewModel.onMagiskPressed()}\"\n                    android:text=\"@string/update\"\n                    android:textAllCaps=\"false\"\n                    android:layout_gravity=\"end\"\n                    app:icon=\"@drawable/ic_update_md2\" />\n\n                <Button\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    gone=\"@{viewModel.magiskState == State.OUTDATED}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"end\"\n                    android:onClick=\"@{() -> viewModel.onMagiskPressed()}\"\n                    android:text=\"@string/install\"\n                    android:textAllCaps=\"false\"\n                    app:icon=\"@drawable/ic_install\"/>\n\n            </FrameLayout>\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/home_magisk_title_barrier\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:referencedIds=\"@{viewModel.magiskTitleBarrierIds}\"\n                tools:constraint_referenced_ids=\"home_magisk_icon,home_magisk_title,home_magisk_button\" />\n\n            <HorizontalScrollView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:fadingEdgeLength=\"@dimen/l1\"\n                android:requiresFadingEdge=\"horizontal\"\n                android:scrollbars=\"none\"\n                app:layout_constraintTop_toBottomOf=\"@+id/home_magisk_title_barrier\">\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"horizontal\">\n\n                    <androidx.constraintlayout.widget.ConstraintLayout\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\">\n\n                        <LinearLayout\n                            android:id=\"@+id/home_magisk_installed_version\"\n                            style=\"@style/W.Home.Item.Top\"\n                            app:layout_constraintStart_toStartOf=\"parent\"\n                            app:layout_constraintTop_toTopOf=\"parent\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"@string/home_installed_version\" />\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{viewModel.magiskInstalledVersion}\"\n                                tools:text=\"22.0 (22000)\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:id=\"@+id/home_device_details_zygisk\"\n                            style=\"@style/W.Home.Item\"\n                            app:layout_constraintStart_toStartOf=\"@+id/home_magisk_installed_version\"\n                            app:layout_constraintTop_toBottomOf=\"@+id/home_magisk_installed_version\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"@string/zygisk\" />\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{Info.isZygiskEnabled ? @string/yes : @string/no}\"\n                                tools:text=\"Yes\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:id=\"@+id/home_device_details_ramdisk\"\n                            style=\"@style/W.Home.Item.Bottom\"\n                            app:layout_constraintStart_toStartOf=\"@+id/home_magisk_installed_version\"\n                            app:layout_constraintTop_toBottomOf=\"@+id/home_device_details_zygisk\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"Ramdisk\" />\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{Info.ramdisk ? @string/yes : @string/no }\"\n                                tools:text=\"Yes\" />\n\n                        </LinearLayout>\n\n                    </androidx.constraintlayout.widget.ConstraintLayout>\n\n                </LinearLayout>\n\n            </HorizontalScrollView>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/include_home_manager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.ui.home.HomeViewModel.State\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.home.HomeViewModel\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:focusable=\"false\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingStart=\"@dimen/l1\"\n            android:paddingTop=\"@dimen/l_50\"\n            android:paddingEnd=\"@dimen/l1\"\n            android:paddingBottom=\"@dimen/l_50\"\n            tools:layout_gravity=\"center\">\n\n            <ImageView\n                android:id=\"@+id/home_manager_icon\"\n                style=\"@style/WidgetFoundation.Icon.Primary\"\n                android:padding=\"@dimen/l_50\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:srcCompat=\"@drawable/ic_manager\" />\n\n            <TextView\n                android:id=\"@+id/home_manager_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:text=\"@string/home_app_title\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textColor=\"?colorPrimary\"\n                android:maxLines=\"1\"\n                android:ellipsize=\"end\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/home_manager_icon\"\n                app:layout_constraintEnd_toStartOf=\"@+id/home_manager_button\"\n                app:layout_constraintStart_toEndOf=\"@+id/home_manager_icon\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"@string/home_app_title\" />\n\n            <FrameLayout\n                android:id=\"@+id/home_manager_button\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/home_manager_title\"\n                app:layout_constraintTop_toTopOf=\"@+id/home_manager_title\">\n\n                <Button\n                    style=\"@style/WidgetFoundation.Button\"\n                    gone=\"@{viewModel.appState != State.OUTDATED}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:onClick=\"@{() -> viewModel.onManagerPressed()}\"\n                    android:text=\"@string/update\"\n                    android:textAllCaps=\"false\"\n                    android:layout_gravity=\"end\"\n                    app:icon=\"@drawable/ic_update_md2\" />\n\n                <Button\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    gone=\"@{viewModel.appState != State.UP_TO_DATE}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"end\"\n                    android:onClick=\"@{() -> viewModel.onManagerPressed()}\"\n                    android:text=\"@string/install\"\n                    android:textAllCaps=\"false\"\n                    app:icon=\"@drawable/ic_install\"/>\n\n            </FrameLayout>\n\n            <androidx.constraintlayout.widget.Barrier\n                android:id=\"@+id/home_manager_title_barrier\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                app:barrierDirection=\"bottom\"\n                app:referencedIds=\"@{viewModel.appTitleBarrierIds}\"\n                tools:constraint_referenced_ids=\"home_manager_icon,home_manager_title,home_manager_button\" />\n\n            <HorizontalScrollView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:fadingEdgeLength=\"@dimen/l1\"\n                android:requiresFadingEdge=\"horizontal\"\n                android:scrollbars=\"none\"\n                app:layout_constraintTop_toBottomOf=\"@+id/home_manager_title_barrier\">\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\">\n\n                    <androidx.constraintlayout.widget.ConstraintLayout\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\">\n\n                        <LinearLayout\n                            android:id=\"@+id/home_manager_latest_version\"\n                            style=\"@style/W.Home.Item.Top\"\n                            app:layout_constraintStart_toStartOf=\"parent\"\n                            app:layout_constraintTop_toTopOf=\"parent\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"@string/home_latest_version\" />\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{viewModel.managerRemoteVersion}\"\n                                tools:text=\"22.0 (22000) (16)\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:id=\"@+id/home_manager_installed_version\"\n                            style=\"@style/W.Home.Item\"\n                            app:layout_constraintStart_toStartOf=\"parent\"\n                            app:layout_constraintTop_toBottomOf=\"@+id/home_manager_latest_version\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"@string/home_installed_version\" />\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{viewModel.managerInstalledVersion}\"\n                                tools:text=\"22.0 (22000) (16)\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:id=\"@+id/home_manager_internal_connection\"\n                            style=\"@style/W.Home.Item.Bottom\"\n                            app:layout_constraintStart_toStartOf=\"parent\"\n                            app:layout_constraintTop_toBottomOf=\"@+id/home_manager_installed_version\">\n\n                            <TextView\n                                style=\"@style/W.Home.ItemContent\"\n                                android:text=\"@string/home_package\" />\n\n                            <TextView\n                                android:id=\"@+id/home_manager_extra_connection_value\"\n                                style=\"@style/W.Home.ItemContent.Right\"\n                                android:text=\"@{context.packageName}\"\n                                tools:text=\"com.topjohnwu.magisk\" />\n\n                        </LinearLayout>\n\n                    </androidx.constraintlayout.widget.ConstraintLayout>\n\n                    <ProgressBar\n                        style=\"@style/WidgetFoundation.ProgressBar\"\n                        gone=\"@{viewModel.stateManagerProgress == 0 || viewModel.stateManagerProgress == 100}\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_gravity=\"bottom\"\n                        android:max=\"100\"\n                        android:progress=\"@{viewModel.stateManagerProgress}\" />\n\n                </LinearLayout>\n\n            </HorizontalScrollView>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/include_log_magisk.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.log.LogViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <HorizontalScrollView\n            android:id=\"@+id/log_scroll_magisk\"\n            gone=\"@{viewModel.loading}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:fillViewport=\"true\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/log_magisk\"\n                app:items=\"@{viewModel.logs}\"\n                app:extraBindings=\"@{viewModel.extraBindings}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:clipToPadding=\"false\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"@dimen/internal_action_bar_size\"\n                android:paddingBottom=\"@dimen/internal_action_bar_size\"\n                app:fitsSystemWindowsInsets=\"top|bottom\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n                tools:listitem=\"@layout/item_log_textview\"\n                tools:paddingTop=\"24dp\" />\n        </HorizontalScrollView>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                goneUnless=\"@{viewModel.loading}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/loading\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\"\n                android:textStyle=\"bold\" />\n\n            <ProgressBar\n                style=\"@style/WidgetFoundation.ProgressBar.Indeterminate\"\n                goneUnless=\"@{viewModel.loading}\"\n                android:layout_marginTop=\"@dimen/l1\" />\n\n            <FrameLayout\n                gone=\"@{viewModel.loading || !viewModel.magiskLogRaw.empty}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\">\n\n                <include\n                    item=\"@{viewModel.itemMagiskEmpty}\"\n                    layout=\"@layout/item_text\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n\n            </FrameLayout>\n        </LinearLayout>\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/include_log_superuser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.log.LogViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/log_superuser\"\n            app:items=\"@{viewModel.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            android:orientation=\"vertical\"\n            android:paddingTop=\"@dimen/internal_action_bar_size\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:listitem=\"@layout/item_log_access_md2\"\n            tools:paddingTop=\"24dp\" />\n\n        <ProgressBar\n            style=\"@style/WidgetFoundation.ProgressBar.Indeterminate\"\n            goneUnless=\"@{viewModel.loading}\"\n            android:layout_marginTop=\"@dimen/l1\" />\n\n        <FrameLayout\n            gone=\"@{viewModel.loading || !viewModel.items.empty}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\">\n\n            <include\n                item=\"@{viewModel.itemEmpty}\"\n                layout=\"@layout/item_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\" />\n\n        </FrameLayout>\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:id=\"@+id/snackbar_container\"\n            app:fitsSystemWindowsInsets=\"top|bottom\"/>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_console_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.flash.ConsoleItem\" />\n\n    </data>\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"monospace\"\n        android:text=\"@{item.item}\"\n        android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n        tools:text=\"@tools:sample/lorem/random\" />\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_developer.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.home.DeveloperItem\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.home.HomeViewModel\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_vertical\"\n        android:paddingStart=\"@dimen/l1\"\n        android:paddingEnd=\"@dimen/l1\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{item.handle}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n            android:textStyle=\"bold\"\n            tools:text=\"\\@topjohnwu\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            app:items=\"@{item.items}\"\n            app:extraBindings=\"@{viewModel.extraBindings}\"\n            app:nestedScrollingEnabled=\"@{false}\"\n            android:overScrollMode=\"never\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:fadingEdgeLength=\"@dimen/l1\"\n            android:orientation=\"horizontal\"\n            android:requiresFadingEdge=\"horizontal\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:itemCount=\"3\"\n            tools:listitem=\"@layout/item_icon_link\" />\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_hide_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.R\" />\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.deny.DenyListRvItem\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.deny.DenyListViewModel\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:focusable=\"false\"\n        tools:layout_gravity=\"center\"\n        tools:layout_marginBottom=\"@dimen/l1\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:id=\"@+id/hide_expand\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?selectableItemBackground\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                android:nextFocusRight=\"@id/hide_expand_icon\"\n                android:onClick=\"@{item::toggleExpand}\">\n\n                <ImageView\n                    android:id=\"@+id/hide_icon\"\n                    style=\"@style/WidgetFoundation.Image\"\n                    android:layout_margin=\"@dimen/l1\"\n                    android:src=\"@{item.info.iconImage}\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:layout_constraintVertical_bias=\"0\"\n                    tools:src=\"@drawable/ic_launcher\" />\n\n                <TextView\n                    android:id=\"@+id/hide_name\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"@dimen/l1\"\n                    android:ellipsize=\"middle\"\n                    android:singleLine=\"true\"\n                    android:text=\"@{item.info.label}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintBottom_toTopOf=\"@+id/hide_package\"\n                    app:layout_constraintEnd_toStartOf=\"@+id/hide_expand_icon\"\n                    app:layout_constraintStart_toEndOf=\"@+id/hide_icon\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    app:layout_constraintVertical_chainStyle=\"packed\"\n                    tools:text=\"@string/magisk\" />\n\n                <TextView\n                    android:id=\"@+id/hide_package\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@{item.info.packageName}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"@+id/hide_name\"\n                    app:layout_constraintStart_toStartOf=\"@+id/hide_name\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/hide_name\"\n                    tools:text=\"com.topjohnwu.magisk\" />\n\n                <com.topjohnwu.widget.IndeterminateCheckBox\n                    android:id=\"@+id/hide_expand_icon\"\n                    state=\"@={item.state}\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginEnd=\"@dimen/l1\"\n                    android:minWidth=\"0dp\"\n                    android:minHeight=\"0dp\"\n                    android:nextFocusLeft=\"@id/hide_expand\"\n                    app:layout_constraintBottom_toBottomOf=\"parent\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n            <androidx.recyclerview.widget.RecyclerView\n                goneUnless=\"@{item.isExpanded}\"\n                app:items=\"@{item.processes}\"\n                app:extraBindings=\"@{viewModel.extraBindings}\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?colorSurfaceVariant\"\n                android:orientation=\"vertical\"\n                app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n                tools:itemCount=\"2\"\n                tools:listitem=\"@layout/item_hide_process_md2\" />\n\n        </LinearLayout>\n\n        <ProgressBar\n            style=\"@style/WidgetFoundation.ProgressBar\"\n            gone=\"@{item.checkedPercent == 0}\"\n            android:layout_width=\"match_parent\"\n            android:layout_gravity=\"top\"\n            android:progress=\"@{item.checkedPercent}\" />\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_hide_process_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.deny.ProcessRvItem\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.deny.DenyListViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:alpha=\"@{item.enabled ? 1f : .7f}\">\n\n        <TextView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"@dimen/l1\"\n            android:layout_marginTop=\"@dimen/l_75\"\n            android:layout_marginEnd=\"@dimen/l1\"\n            android:layout_marginBottom=\"@dimen/l_75\"\n            android:singleLine=\"true\"\n            android:ellipsize=\"middle\"\n            android:text=\"@{item.displayName}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toStartOf=\"@+id/hide_process_checkbox\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:text=\"com.topjohnwu.magisk\" />\n\n        <com.google.android.material.switchmaterial.SwitchMaterial\n            android:id=\"@+id/hide_process_checkbox\"\n            android:checked=\"@={item.enabled}\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginEnd=\"@dimen/l_50\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_icon_link.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.home.IconLink\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.home.HomeViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?selectableItemBackground\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:minWidth=\"48dp\"\n        android:onClick=\"@{() -> viewModel.onLinkPressed(item.link)}\"\n        android:padding=\"@dimen/l_50\"\n        tools:layout_gravity=\"center\">\n\n        <ImageView\n            android:id=\"@+id/developer_link\"\n            style=\"@style/WidgetFoundation.Image.Small\"\n            android:layout_gravity=\"center\"\n            app:srcCompat=\"@{item.icon}\"\n            app:tint=\"?colorOnSurface\"\n            tools:srcCompat=\"@drawable/ic_paypal\" />\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_list_single_line.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.view.MagiskDialog.DialogItem\" />\n\n        <variable\n            name=\"listener\"\n            type=\"com.topjohnwu.magisk.view.MagiskDialog.DialogClickListener\" />\n\n    </data>\n\n    <TextView\n        android:id=\"@android:id/text1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?selectableItemBackground\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:onClick=\"@{() -> listener.onClick(item.position)}\"\n        android:paddingStart=\"@dimen/l1\"\n        android:paddingTop=\"@dimen/l_75\"\n        android:paddingEnd=\"@dimen/l1\"\n        android:paddingBottom=\"@dimen/l_75\"\n        android:text=\"@{item.item}\"\n        android:textAppearance=\"@style/AppearanceFoundation.Body\"\n        tools:text=\"@tools:sample/lorem\" />\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_log_access_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.R\" />\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.log.SuLogRvItem\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.log.LogViewModel\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:minHeight=\"?listPreferredItemHeightSmall\"\n        tools:layout_gravity=\"center\">\n\n        <include\n            android:id=\"@+id/log_track_container\"\n            bullet=\"@{item.log.action >= 2 ? R.drawable.ic_check_md2 : R.drawable.ic_close_md2}\"\n            isBottom=\"@{item.isBottom}\"\n            isSelected=\"@{item.log.action != 2}\"\n            isTop=\"@{item.isTop}\"\n            layout=\"@layout/item_log_track_md2\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"@dimen/l1\"\n            android:paddingTop=\"@dimen/l_50\"\n            android:paddingBottom=\"@dimen/l_50\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toEndOf=\"@+id/log_track_container\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <TextView\n                android:id=\"@+id/log_app_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@{item.log.appName}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toTopOf=\"@+id/log_info\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"@string/magisk\" />\n\n            <TextView\n                android:id=\"@+id/log_info\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@{item.info}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/log_app_name\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_log_textview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.log.LogRvItem\" />\n\n    </data>\n\n\n    <com.google.android.material.textview.MaterialTextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:fontFamily=\"monospace\"\n        android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n        android:text=\"@{item.item}\" />\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_log_track_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"isTop\"\n            type=\"Boolean\" />\n\n        <variable\n            name=\"isBottom\"\n            type=\"Boolean\" />\n\n        <variable\n            name=\"bullet\"\n            type=\"Integer\" />\n\n        <variable\n            name=\"isSelected\"\n            type=\"Boolean\" />\n\n    </data>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        tools:layout_gravity=\"center\"\n        tools:minHeight=\"?listPreferredItemHeightSmall\">\n\n        <ImageView\n            android:id=\"@+id/track_top\"\n            invisible=\"@{isTop}\"\n            android:layout_width=\"2dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginBottom=\"2dp\"\n            android:alpha=\".3\"\n            app:tint=\"?colorSecondary\"\n            app:srcCompat=\"@drawable/bg_line_bottom_rounded\"\n            app:layout_constraintBottom_toTopOf=\"@+id/track_bullet\"\n            app:layout_constraintEnd_toEndOf=\"@+id/track_bullet\"\n            app:layout_constraintStart_toStartOf=\"@+id/track_bullet\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <ImageView\n            android:id=\"@+id/track_bullet\"\n            style=\"@style/WidgetFoundation.Image.Small\"\n            isSelected=\"@{isSelected}\"\n            srcCompat=\"@{bullet}\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:tint=\"@color/color_primary_error_transient\"\n            tools:srcCompat=\"@drawable/ic_check_md2\" />\n\n        <ImageView\n            android:id=\"@+id/track_bottom\"\n            invisible=\"@{isBottom}\"\n            android:layout_width=\"2dp\"\n            android:layout_height=\"0dp\"\n            android:layout_marginTop=\"2dp\"\n            android:alpha=\".3\"\n            app:tint=\"?colorSecondary\"\n            app:srcCompat=\"@drawable/bg_line_top_rounded\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"@+id/track_bullet\"\n            app:layout_constraintStart_toStartOf=\"@+id/track_bullet\"\n            app:layout_constraintTop_toBottomOf=\"@+id/track_bullet\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_module_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.module.InstallModule\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.module.ModuleViewModel\" />\n\n    </data>\n\n    <Button\n        style=\"@style/WidgetFoundation.Button.Outlined\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"@dimen/l_50\"\n        android:onClick=\"@{() -> viewModel.installPressed()}\"\n        android:text=\"@string/module_action_install_external\"\n        android:textAllCaps=\"false\"\n        android:textSize=\"12sp\"\n        app:cornerRadius=\"@dimen/r1\"\n        app:icon=\"@drawable/ic_module_storage_md2\" />\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_module_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.R\" />\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.module.LocalModuleRvItem\" />\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.module.ModuleViewModel\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/module\"\n            style=\"@style/WidgetFoundation.Card\"\n            isEnabled=\"@{!item.removed &amp;&amp; item.enabled &amp;&amp; !item.showNotice}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:focusable=\"@{!item.removed &amp;&amp; item.enabled &amp;&amp; !item.showNotice}\"\n            android:nextFocusRight=\"@id/module_indicator\"\n            app:cardBackgroundColor=\"@color/color_card_background_color_selector\"\n            tools:isEnabled=\"false\"\n            tools:layout_gravity=\"center\"\n            tools:layout_margin=\"@dimen/l1\">\n\n            <ImageView\n                android:id=\"@+id/module_state_icon\"\n                gone=\"@{!item.removed &amp;&amp; !item.updated}\"\n                srcCompat=\"@{item.removed ? R.drawable.ic_delete_md2 : (item.updated ? R.drawable.ic_update_md2 : 0)}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:layout_gravity=\"center\"\n                android:adjustViewBounds=\"true\"\n                android:alpha=\".05\"\n                android:background=\"@null\"\n                android:duplicateParentState=\"true\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:tint=\"?colorOnSurface\"\n                tools:srcCompat=\"@drawable/ic_update_md2\" />\n\n            <androidx.constraintlayout.widget.ConstraintLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:animateLayoutChanges=\"true\"\n                android:duplicateParentState=\"true\">\n\n                <TextView\n                    android:id=\"@+id/module_title\"\n                    strikeThrough=\"@{item.removed}\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"@dimen/l1\"\n                    android:layout_marginTop=\"@dimen/l1\"\n                    android:layout_marginEnd=\"@dimen/l_50\"\n                    android:duplicateParentState=\"true\"\n                    android:text=\"@{item.item.name}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                    android:textColor=\"?android:textColorPrimary\"\n                    android:textStyle=\"bold\"\n                    app:layout_constraintEnd_toStartOf=\"@+id/module_indicator\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:layout_constraintTop_toTopOf=\"parent\"\n                    tools:text=\"@tools:sample/lorem\" />\n\n                <TextView\n                    android:id=\"@+id/module_version_author\"\n                    strikeThrough=\"@{item.removed}\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:duplicateParentState=\"true\"\n                    android:text=\"@{@string/module_version_author(item.item.version, item.item.author)}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                    android:textColor=\"?android:textColorSecondary\"\n                    app:layout_constraintEnd_toEndOf=\"@+id/module_title\"\n                    app:layout_constraintStart_toStartOf=\"@+id/module_title\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/module_title\"\n                    tools:text=\"@tools:sample/lorem\" />\n\n                <com.google.android.material.switchmaterial.SwitchMaterial\n                    android:id=\"@+id/module_indicator\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginEnd=\"@dimen/l_50\"\n                    android:checked=\"@={item.enabled}\"\n                    android:nextFocusLeft=\"@id/module\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/module_version_author\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintHorizontal_bias=\"1\"\n                    app:layout_constraintStart_toEndOf=\"@+id/module_update\"\n                    app:layout_constraintTop_toTopOf=\"@+id/module_title\" />\n\n                <TextView\n                    android:id=\"@+id/module_description\"\n                    gone=\"@{item.item.description.empty}\"\n                    strikeThrough=\"@{item.removed}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"@dimen/l1\"\n                    android:layout_marginTop=\"@dimen/l1\"\n                    android:layout_marginEnd=\"@dimen/l1\"\n                    android:duplicateParentState=\"true\"\n                    android:maxLines=\"5\"\n                    android:text=\"@{item.item.description}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                    android:textColor=\"?android:textColorSecondary\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/module_version_author\"\n                    tools:lines=\"4\"\n                    tools:text=\"@tools:sample/lorem/random\" />\n\n                <View\n                    android:id=\"@+id/module_divider\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"1dp\"\n                    android:layout_marginTop=\"@dimen/l1\"\n                    android:background=\"?colorSurfaceSurfaceVariant\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/module_description\" />\n\n                <Button\n                    android:id=\"@+id/module_update\"\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    goneUnless=\"@{item.showUpdate}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:clickable=\"true\"\n                    android:enabled=\"@{item.updateReady}\"\n                    android:focusable=\"true\"\n                    android:onClick=\"@{() -> viewModel.downloadPressed(item.item.updateInfo)}\"\n                    android:text=\"@string/update\"\n                    android:textAllCaps=\"false\"\n                    app:icon=\"@drawable/ic_update_md2\"\n                    app:iconGravity=\"textEnd\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/module_remove\"\n                    app:layout_constraintEnd_toStartOf=\"@+id/module_remove\"\n                    app:layout_constraintTop_toTopOf=\"@+id/module_remove\"\n                    app:srcCompat=\"@drawable/ic_download_md2\" />\n\n                <Button\n                    android:id=\"@+id/module_remove\"\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:clickable=\"true\"\n                    android:enabled=\"@{!item.updated}\"\n                    android:focusable=\"true\"\n                    android:onClick=\"@{() -> item.delete()}\"\n                    android:text=\"@{item.removed ? @string/module_state_restore : @string/module_state_remove}\"\n                    android:textAllCaps=\"false\"\n                    android:theme=\"@style/ThemeOverlay.Button.Text.Secondary\"\n                    app:icon=\"@{item.removed ? @drawable/ic_restart : @drawable/ic_delete_md2}\"\n                    app:iconGravity=\"textEnd\"\n                    app:layout_constraintEnd_toEndOf=\"parent\"\n                    app:layout_constraintTop_toBottomOf=\"@+id/module_divider\"\n                    tools:icon=\"@drawable/ic_delete_md2\"\n                    tools:text=\"Remove\" />\n\n                <androidx.constraintlayout.widget.Barrier\n                    android:id=\"@+id/bottom_bar_barrier\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    app:barrierDirection=\"start\"\n                    app:referencedIds=\"@{viewModel.bottomBarBarrierIds}\"\n                    tools:constraint_referenced_ids=\"module_update,module_remove\" />\n\n                <TextView\n                    android:id=\"@+id/module_notice_text\"\n                    goneUnless=\"@{item.showNotice}\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"@dimen/l1\"\n                    android:layout_marginEnd=\"@dimen/l_50\"\n                    android:fontFamily=\"sans-serif-medium\"\n                    android:text=\"@{item.noticeText}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Caption.Primary\"\n                    android:textColor=\"?colorError\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/module_remove\"\n                    app:layout_constraintEnd_toStartOf=\"@+id/bottom_bar_barrier\"\n                    app:layout_constraintStart_toEndOf=\"@id/module_config\"\n                    app:layout_constraintTop_toTopOf=\"@+id/module_remove\"\n                    tools:lines=\"2\"\n                    tools:text=\"@tools:sample/lorem/random\"\n                    tools:visibility=\"visible\" />\n\n                <Button\n                    android:id=\"@+id/module_config\"\n                    style=\"@style/WidgetFoundation.Button.Text\"\n                    goneUnless=\"@{item.showAction}\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:clickable=\"true\"\n                    android:enabled=\"@{item.enabled}\"\n                    android:focusable=\"true\"\n                    android:onClick=\"@{() -> viewModel.runAction(item.item.id, item.item.name)}\"\n                    android:text=\"@string/module_action\"\n                    android:textAllCaps=\"false\"\n                    android:visibility=\"gone\"\n                    app:icon=\"@drawable/ic_action_md2\"\n                    app:iconGravity=\"textStart\"\n                    app:layout_constraintBottom_toBottomOf=\"@+id/module_remove\"\n                    app:layout_constraintTop_toTopOf=\"@+id/module_remove\"\n                    app:layout_constraintStart_toStartOf=\"parent\"\n                    app:srcCompat=\"@drawable/ic_download_md2\" />\n\n            </androidx.constraintlayout.widget.ConstraintLayout>\n\n        </com.google.android.material.card.MaterialCardView>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_policy_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <import type=\"com.topjohnwu.magisk.databinding.DataBindingAdaptersKt\" />\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.superuser.PolicyRvItem\" />\n\n    </data>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\">\n\n        <com.google.android.material.card.MaterialCardView\n            android:id=\"@+id/policy\"\n            style=\"@style/WidgetFoundation.Card\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:alpha=\"@{item.enabled ? 1f : .5f}\"\n            android:focusable=\"true\"\n            android:nextFocusRight=\"@id/policy_indicator\"\n            android:onClick=\"@{() -> item.toggleExpand()}\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <androidx.constraintlayout.widget.ConstraintLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <ImageView\n                        android:id=\"@+id/policy_app_icon\"\n                        style=\"@style/WidgetFoundation.Image\"\n                        srcCompat=\"@{item.icon}\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:layout_marginTop=\"@dimen/l1\"\n                        android:layout_marginBottom=\"@dimen/l1\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintStart_toStartOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\"\n                        app:layout_constraintVertical_bias=\"0\"\n                        tools:srcCompat=\"@drawable/ic_logo\" />\n\n                    <TextView\n                        android:id=\"@+id/policy_app_name\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginLeft=\"@dimen/margin_generic\"\n                        android:layout_marginRight=\"@dimen/margin_generic\"\n                        android:ellipsize=\"middle\"\n                        android:gravity=\"start\"\n                        android:maxLines=\"2\"\n                        android:text=\"@{item.title}\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                        android:textIsSelectable=\"false\"\n                        android:textStyle=\"bold\"\n                        app:layout_constraintEnd_toStartOf=\"@+id/policy_indicator\"\n                        app:layout_constraintStart_toEndOf=\"@+id/policy_app_icon\"\n                        app:layout_constraintTop_toTopOf=\"@+id/policy_app_icon\"\n                        tools:text=\"@string/magisk\" />\n\n                    <TextView\n                        android:id=\"@+id/policy_package_name\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginBottom=\"@dimen/l1\"\n                        android:ellipsize=\"middle\"\n                        android:gravity=\"start\"\n                        android:maxLines=\"2\"\n                        android:text=\"@{item.packageName}\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Caption.Variant\"\n                        android:textColor=\"@android:color/tertiary_text_dark\"\n                        android:textIsSelectable=\"false\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintEnd_toEndOf=\"@id/policy_app_name\"\n                        app:layout_constraintStart_toStartOf=\"@id/policy_app_name\"\n                        app:layout_constraintTop_toBottomOf=\"@id/policy_app_name\"\n                        app:layout_constraintVertical_bias=\"0\"\n                        tools:text=\"com.topjohnwu.magisk\" />\n\n                    <FrameLayout\n                        android:id=\"@+id/policy_indicator\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginEnd=\"@dimen/l1\"\n                        android:nextFocusLeft=\"@id/policy\"\n                        app:layout_constraintBottom_toBottomOf=\"parent\"\n                        app:layout_constraintEnd_toEndOf=\"parent\"\n                        app:layout_constraintTop_toTopOf=\"parent\">\n\n                        <com.google.android.material.switchmaterial.SwitchMaterial\n                            gone=\"@{item.showSlider}\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:checked=\"@={item.enabled}\" />\n\n                        <com.google.android.material.slider.Slider\n                            goneUnless=\"@{item.showSlider}\"\n                            labelFormatter=\"@{item.sliderValueToPolicyString}\"\n                            android:layout_width=\"96dp\"\n                            android:layout_height=\"wrap_content\"\n                            android:stepSize=\"1\"\n                            android:value=\"@={DataBindingAdaptersKt.policyToSliderValue(item.sliderValue)}\"\n                            android:valueFrom=\"1\"\n                            android:valueTo=\"3\" />\n                    </FrameLayout>\n\n                </androidx.constraintlayout.widget.ConstraintLayout>\n\n                <LinearLayout\n                    android:id=\"@+id/policy_expand_container\"\n                    gone=\"@{!item.isExpanded}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:background=\"?colorSurfaceVariant\"\n                    android:orientation=\"horizontal\"\n                    tools:visibility=\"visible\">\n\n                    <Button\n                        android:id=\"@+id/policy_notify\"\n                        style=\"@style/WidgetFoundation.Button.Text\"\n                        isSelected=\"@{item.shouldNotify}\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:minHeight=\"24dp\"\n                        android:onClick=\"@{() -> item.toggleNotify()}\"\n                        android:text=\"@string/superuser_toggle_notification\"\n                        android:textAllCaps=\"false\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Tiny\"\n                        android:textColor=\"@color/color_state_primary_transient\"\n                        app:icon=\"@drawable/ic_notifications_md2\"\n                        app:iconTint=\"@color/color_state_primary_transient\"\n                        app:tint=\"@color/color_state_primary_transient\" />\n\n                    <View\n                        android:layout_width=\"1dp\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_marginTop=\"@dimen/l_50\"\n                        android:layout_marginBottom=\"@dimen/l_50\"\n                        android:background=\"?colorSurfaceSurfaceVariant\" />\n\n                    <Button\n                        android:id=\"@+id/policy_log\"\n                        style=\"@style/WidgetFoundation.Button.Text\"\n                        isSelected=\"@{item.shouldLog}\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:minHeight=\"24dp\"\n                        android:onClick=\"@{() -> item.toggleLog()}\"\n                        android:text=\"@string/logs\"\n                        android:textAllCaps=\"false\"\n                        android:textAppearance=\"@style/AppearanceFoundation.Tiny\"\n                        android:textColor=\"@color/color_state_primary_transient\"\n                        app:icon=\"@drawable/ic_bug_md2\"\n                        app:iconTint=\"@color/color_state_primary_transient\"\n                        app:tint=\"@color/color_state_primary_transient\" />\n\n                    <View\n                        android:layout_width=\"1dp\"\n                        android:layout_height=\"match_parent\"\n                        android:layout_marginTop=\"@dimen/l_50\"\n                        android:layout_marginBottom=\"@dimen/l_50\"\n                        android:background=\"?colorSurfaceSurfaceVariant\" />\n\n                    <Button\n                        android:id=\"@+id/policy_delete\"\n                        style=\"@style/WidgetFoundation.Button.Text\"\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_weight=\"1\"\n                        android:minHeight=\"24dp\"\n                        android:onClick=\"@{() -> item.revoke()}\"\n                        android:text=\"@string/superuser_toggle_revoke\"\n                        android:textAllCaps=\"false\"\n                        android:textColor=\"?colorError\"\n                        android:textSize=\"12sp\"\n                        app:icon=\"@drawable/ic_delete_md2\"\n                        app:iconTint=\"?colorError\"\n                        app:rippleColor=\"?colorError\" />\n\n                </LinearLayout>\n\n            </LinearLayout>\n\n        </com.google.android.material.card.MaterialCardView>\n\n    </FrameLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.settings.BaseSettingsItem\" />\n\n        <variable\n            name=\"handler\"\n            type=\"com.topjohnwu.magisk.ui.settings.BaseSettingsItem.Handler\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        isEnabled=\"@{item.enabled}\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:alpha=\"@{item.enabled ? 1f : .5f}\"\n        android:clickable=\"@{item.enabled}\"\n        android:focusable=\"@{item.enabled}\"\n        android:onClick=\"@{(view) -> item.onPressed(view, handler)}\"\n        tools:layout_gravity=\"center\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingStart=\"@{item.icon == 0 ? @dimen/l1 : 0}\"\n            android:paddingEnd=\"@dimen/l1\">\n\n            <ImageView\n                android:id=\"@+id/icon\"\n                style=\"@style/WidgetFoundation.Icon\"\n                gone=\"@{item.icon == 0}\"\n                android:background=\"@null\"\n                app:srcCompat=\"@{item.icon}\"\n                tools:srcCompat=\"@drawable/ic_fingerprint\" />\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:gravity=\"center_vertical\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"@dimen/l1\"\n                android:paddingBottom=\"@dimen/l1\">\n\n                <TextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center\"\n                    android:gravity=\"start\"\n                    android:text=\"@{item.title}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                    android:textStyle=\"bold\"\n                    tools:lines=\"1\"\n                    tools:text=\"@tools:sample/lorem/random\" />\n\n                <TextView\n                    gone=\"@{item.description.empty}\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@{item.description}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Tiny.Variant\"\n                    tools:lines=\"2\"\n                    tools:text=\"@tools:sample/lorem/random\" />\n\n            </LinearLayout>\n\n            <com.google.android.material.switchmaterial.SwitchMaterial\n                android:id=\"@+id/selector_indicator\"\n                goneUnless=\"@{item.showSwitch}\"\n                isEnabled=\"@{item.enabled}\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"@{item.checked}\"\n                android:focusable=\"@{item.enabled}\"\n                android:onCheckedChanged=\"@{(v, c) -> item.onToggle(v, handler, c)}\" />\n\n        </LinearLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_settings_section.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.ui.settings.BaseSettingsItem\" />\n\n    </data>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingTop=\"@dimen/l1\"\n        android:paddingBottom=\"@dimen/l_50\">\n\n        <TextView\n            gone=\"@{item.title.empty}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{item.title}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Large.Secondary\"\n            android:textStyle=\"bold\"\n            tools:text=\"@tools:sample/lorem\" />\n\n        <TextView\n            gone=\"@{item.description.empty}\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@{item.description}\"\n            android:textAppearance=\"@style/AppearanceFoundation.Tiny.Bold\"\n            tools:text=\"@tools:sample/lorem/random\" />\n\n    </LinearLayout>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_spinner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    style=\"?android:attr/spinnerItemStyle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:ellipsize=\"marquee\"\n    android:background=\"?colorSurfaceSurfaceVariant\"\n    android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n    android:gravity=\"center_vertical\"\n    android:minHeight=\"?attr/listPreferredItemHeightSmall\"\n    android:singleLine=\"true\"\n    android:textAlignment=\"inherit\"\n    tools:text=\"Forever\" />\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_tappable_headline.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.view.TappableHeadlineItem\" />\n\n        <variable\n            name=\"listener\"\n            type=\"com.topjohnwu.magisk.view.TappableHeadlineItem.Listener\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"@{() -> listener.onItemPressed(item)}\"\n        tools:layout_gravity=\"center\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <ImageView\n                android:id=\"@+id/tappable_icon\"\n                style=\"@style/WidgetFoundation.Icon\"\n                android:background=\"@null\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:srcCompat=\"@{item.icon}\"\n                tools:srcCompat=\"@drawable/ic_day_night\" />\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:requiresFadingEdge=\"horizontal\"\n                android:singleLine=\"true\"\n                android:text=\"@{item.title}\"\n                android:textAppearance=\"@style/AppearanceFoundation.Body\"\n                android:textStyle=\"bold\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toStartOf=\"@+id/headline_icon_pointer\"\n                app:layout_constraintStart_toEndOf=\"@+id/tappable_icon\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"@string/settings_dark_mode_title\" />\n\n            <ImageView\n                android:id=\"@+id/headline_icon_pointer\"\n                style=\"@style/WidgetFoundation.Icon\"\n                android:background=\"@null\"\n                android:rotation=\"180\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:srcCompat=\"@drawable/ic_back_md2\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"item\"\n            type=\"com.topjohnwu.magisk.view.TextItem\" />\n\n    </data>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:padding=\"@dimen/l1\"\n        android:text=\"@{item.item}\"\n        android:textAppearance=\"@style/AppearanceFoundation.Tiny.Variant\"\n        tools:text=\"@tools:sample/lorem/random\" />\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_theme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <data>\n\n        <variable\n            name=\"viewModel\"\n            type=\"com.topjohnwu.magisk.ui.theme.ThemeViewModel\" />\n\n        <variable\n            name=\"theme\"\n            type=\"com.topjohnwu.magisk.ui.theme.Theme\" />\n\n    </data>\n\n    <com.google.android.material.card.MaterialCardView\n        style=\"@style/WidgetFoundation.Card\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:onClick=\"@{() -> viewModel.saveTheme(theme)}\"\n        app:cardBackgroundColor=\"@android:color/transparent\"\n        app:strokeColor=\"?colorPrimaryVariant\"\n        app:strokeWidth=\"1.5dp\"\n        tools:layout_gravity=\"center\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?colorSurface\">\n\n            <com.google.android.material.appbar.AppBarLayout\n                android:id=\"@+id/theme_appbar\"\n                style=\"@style/WidgetFoundation.Appbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintTop_toTopOf=\"parent\">\n\n                <com.google.android.material.appbar.MaterialToolbar\n                    style=\"@style/WidgetFoundation.Toolbar\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\">\n\n                        <View\n                            android:layout_width=\"100dp\"\n                            android:layout_height=\"17sp\"\n                            android:layout_gravity=\"center_vertical|start\"\n                            android:background=\"?colorOnSurface\" />\n\n                        <View\n                            android:layout_width=\"70dp\"\n                            android:layout_height=\"12sp\"\n                            android:layout_gravity=\"center_vertical|start\"\n                            android:layout_marginTop=\"@dimen/l_25\"\n                            android:background=\"?colorOnSurfaceVariant\" />\n\n                    </LinearLayout>\n\n                </com.google.android.material.appbar.MaterialToolbar>\n\n            </com.google.android.material.appbar.AppBarLayout>\n\n            <com.google.android.material.card.MaterialCardView\n                android:id=\"@+id/theme_card_bottom\"\n                style=\"@style/WidgetFoundation.Card\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"@dimen/l1\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginEnd=\"@dimen/l1\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/theme_appbar\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"@dimen/l1\">\n\n                    <View\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"17sp\"\n                        android:background=\"?colorOnSurface\" />\n\n                    <View\n                        android:layout_width=\"40dp\"\n                        android:layout_height=\"11sp\"\n                        android:layout_marginTop=\"@dimen/l_25\"\n                        android:background=\"?colorOnSurfaceVariant\" />\n\n                    <View\n                        android:layout_width=\"30dp\"\n                        android:layout_height=\"11sp\"\n                        android:layout_marginTop=\"@dimen/l_25\"\n                        android:background=\"?colorOnSurfaceVariant\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <LinearLayout\n                android:id=\"@+id/theme_primary\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:background=\"?colorPrimary\"\n                android:orientation=\"vertical\"\n                android:padding=\"@dimen/l1\"\n                app:layout_constraintTop_toBottomOf=\"@+id/theme_card_bottom\">\n\n                <TextView\n                    android:layout_width=\"125dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@{theme.themeName}\"\n                    android:textAppearance=\"@style/AppearanceFoundation.Title.OnPrimary\"\n                    android:textStyle=\"bold\"\n                    tools:text=\"Default\" />\n\n                <View\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"12sp\"\n                    android:background=\"?colorOnPrimaryVariant\" />\n\n                <View\n                    android:layout_width=\"75dp\"\n                    android:layout_height=\"12sp\"\n                    android:layout_marginTop=\"@dimen/l_25\"\n                    android:background=\"?colorOnPrimaryVariant\" />\n\n            </LinearLayout>\n\n            <com.google.android.material.card.MaterialCardView\n                android:id=\"@+id/theme_navigation\"\n                style=\"@style/WidgetFoundation.Card.Elevated\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"@dimen/l1\"\n                android:layout_marginBottom=\"@dimen/l1\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@+id/theme_primary\">\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"horizontal\"\n                    android:padding=\"@dimen/l1\">\n\n                    <View\n                        style=\"@style/WidgetFoundation.Image.Small\"\n                        android:background=\"?colorSecondary\" />\n\n                    <View\n                        style=\"@style/WidgetFoundation.Image.Small\"\n                        android:layout_marginStart=\"@dimen/l1\"\n                        android:background=\"?colorDisabled\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <ImageView\n            style=\"@style/WidgetFoundation.Icon.OnPrimary\"\n            gone=\"@{!theme.isSelected}\"\n            android:layout_gravity=\"end|top\"\n            android:layout_margin=\"@dimen/l_50\"\n            android:background=\"@drawable/bg_selection_circle_green\"\n            app:srcCompat=\"@drawable/ic_check_md2\"\n            app:tint=\"#fff\" />\n\n    </com.google.android.material.card.MaterialCardView>\n\n</layout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/item_theme_container.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    style=\"@style/W.Theme.Container\">\n\n    <FrameLayout\n        android:id=\"@+id/left\"\n        style=\"@style/W.Theme.Left\" />\n\n    <FrameLayout\n        android:id=\"@+id/right\"\n        style=\"@style/W.Theme.Right\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/apk/src/main/res/layout/markdown_window_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <TextView\n        android:id=\"@+id/md_txt\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"15dp\"\n        android:layout_marginEnd=\"15dp\"\n        android:breakStrategy=\"simple\"\n        android:hyphenationFrequency=\"none\"\n        android:paddingTop=\"10dp\"\n        android:textAppearance=\"@style/AppearanceFoundation.Caption\"\n        tools:ignore=\"UnusedAttribute\" />\n\n</ScrollView>\n"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_bottom_nav.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/homeFragment\"\n        android:icon=\"@drawable/ic_home_md2\"\n        android:title=\"@string/section_home\"\n        tools:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/superuserFragment\"\n        android:icon=\"@drawable/ic_superuser_md2\"\n        android:title=\"@string/superuser\"\n        tools:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/logFragment\"\n        android:icon=\"@drawable/ic_bug_md2\"\n        android:title=\"@string/logs\"\n        tools:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/modulesFragment\"\n        android:icon=\"@drawable/ic_module_md2\"\n        android:title=\"@string/modules\"\n        tools:showAsAction=\"always\" />\n\n</menu>\n"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_deny_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_search\"\n        android:icon=\"@drawable/ic_search_md2\"\n        android:title=\"@string/hide_search\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_show_system\"\n        android:checkable=\"true\"\n        android:title=\"@string/show_system_app\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/action_show_OS\"\n        android:checkable=\"true\"\n        android:title=\"@string/show_os_app\"\n        app:showAsAction=\"never\" />\n</menu>\n"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_flash.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/action_save\"\n        android:icon=\"@drawable/ic_save_md2\"\n        android:title=\"@string/menuSaveLog\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_home_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/action_reboot\"\n        android:icon=\"@drawable/ic_restart\"\n        android:title=\"@string/reboot\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/action_settings\"\n        android:icon=\"@drawable/ic_settings_md2\"\n        android:title=\"@string/settings\"\n        app:showAsAction=\"ifRoom\" />\n\n</menu>\n"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_log_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/action_save\"\n        android:icon=\"@drawable/ic_save_md2\"\n        android:title=\"@string/menuSaveLog\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/action_clear\"\n        android:icon=\"@drawable/ic_delete_md2\"\n        android:title=\"@string/menuClearLog\"\n        app:showAsAction=\"ifRoom\" />\n\n</menu>"
  },
  {
    "path": "app/apk/src/main/res/menu/menu_reboot.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/action_reboot_normal\"\n        android:title=\"@string/reboot\" />\n\n    <item\n        android:id=\"@+id/action_reboot_userspace\"\n        android:title=\"@string/reboot_userspace\"\n        android:visible=\"false\" />\n\n    <item\n        android:id=\"@+id/action_reboot_recovery\"\n        android:title=\"@string/reboot_recovery\" />\n\n    <item\n        android:id=\"@+id/action_reboot_bootloader\"\n        android:title=\"@string/reboot_bootloader\" />\n\n    <item\n        android:id=\"@+id/action_reboot_download\"\n        android:title=\"@string/reboot_download\" />\n\n    <item\n        android:id=\"@+id/action_reboot_edl\"\n        android:title=\"@string/reboot_edl\" />\n\n    <item\n        android:id=\"@+id/action_reboot_safe_mode\"\n        android:checkable=\"true\"\n        android:title=\"@string/reboot_safe_mode\" />\n\n</menu>\n"
  },
  {
    "path": "app/apk/src/main/res/navigation/main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<navigation xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main\"\n    app:startDestination=\"@id/homeFragment\">\n\n    <fragment\n        android:id=\"@+id/denyFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.deny.DenyListFragment\"\n        android:label=\"DenyListFragment\"\n        tools:layout=\"@layout/fragment_deny_md2\" />\n\n    <fragment\n        android:id=\"@+id/homeFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.home.HomeFragment\"\n        android:label=\"HomeFragment\"\n        tools:layout=\"@layout/fragment_home_md2\">\n\n        <action\n            android:id=\"@+id/action_homeFragment_to_settingsFragment\"\n            app:destination=\"@id/settingsFragment\"\n            app:enterAnim=\"@anim/fragment_enter\"\n            app:exitAnim=\"@anim/fragment_exit\"\n            app:popEnterAnim=\"@anim/fragment_enter_pop\"\n            app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n        <action\n            android:id=\"@+id/action_homeFragment_to_installFragment\"\n            app:destination=\"@id/installFragment\"\n            app:enterAnim=\"@anim/fragment_enter\"\n            app:exitAnim=\"@anim/fragment_exit\"\n            app:popEnterAnim=\"@anim/fragment_enter_pop\"\n            app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/flashFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.flash.FlashFragment\"\n        android:label=\"FlashFragment\"\n        tools:layout=\"@layout/fragment_flash_md2\">\n\n        <argument\n            android:name=\"action\"\n            app:argType=\"string\" />\n\n        <argument\n            android:name=\"additional_data\"\n            android:defaultValue=\"@null\"\n            app:argType=\"android.net.Uri\"\n            app:nullable=\"true\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/actionFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.module.ActionFragment\"\n        android:label=\"ActionFragment\"\n        tools:layout=\"@layout/fragment_action_md2\" >\n\n        <argument\n            android:name=\"id\"\n            app:argType=\"string\" />\n\n        <argument\n            android:name=\"name\"\n            app:argType=\"string\" />\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/installFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.install.InstallFragment\"\n        android:label=\"InstallFragment\"\n        tools:layout=\"@layout/fragment_install_md2\" />\n\n    <fragment\n        android:id=\"@+id/logFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.log.LogFragment\"\n        android:label=\"LogFragment\"\n        tools:layout=\"@layout/fragment_log_md2\" />\n\n    <fragment\n        android:id=\"@+id/modulesFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.module.ModuleFragment\"\n        android:label=\"ModuleFragment\"\n        tools:layout=\"@layout/fragment_module_md2\" />\n\n    <fragment\n        android:id=\"@+id/settingsFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.settings.SettingsFragment\"\n        android:label=\"SettingsFragment\"\n        tools:layout=\"@layout/fragment_settings_md2\">\n\n        <action\n            android:id=\"@+id/action_settingsFragment_to_themeFragment\"\n            app:destination=\"@id/themeFragment\"\n            app:enterAnim=\"@anim/fragment_enter\"\n            app:exitAnim=\"@anim/fragment_exit\"\n            app:popEnterAnim=\"@anim/fragment_enter_pop\"\n            app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n        <action\n            android:id=\"@+id/action_settingsFragment_to_denyFragment\"\n            app:destination=\"@id/denyFragment\"\n            app:enterAnim=\"@anim/fragment_enter\"\n            app:exitAnim=\"@anim/fragment_exit\"\n            app:popEnterAnim=\"@anim/fragment_enter_pop\"\n            app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n    </fragment>\n\n    <fragment\n        android:id=\"@+id/superuserFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.superuser.SuperuserFragment\"\n        android:label=\"SuperuserFragment\"\n        tools:layout=\"@layout/fragment_superuser_md2\" />\n\n    <fragment\n        android:id=\"@+id/themeFragment\"\n        android:name=\"com.topjohnwu.magisk.ui.theme.ThemeFragment\"\n        android:label=\"ThemeFragment\"\n        tools:layout=\"@layout/fragment_theme_md2\" />\n\n    <action\n        android:id=\"@+id/action_homeFragment\"\n        app:destination=\"@id/homeFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\"\n        app:popUpTo=\"@id/homeFragment\"\n        app:popUpToInclusive=\"true\" />\n\n    <action\n        android:id=\"@+id/action_superuserFragment\"\n        app:destination=\"@id/superuserFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\"\n        app:popUpTo=\"@id/homeFragment\" />\n\n    <action\n        android:id=\"@+id/action_logFragment\"\n        app:destination=\"@id/logFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\"\n        app:popUpTo=\"@id/homeFragment\" />\n\n    <action\n        android:id=\"@+id/action_moduleFragment\"\n        app:destination=\"@id/modulesFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\"\n        app:popUpTo=\"@id/homeFragment\" />\n\n    <action\n        android:id=\"@+id/action_flashFragment\"\n        app:destination=\"@id/flashFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n    <action\n        android:id=\"@+id/action_actionFragment\"\n        app:destination=\"@id/actionFragment\"\n        app:enterAnim=\"@anim/fragment_enter\"\n        app:exitAnim=\"@anim/fragment_exit\"\n        app:popEnterAnim=\"@anim/fragment_enter_pop\"\n        app:popExitAnim=\"@anim/fragment_exit_pop\" />\n\n</navigation>\n"
  },
  {
    "path": "app/apk/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--region Deprecated-->\n    <attr name=\"cardStyle\" format=\"reference\" />\n    <attr name=\"colorAccentFallback\" format=\"reference\" />\n    <attr name=\"imageColorTint\" format=\"reference\" />\n    <attr name=\"colorControl\" format=\"reference\" />\n    <!--endregion-->\n\n    <!--region Colors-->\n\n    <!--Static-->\n    <attr name=\"colorDisabled\" format=\"color\" />\n    <attr name=\"colorDisabledVariant\" format=\"color\" />\n    <attr name=\"colorSurfaceVariant\" format=\"color\" />\n    <attr name=\"colorOnPrimaryVariant\" format=\"color\" />\n    <attr name=\"colorOnSurfaceVariant\" format=\"color\" />\n    <attr name=\"colorSurfaceSurfaceVariant\" format=\"color\" />\n\n    <!--endregion-->\n\n    <declare-styleable name=\"ConcealableView\">\n        <attr name=\"state_hidden\" format=\"boolean\" />\n    </declare-styleable>\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <dimen name=\"margin_generic\">16dp</dimen>\n\n    <dimen name=\"l_125\">2dp</dimen>\n    <dimen name=\"l_25\">4dp</dimen>\n    <dimen name=\"l_50\">8dp</dimen>\n    <dimen name=\"l_75\">12dp</dimen>\n    <dimen name=\"l1\">16dp</dimen>\n    <dimen name=\"l2\">32dp</dimen>\n    <dimen name=\"l3\">48dp</dimen>\n\n    <dimen name=\"r1\">8dp</dimen>\n\n    <dimen name=\"internal_action_bar_size\">56dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <item name=\"recyclerScrollListener\" type=\"id\" />\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/styles_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Foundation\" parent=\"Theme.Foundation.Light\" />\n\n    <!--region Do not remove-->\n    <style name=\"Empty\" />\n\n    <style name=\"WidgetFoundation\" parent=\"android:Widget\" />\n\n    <!--endregion-->\n\n    <style name=\"Foundation.PopupMenu\" parent=\"Widget.MaterialComponents.PopupMenu\">\n        <item name=\"android:itemTextAppearance\">@style/AppearanceFoundation.Caption</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"Foundation.Default\">\n        <item name=\"android:includeFontPadding\">false</item>\n        <item name=\"actionBarSize\">@dimen/internal_action_bar_size</item>\n        <item name=\"popupMenuStyle\">@style/Foundation.PopupMenu</item>\n    </style>\n\n    <style name=\"Foundation.Floating\" parent=\"Empty\">\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/styles_md2_appearance.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--region Display-->\n    <style name=\"AppearanceFoundation.Large\" parent=\"TextAppearance.AppCompat.Large\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Large.Secondary\">\n        <item name=\"android:textColor\">?colorSecondary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Title-->\n    <style name=\"AppearanceFoundation.Title\" parent=\"TextAppearance.AppCompat.Title\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Title.OnPrimary\">\n        <item name=\"android:textColor\">?attr/colorOnPrimary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Body-->\n    <style name=\"AppearanceFoundation.Body\" parent=\"TextAppearance.AppCompat.Body1\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Caption-->\n    <style name=\"AppearanceFoundation.Caption\" parent=\"TextAppearance.AppCompat.Caption\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.Variant\">\n        <item name=\"android:textColor\">?attr/colorOnSurfaceVariant</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.Primary\">\n        <item name=\"android:textColor\">?attr/colorPrimary</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.OnPrimary\">\n        <item name=\"android:textColor\">?attr/colorOnPrimary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Tiny-->\n    <style name=\"AppearanceFoundation.Tiny\" parent=\"TextAppearance.AppCompat.Caption\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n        <item name=\"android:textSize\">11sp</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Tiny.Bold\">\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Tiny.Variant\">\n        <item name=\"android:textColor\">?attr/colorOnSurfaceVariant</item>\n    </style>\n\n    <!--endregion-->\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/styles_md2_impl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"WidgetFoundation.Appbar\" parent=\"Widget.MaterialComponents.AppBarLayout.Surface\" />\n\n    <style name=\"WidgetFoundation.Toolbar\" parent=\"Widget.MaterialComponents.Toolbar.Surface\">\n        <item name=\"titleTextAppearance\">@style/AppearanceFoundation.Title</item>\n        <item name=\"titleTextColor\">?colorOnSurface</item>\n        <item name=\"subtitleTextAppearance\">@style/AppearanceFoundation.Caption</item>\n        <item name=\"subtitleTextColor\">?colorOnSurfaceVariant</item>\n        <item name=\"android:background\">@android:color/transparent</item>\n        <item name=\"contentInsetStartWithNavigation\">0dp</item>\n        <item name=\"android:layout_height\">?actionBarSize</item>\n        <item name=\"android:theme\">@style/ThemeOverlay.MaterialComponents.ActionBar</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Card\" parent=\"Widget.MaterialComponents.CardView\">\n        <item name=\"android:focusable\">auto</item>\n        <item name=\"cardBackgroundColor\">?colorSurfaceVariant</item>\n        <item name=\"cardCornerRadius\">@dimen/l_50</item>\n        <item name=\"cardElevation\">0dp</item>\n        <item name=\"cardPreventCornerOverlap\">false</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Card.Primary\">\n        <item name=\"cardBackgroundColor\">?colorPrimary</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Card.Elevated\">\n        <item name=\"cardBackgroundColor\">?colorSurfaceSurfaceVariant</item>\n        <item name=\"cardCornerRadius\">@dimen/l_50</item>\n        <item name=\"cardElevation\">@dimen/l_125</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Button\" parent=\"Widget.MaterialComponents.Button\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Outlined\" parent=\"Widget.MaterialComponents.Button.OutlinedButton\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n        <item name=\"strokeColor\">?colorPrimary</item>\n        <item name=\"android:textColor\">?colorPrimary</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Outlined.Error\">\n        <item name=\"strokeColor\">@color/color_error_transient</item>\n        <item name=\"rippleColor\">@color/color_error_transient</item>\n        <item name=\"android:textColor\">@color/color_error_transient</item>\n        <item name=\"iconTint\">@color/color_error_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Text\" parent=\"Widget.MaterialComponents.Button.TextButton\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Text.OnPrimary\">\n        <item name=\"rippleColor\">?colorOnPrimary</item>\n        <item name=\"android:textColor\">?colorOnPrimary</item>\n        <item name=\"iconTint\">?colorOnPrimary</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Image\">\n        <item name=\"android:layout_width\">32dp</item>\n        <item name=\"android:layout_height\">32dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Image.Big\">\n        <item name=\"android:layout_width\">48dp</item>\n        <item name=\"android:layout_height\">48dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Image.Small\">\n        <item name=\"android:layout_width\">24dp</item>\n        <item name=\"android:layout_height\">24dp</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Icon\" parent=\"WidgetFoundation.Image.Big\">\n        <item name=\"android:padding\">@dimen/l_75</item>\n        <item name=\"android:background\">?selectableItemBackgroundBorderless</item>\n        <item name=\"tint\">@color/color_text_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Icon.Primary\">\n        <item name=\"tint\">@color/color_primary_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Icon.OnPrimary\">\n        <item name=\"tint\">@color/color_on_primary_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Checkbox\" parent=\"Widget.AppCompat.CompoundButton.CheckBox\">\n        <item name=\"android:paddingStart\">@dimen/l1</item>\n        <item name=\"android:paddingEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"WidgetFoundation.RadioButton\" parent=\"Widget.AppCompat.CompoundButton.RadioButton\">\n        <item name=\"android:paddingStart\">@dimen/l1</item>\n        <item name=\"android:paddingEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"WidgetFoundation.ProgressBar\" parent=\"Widget.AppCompat.ProgressBar.Horizontal\">\n        <item name=\"android:indeterminate\">false</item>\n        <item name=\"android:layout_height\">4dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.ProgressBar.Indeterminate\" parent=\"Widget.AppCompat.ProgressBar.Horizontal\">\n        <item name=\"android:indeterminate\">true</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_width\">100dp</item>\n        <item name=\"android:indeterminateTint\">?colorPrimary</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/styles_view_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"W\" parent=\"android:Widget\" />\n\n    <!--region Home-->\n\n    <style name=\"W.Home\" />\n\n    <style name=\"W.Home.Item\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:orientation\">horizontal</item>\n        <item name=\"android:paddingStart\">@dimen/l1</item>\n        <item name=\"android:paddingEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"W.Home.Item.Top\">\n        <item name=\"android:paddingTop\">@dimen/l_75</item>\n    </style>\n\n    <style name=\"W.Home.Item.Bottom\">\n        <item name=\"android:paddingBottom\">@dimen/l_75</item>\n    </style>\n\n    <style name=\"W.Home.ItemContent\">\n        <item name=\"android:layout_width\">wrap_content</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:includeFontPadding\">false</item>\n        <item name=\"android:textAppearance\">@style/AppearanceFoundation.Caption.Variant</item>\n    </style>\n\n    <style name=\"W.Home.ItemContent.Right\">\n        <item name=\"android:layout_marginStart\">@dimen/l_50</item>\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"android:textAppearance\">@style/AppearanceFoundation.Caption</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Themes-->\n\n    <style name=\"W.Theme\">\n        <item name=\"android:layout_width\">0dp</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_marginTop\">@dimen/l1</item>\n        <item name=\"android:layout_weight\">1</item>\n    </style>\n\n    <style name=\"W.Theme.Left\">\n        <item name=\"android:layout_marginEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"W.Theme.Right\" />\n\n    <style name=\"W.Theme.Container\" parent=\"Empty\">\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:orientation\">horizontal</item>\n        <item name=\"android:baselineAligned\">false</item>\n    </style>\n\n    <!--endregion-->\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/theme_overlay.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"ThemeOverlay.Button.Text.Secondary\" parent=\"\">\n        <item name=\"colorPrimary\">?colorSecondary</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Base.V23.Theme.Foundation.Light\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"android:windowBackground\">?colorSurface</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"dialogTheme\">@style/ThemeOverlay.Foundation.Dialog</item>\n        <item name=\"android:statusBarColor\">#40bdbdbd</item>\n        <item name=\"android:navigationBarColor\">#38000000</item>\n        <item name=\"android:windowLightStatusBar\">true</item>\n    </style>\n\n    <style name=\"Base.V23.Theme.Foundation\" parent=\"Theme.MaterialComponents.NoActionBar\">\n        <item name=\"android:windowBackground\">?colorSurface</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"dialogTheme\">@style/ThemeOverlay.Foundation.Dialog</item>\n        <item name=\"android:statusBarColor\">#60000000</item>\n        <item name=\"android:navigationBarColor\">#60000000</item>\n    </style>\n\n    <style name=\"Theme.Foundation.Light\" parent=\"Base.V23.Theme.Foundation.Light\" />\n\n    <style name=\"Theme.Foundation\" parent=\"Base.V23.Theme.Foundation\" />\n\n    <style name=\"Base.V23.ThemeOverlay.Foundation.Dialog\" parent=\"ThemeOverlay.MaterialComponents.Dialog\">\n        <item name=\"android:windowMinWidthMajor\">@dimen/abc_dialog_min_width_major</item>\n        <item name=\"android:windowMinWidthMinor\">@dimen/abc_dialog_min_width_minor</item>\n    </style>\n\n    <style name=\"ThemeOverlay.Foundation.Dialog\" parent=\"Base.V23.ThemeOverlay.Foundation.Dialog\" />\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/themes_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--\n    # Theme guide\n\n    This should provide all the information you need to create new !color! themes.\n\n    ## Inheritance\n\n    You might want to inherit default values from \"base\" theme. You can do so implicitly by just\n    writing `<style name=\"ThemeFoundationMD2.MySuperAwesomeTheme\" parent=\"ThemeFoundationMD2.Piplup\">`.\n    With this approach you can only change values you (don't) like - such as `colorPrimary` or\n    `colorSecondary`.\n\n    ## Day / Night ? How do I define both in one theme?\n\n    Define Theme here for \"Day\" theme and in `values-night` directory define theme with same name\n    and parent if applicable. The framework will automatically switch between day/night themes based\n    on user's requested configuration. (Always light, Always dark, Follow system)\n\n    You might choose to define only \"Day\" theme with Dark colors to have dark theme regardless.\n    That's super lazy approach and is discouraged but the framework permits it.\n\n    ## What to theme\n\n    Generally I'd suggest theming only `colorPrimary`, `colorSecondary` and their variants. Make\n    sure that text is readable if displayed on primary or secondary color!\n\n    You can check that very easily by visiting https://www.colorhexa.com/. After you put in your\n    color hex, you can scroll down to \"Preview\" where it will compute whether dark/bright text\n    should be displayed on top of it. Then you must edit respective `colorOn...`.\n\n    Also check \"Color Blindness Simulator\". Primary and secondary colors must not match or be\n    similar in type. Text on color must have enough contrast so it can be read by everyone!\n\n    !! Themes that will not satisfy these requirements will be promptly deleted without further\n    !! notice. In repeated attempts to push such themes you will be automatically blacklisted.\n    -->\n\n    <style name=\"ThemeFoundationMD2\" parent=\"Foundation.Default\" />\n\n    <!--1st party themes-->\n\n    <style name=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#4EAFF5</item>\n        <item name=\"colorPrimaryVariant\">#804EAFF5</item>\n        <item name=\"colorSecondary\">#3E78AF</item>\n        <item name=\"colorSecondaryVariant\">#803E78AF</item>\n        <item name=\"colorSurface\">#F9F9F9</item>\n        <item name=\"colorSurfaceVariant\">#EEEEEE</item>\n        <item name=\"colorSurfaceSurfaceVariant\">?colorSurface</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n        <item name=\"colorOnSecondary\">#F9F9F9</item>\n        <item name=\"colorOnBackground\">?colorOnSurface</item>\n        <item name=\"colorError\">#CC0047</item>\n        <item name=\"colorOnError\">#F9F9F9</item>\n        <item name=\"colorOnSurface\">#444444</item>\n        <item name=\"colorOnSurfaceVariant\">#BF444444</item>\n        <item name=\"colorDisabled\">#808080</item>\n        <item name=\"colorDisabledVariant\">#66808080</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Amoled\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSurface\">#FFF</item>\n        <item name=\"colorOnPrimary\">#FFF</item>\n        <item name=\"colorOnSecondary\">#FFF</item>\n        <item name=\"colorOnBackground\">#FFF</item>\n        <item name=\"colorOnError\">#FFF</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Rayquaza\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#68A17F</item>\n        <item name=\"colorPrimaryVariant\">#8068A17F</item>\n        <item name=\"colorSecondary\">#2F6D43</item>\n        <item name=\"colorSecondaryVariant\">#802F6D43</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos.Base\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSecondary\">#B29667</item>\n        <item name=\"colorSecondaryVariant\">#80B29667</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos\" parent=\"ThemeFoundationMD2.Zapdos.Base\">\n        <item name=\"colorPrimary\">#F2B90D</item>\n        <item name=\"colorPrimaryVariant\">#80F2B90D</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Charmeleon\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#DB7366</item>\n        <item name=\"colorPrimaryVariant\">#80DB7366</item>\n        <item name=\"colorSecondary\">#B65247</item>\n        <item name=\"colorSecondaryVariant\">#80B65247</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew.Base\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSecondary\">#B5889B</item>\n        <item name=\"colorSecondaryVariant\">#80B5889B</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew\" parent=\"ThemeFoundationMD2.Mew.Base\">\n        <item name=\"colorPrimary\">#B3566C</item>\n        <item name=\"colorPrimaryVariant\">#80B3566C</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Salamence\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#70B2C6</item>\n        <item name=\"colorPrimaryVariant\">#8070B2C6</item>\n        <item name=\"colorSecondary\">#C06A75</item>\n        <item name=\"colorSecondaryVariant\">#80C06A75</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Fraxure\" parent=\"ThemeFoundationMD2.Amoled\">\n        <item name=\"colorPrimary\">#009688</item>\n        <item name=\"colorPrimaryVariant\">#8000796B</item>\n        <item name=\"colorSecondary\">?colorError</item>\n        <item name=\"colorSecondaryVariant\">#806D1111</item>\n    </style>\n\n    <!--3rd party themes-->\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values/themes_override.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"PrivateResource\">\n\n    <!-- Add android:colorControlNormal -->\n    <style name=\"Base.ThemeOverlay.AppCompat.ActionBar\" tools:override=\"true\">\n        <item name=\"android:colorControlNormal\">?android:attr/textColorPrimary</item>\n        <item name=\"colorControlNormal\">?android:attr/textColorPrimary</item>\n        <item name=\"searchViewStyle\">@style/Widget.AppCompat.SearchView.ActionBar</item>\n    </style>\n\n    <!-- Removes minimal size for MaterialComponents CheckBox and RadioButton,\n         so that they will not have extra space in menus.\n    -->\n    <style name=\"Widget.MaterialComponents.CompoundButton.CheckBox\" parent=\"Widget.AppCompat.CompoundButton.CheckBox\" tools:override=\"true\">\n        <item name=\"enforceMaterialTheme\">true</item>\n        <item name=\"useMaterialThemeColors\">true</item>\n    </style>\n\n    <style name=\"Widget.MaterialComponents.CompoundButton.RadioButton\" parent=\"Widget.AppCompat.CompoundButton.RadioButton\" tools:override=\"true\">\n        <item name=\"enforceMaterialTheme\">true</item>\n        <item name=\"useMaterialThemeColors\">true</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values-night/styles_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Foundation\" parent=\"Theme.Foundation\" />\n\n</resources>\n"
  },
  {
    "path": "app/apk/src/main/res/values-night/themes_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--1st party themes-->\n\n    <style name=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#4EAFF5</item>\n        <item name=\"colorPrimaryVariant\">#804EAFF5</item>\n        <item name=\"colorSecondary\">#3E78AF</item>\n        <item name=\"colorSecondaryVariant\">#803E78AF</item>\n        <item name=\"colorSurface\">#0D0D0D</item>\n        <item name=\"colorSurfaceVariant\">#171717</item>\n        <item name=\"colorSurfaceSurfaceVariant\">#292929</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n        <item name=\"colorOnSecondary\">#F9F9F9</item>\n        <item name=\"colorOnBackground\">?colorOnSurface</item>\n        <item name=\"colorError\">#EF8282</item>\n        <item name=\"colorOnError\">#0D0D0D</item>\n        <item name=\"colorOnSurface\">#D8D8D8</item>\n        <item name=\"colorOnSurfaceVariant\">#CCBABABA</item>\n        <item name=\"colorDisabled\">#808080</item>\n        <item name=\"colorDisabledVariant\">#66808080</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Amoled\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSurface\">#000</item>\n        <item name=\"colorOnPrimary\">#FFF</item>\n        <item name=\"colorOnSecondary\">#FFF</item>\n        <item name=\"colorOnBackground\">#000</item>\n        <item name=\"colorOnError\">#FFF</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos\" parent=\"ThemeFoundationMD2.Zapdos.Base\">\n        <item name=\"colorPrimary\">#FBD179</item>\n        <item name=\"colorPrimaryVariant\">#80FBD179</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew\" parent=\"ThemeFoundationMD2.Mew.Base\">\n        <item name=\"colorPrimary\">#D9ADB7</item>\n        <item name=\"colorPrimaryVariant\">#80E9BBC5</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <!--3rd party themes-->\n\n</resources>"
  },
  {
    "path": "app/apk/src/main/res/values-v27/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Base.V27.Theme.Foundation.Light\" parent=\"Base.V23.Theme.Foundation.Light\">\n        <item name=\"android:navigationBarColor\">#e0fafafa</item>\n        <item name=\"android:navigationBarDividerColor\">#1f000000</item>\n        <item name=\"android:windowLightNavigationBar\">true</item>\n    </style>\n\n    <style name=\"Base.V27.Theme.Foundation\" parent=\"Base.V23.Theme.Foundation\">\n        <item name=\"android:navigationBarDividerColor\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"Theme.Foundation.Light\" parent=\"Base.V27.Theme.Foundation.Light\" />\n\n    <style name=\"Theme.Foundation\" parent=\"Base.V27.Theme.Foundation\" />\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n    kotlin(\"plugin.parcelize\")\n    kotlin(\"plugin.compose\")\n    kotlin(\"plugin.serialization\")\n}\n\nsetupMainApk()\n\nandroid {\n    buildFeatures {\n        compose = true\n    }\n\n    compileOptions {\n        isCoreLibraryDesugaringEnabled = true\n    }\n\n    packaging {\n        jniLibs {\n            excludes += \"lib/*/libandroidx.graphics.path.so\"\n        }\n    }\n\n    defaultConfig {\n        proguardFile(\"proguard-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n        }\n    }\n}\n\ndependencies {\n    implementation(project(\":core\"))\n    coreLibraryDesugaring(libs.jdk.libs)\n\n    implementation(libs.appcompat)\n    implementation(libs.material)\n\n    // Compose\n    implementation(platform(libs.compose.bom))\n    implementation(libs.compose.ui)\n    implementation(libs.compose.ui.tooling.preview)\n    debugImplementation(libs.compose.ui.tooling)\n    implementation(libs.activity.compose)\n    implementation(libs.lifecycle.runtime.compose)\n    implementation(libs.lifecycle.viewmodel.compose)\n    implementation(libs.miuix)\n    implementation(libs.miuix.icons)\n    implementation(libs.miuix.navigation3.ui)\n\n    // Navigation3\n    implementation(libs.navigation3.runtime)\n    implementation(libs.navigationevent.compose)\n    implementation(libs.lifecycle.viewmodel.navigation3)\n\n}\n"
  },
  {
    "path": "app/apk-ng/proguard-rules.pro",
    "content": "# Excessive obfuscation\n-flattenpackagehierarchy\n-allowaccessmodification\n"
  },
  {
    "path": "app/apk-ng/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application android:localeConfig=\"@xml/locale_config\">\n        <activity\n            android:name=\".ui.MainActivity\"\n            android:exported=\"true\"\n            android:windowSoftInputMode=\"adjustResize\"\n            android:theme=\"@style/SplashTheme\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.APPLICATION_PREFERENCES\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n\n        <activity\n            android:name=\".ui.surequest.SuRequestActivity\"\n            android:directBootAware=\"true\"\n            android:exported=\"false\"\n            android:taskAffinity=\"\"\n            tools:ignore=\"AppLinkUrlError\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n                <category android:name=\"android.intent.category.DEFAULT\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/arch/AsyncLoadViewModel.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport androidx.annotation.MainThread\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\n\nabstract class AsyncLoadViewModel : BaseViewModel() {\n\n    private var loadingJob: Job? = null\n\n    @MainThread\n    fun startLoading() {\n        if (loadingJob?.isActive == true) {\n            return\n        }\n        loadingJob = viewModelScope.launch { doLoadWork() }\n    }\n\n    @MainThread\n    fun reload() {\n        loadingJob?.cancel()\n        loadingJob = viewModelScope.launch { doLoadWork() }\n    }\n\n    protected abstract suspend fun doLoadWork()\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/arch/BaseViewModel.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.annotation.StringRes\nimport androidx.lifecycle.ViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.SharedFlow\n\nabstract class BaseViewModel : ViewModel() {\n\n    private val _navEvents = MutableSharedFlow<Route>(extraBufferCapacity = 1)\n    val navEvents: SharedFlow<Route> = _navEvents\n\n    open fun onSaveState(state: Bundle) {}\n    open fun onRestoreState(state: Bundle) {}\n\n    fun showSnackbar(@StringRes resId: Int) {\n        AppContext.toast(resId, Toast.LENGTH_SHORT)\n    }\n\n    fun showSnackbar(msg: String) {\n        AppContext.toast(msg, Toast.LENGTH_SHORT)\n    }\n\n    fun navigateTo(route: Route) {\n        _navEvents.tryEmit(route)\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/arch/ViewModelFactory.kt",
    "content": "package com.topjohnwu.magisk.arch\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.ui.home.HomeViewModel\nimport com.topjohnwu.magisk.ui.install.InstallViewModel\nimport com.topjohnwu.magisk.ui.log.LogViewModel\nimport com.topjohnwu.magisk.ui.superuser.SuperuserViewModel\nimport com.topjohnwu.magisk.ui.surequest.SuRequestViewModel\n\nobject VMFactory : ViewModelProvider.Factory {\n    @Suppress(\"UNCHECKED_CAST\")\n    override fun <T : ViewModel> create(modelClass: Class<T>): T {\n        return when (modelClass) {\n            HomeViewModel::class.java -> HomeViewModel(ServiceLocator.networkService)\n            LogViewModel::class.java -> LogViewModel(ServiceLocator.logRepo)\n            SuperuserViewModel::class.java -> SuperuserViewModel(ServiceLocator.policyDB)\n            InstallViewModel::class.java ->\n                InstallViewModel(ServiceLocator.networkService)\n            SuRequestViewModel::class.java ->\n                SuRequestViewModel(ServiceLocator.policyDB, ServiceLocator.timeoutPrefs)\n            else -> modelClass.newInstance()\n        } as T\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/TerminalBuffer.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\nimport java.util.Arrays\n\n/**\n * A circular buffer of [TerminalRow]s which keeps notes about what is visible on a logical screen and the scroll\n * history.\n *\n * See [externalToInternalRow] for how to map from logical screen rows to array indices.\n */\nclass TerminalBuffer(columns: Int, totalRows: Int, screenRows: Int) {\n\n    var lines: Array<TerminalRow?>\n\n    /** The length of [lines]. */\n    var totalRows: Int = totalRows\n        private set\n\n    /** The number of rows and columns visible on the screen. */\n    var screenRows: Int = screenRows\n\n    var columns: Int = columns\n\n    /** The number of rows kept in history. */\n    var activeTranscriptRows: Int = 0\n        private set\n\n    /** The index in the circular buffer where the visible screen starts. */\n    private var screenFirstRow = 0\n\n    init {\n        lines = arrayOfNulls(totalRows)\n        blockSet(0, 0, columns, screenRows, ' '.code, TextStyle.NORMAL)\n    }\n\n    val transcriptText: String\n        get() = getSelectedText(0, -activeTranscriptRows, columns, screenRows).trim()\n\n    val transcriptTextWithoutJoinedLines: String\n        get() = getSelectedText(0, -activeTranscriptRows, columns, screenRows, false).trim()\n\n    val transcriptTextWithFullLinesJoined: String\n        get() = getSelectedText(0, -activeTranscriptRows, columns, screenRows, joinBackLines = true, joinFullLines = true).trim()\n\n    fun getSelectedText(selX1: Int, selY1: Int, selX2: Int, selY2: Int, joinBackLines: Boolean = true, joinFullLines: Boolean = false): String {\n        val builder = StringBuilder()\n\n        var y1 = selY1\n        var y2 = selY2\n        if (y1 < -activeTranscriptRows) y1 = -activeTranscriptRows\n        if (y2 >= screenRows) y2 = screenRows - 1\n\n        for (row in y1..y2) {\n            val x1 = if (row == y1) selX1 else 0\n            var x2: Int\n            if (row == y2) {\n                x2 = selX2 + 1\n                if (x2 > columns) x2 = columns\n            } else {\n                x2 = columns\n            }\n            val lineObject = lines[externalToInternalRow(row)]!!\n            val x1Index = lineObject.findStartOfColumn(x1)\n            var x2Index = if (x2 < columns) lineObject.findStartOfColumn(x2) else lineObject.spaceUsed\n            if (x2Index == x1Index) {\n                x2Index = lineObject.findStartOfColumn(x2 + 1)\n            }\n            val line = lineObject.text\n            var lastPrintingCharIndex = -1\n            val rowLineWrap = getLineWrap(row)\n            if (rowLineWrap && x2 == columns) {\n                lastPrintingCharIndex = x2Index - 1\n            } else {\n                for (i in x1Index until x2Index) {\n                    val c = line[i]\n                    if (c != ' ') lastPrintingCharIndex = i\n                }\n            }\n\n            val len = lastPrintingCharIndex - x1Index + 1\n            if (lastPrintingCharIndex != -1 && len > 0)\n                builder.append(line, x1Index, len)\n\n            val lineFillsWidth = lastPrintingCharIndex == x2Index - 1\n            if ((!joinBackLines || !rowLineWrap) && (!joinFullLines || !lineFillsWidth)\n                && row < y2 && row < screenRows - 1) builder.append('\\n')\n        }\n        return builder.toString()\n    }\n\n    fun getWordAtLocation(x: Int, y: Int): String {\n        var y1 = y\n        var y2 = y\n        while (y1 > 0 && !getSelectedText(0, y1 - 1, columns, y, joinBackLines = true, joinFullLines = true).contains(\"\\n\")) {\n            y1--\n        }\n        while (y2 < screenRows && !getSelectedText(0, y, columns, y2 + 1, joinBackLines = true, joinFullLines = true).contains(\"\\n\")) {\n            y2++\n        }\n\n        val text = getSelectedText(0, y1, columns, y2, joinBackLines = true, joinFullLines = true)\n        val textOffset = (y - y1) * columns + x\n\n        if (textOffset >= text.length) {\n            return \"\"\n        }\n\n        val x1 = text.lastIndexOf(' ', textOffset)\n        var x2 = text.indexOf(' ', textOffset)\n        if (x2 == -1) {\n            x2 = text.length\n        }\n\n        if (x1 == x2) {\n            return \"\"\n        }\n        return text.substring(x1 + 1, x2)\n    }\n\n    val activeRows: Int get() = activeTranscriptRows + screenRows\n\n    /**\n     * Convert a row value from the public external coordinate system to our internal private coordinate system.\n     *\n     * ```\n     * - External coordinate system: -activeTranscriptRows to screenRows-1, with the screen being 0..screenRows-1.\n     * - Internal coordinate system: the screenRows lines starting at screenFirstRow comprise the screen, while the\n     *   activeTranscriptRows lines ending at screenFirstRow-1 form the transcript (as a circular buffer).\n     *\n     * External <-> Internal:\n     *\n     * [ ...                            ]     [ ...                                     ]\n     * [ -activeTranscriptRows         ]     [ screenFirstRow - activeTranscriptRows ]\n     * [ ...                            ]     [ ...                                     ]\n     * [ 0 (visible screen starts here) ]  <->  [ screenFirstRow                         ]\n     * [ ...                            ]     [ ...                                     ]\n     * [ screenRows-1                  ]     [ screenFirstRow + screenRows-1         ]\n     * ```\n     *\n     * @param externalRow a row in the external coordinate system.\n     * @return The row corresponding to the input argument in the private coordinate system.\n     */\n    fun externalToInternalRow(externalRow: Int): Int {\n        if (externalRow < -activeTranscriptRows || externalRow > screenRows)\n            throw IllegalArgumentException(\"extRow=$externalRow, screenRows=$screenRows, activeTranscriptRows=$activeTranscriptRows\")\n        val internalRow = screenFirstRow + externalRow\n        return if (internalRow < 0) (totalRows + internalRow) else (internalRow % totalRows)\n    }\n\n    fun setLineWrap(row: Int) {\n        lines[externalToInternalRow(row)]!!.lineWrap = true\n    }\n\n    fun getLineWrap(row: Int): Boolean {\n        return lines[externalToInternalRow(row)]!!.lineWrap\n    }\n\n    fun clearLineWrap(row: Int) {\n        lines[externalToInternalRow(row)]!!.lineWrap = false\n    }\n\n    /**\n     * Resize the screen which this transcript backs. Currently, this only works if the number of columns does not\n     * change or the rows expand (that is, it only works when shrinking the number of rows).\n     *\n     * @param newColumns The number of columns the screen should have.\n     * @param newRows    The number of rows the screen should have.\n     * @param cursor     An int[2] containing the (column, row) cursor location.\n     */\n    fun resize(newColumns: Int, newRows: Int, newTotalRows: Int, cursor: IntArray, currentStyle: Long, altScreen: Boolean) {\n        // newRows > totalRows should not normally happen since totalRows is TRANSCRIPT_ROWS (10000):\n        if (newColumns == columns && newRows <= totalRows) {\n            // Fast resize where just the rows changed.\n            var shiftDownOfTopRow = screenRows - newRows\n            if (shiftDownOfTopRow > 0 && shiftDownOfTopRow < screenRows) {\n                // Shrinking. Check if we can skip blank rows at bottom below cursor.\n                for (i in screenRows - 1 downTo 1) {\n                    if (cursor[1] >= i) break\n                    val r = externalToInternalRow(i)\n                    if (lines[r] == null || lines[r]!!.isBlank()) {\n                        if (--shiftDownOfTopRow == 0) break\n                    }\n                }\n            } else if (shiftDownOfTopRow < 0) {\n                // Negative shift down = expanding. Only move screen up if there is transcript to show:\n                val actualShift = maxOf(shiftDownOfTopRow, -activeTranscriptRows)\n                if (shiftDownOfTopRow != actualShift) {\n                    for (i in 0 until actualShift - shiftDownOfTopRow)\n                        allocateFullLineIfNecessary((screenFirstRow + screenRows + i) % totalRows).clear(currentStyle)\n                    shiftDownOfTopRow = actualShift\n                }\n            }\n            screenFirstRow += shiftDownOfTopRow\n            screenFirstRow = if (screenFirstRow < 0) (screenFirstRow + totalRows) else (screenFirstRow % totalRows)\n            totalRows = newTotalRows\n            activeTranscriptRows = if (altScreen) 0 else maxOf(0, activeTranscriptRows + shiftDownOfTopRow)\n            cursor[1] -= shiftDownOfTopRow\n            screenRows = newRows\n        } else {\n            // Copy away old state and update new:\n            val oldLines = lines\n            lines = arrayOfNulls(newTotalRows)\n            for (i in 0 until newTotalRows)\n                lines[i] = TerminalRow(newColumns, currentStyle)\n\n            val oldActiveTranscriptRows = activeTranscriptRows\n            val oldScreenFirstRow = screenFirstRow\n            val oldScreenRows = screenRows\n            val oldTotalRows = totalRows\n            totalRows = newTotalRows\n            screenRows = newRows\n            activeTranscriptRows = 0\n            screenFirstRow = 0\n            columns = newColumns\n\n            var newCursorRow = -1\n            var newCursorColumn = -1\n            val oldCursorRow = cursor[1]\n            val oldCursorColumn = cursor[0]\n            var newCursorPlaced = false\n\n            var currentOutputExternalRow = 0\n            var currentOutputExternalColumn = 0\n\n            var skippedBlankLines = 0\n            for (externalOldRow in -oldActiveTranscriptRows until oldScreenRows) {\n                var internalOldRow = oldScreenFirstRow + externalOldRow\n                internalOldRow = if (internalOldRow < 0) (oldTotalRows + internalOldRow) else (internalOldRow % oldTotalRows)\n\n                val oldLine = oldLines[internalOldRow]\n                val cursorAtThisRow = externalOldRow == oldCursorRow\n                if (oldLine == null || (!(!newCursorPlaced && cursorAtThisRow)) && oldLine.isBlank()) {\n                    skippedBlankLines++\n                    continue\n                } else if (skippedBlankLines > 0) {\n                    for (i in 0 until skippedBlankLines) {\n                        if (currentOutputExternalRow == screenRows - 1) {\n                            scrollDownOneLine(0, screenRows, currentStyle)\n                        } else {\n                            currentOutputExternalRow++\n                        }\n                        currentOutputExternalColumn = 0\n                    }\n                    skippedBlankLines = 0\n                }\n\n                var lastNonSpaceIndex = 0\n                var justToCursor = false\n                if (cursorAtThisRow || oldLine.lineWrap) {\n                    lastNonSpaceIndex = oldLine.spaceUsed\n                    if (cursorAtThisRow) justToCursor = true\n                } else {\n                    for (i in 0 until oldLine.spaceUsed)\n                        // NEWLY INTRODUCED BUG! Should not index oldLine.styles with char indices\n                        if (oldLine.text[i] != ' '/* || oldLine.styles[i] != currentStyle */)\n                            lastNonSpaceIndex = i + 1\n                }\n\n                var currentOldCol = 0\n                var styleAtCol = 0L\n                var i = 0\n                while (i < lastNonSpaceIndex) {\n                    val c = oldLine.text[i]\n                    val codePoint: Int\n                    if (Character.isHighSurrogate(c)) {\n                        i++\n                        codePoint = Character.toCodePoint(c, oldLine.text[i])\n                    } else {\n                        codePoint = c.code\n                    }\n                    val displayWidth = WcWidth.width(codePoint)\n                    if (displayWidth > 0) styleAtCol = oldLine.getStyle(currentOldCol)\n\n                    if (currentOutputExternalColumn + displayWidth > columns) {\n                        setLineWrap(currentOutputExternalRow)\n                        if (currentOutputExternalRow == screenRows - 1) {\n                            if (newCursorPlaced) newCursorRow--\n                            scrollDownOneLine(0, screenRows, currentStyle)\n                        } else {\n                            currentOutputExternalRow++\n                        }\n                        currentOutputExternalColumn = 0\n                    }\n\n                    val offsetDueToCombiningChar = if (displayWidth <= 0 && currentOutputExternalColumn > 0) 1 else 0\n                    val outputColumn = currentOutputExternalColumn - offsetDueToCombiningChar\n                    setChar(outputColumn, currentOutputExternalRow, codePoint, styleAtCol)\n\n                    if (displayWidth > 0) {\n                        if (oldCursorRow == externalOldRow && oldCursorColumn == currentOldCol) {\n                            newCursorColumn = currentOutputExternalColumn\n                            newCursorRow = currentOutputExternalRow\n                            newCursorPlaced = true\n                        }\n                        currentOldCol += displayWidth\n                        currentOutputExternalColumn += displayWidth\n                        if (justToCursor && newCursorPlaced) break\n                    }\n                    i++\n                }\n                if (externalOldRow != (oldScreenRows - 1) && !oldLine.lineWrap) {\n                    if (currentOutputExternalRow == screenRows - 1) {\n                        if (newCursorPlaced) newCursorRow--\n                        scrollDownOneLine(0, screenRows, currentStyle)\n                    } else {\n                        currentOutputExternalRow++\n                    }\n                    currentOutputExternalColumn = 0\n                }\n            }\n\n            cursor[0] = newCursorColumn\n            cursor[1] = newCursorRow\n        }\n\n        // Handle cursor scrolling off screen:\n        if (cursor[0] < 0 || cursor[1] < 0) {\n            cursor[0] = 0\n            cursor[1] = 0\n        }\n    }\n\n    /**\n     * Block copy lines and associated metadata from one location to another in the circular buffer, taking wraparound\n     * into account.\n     *\n     * @param srcInternal The first line to be copied.\n     * @param len         The number of lines to be copied.\n     */\n    private fun blockCopyLinesDown(srcInternal: Int, len: Int) {\n        if (len == 0) return\n\n        val start = len - 1\n        val lineToBeOverWritten = lines[(srcInternal + start + 1) % totalRows]\n        for (i in start downTo 0)\n            lines[(srcInternal + i + 1) % totalRows] = lines[(srcInternal + i) % totalRows]\n        lines[srcInternal % totalRows] = lineToBeOverWritten\n    }\n\n    /**\n     * Scroll the screen down one line. To scroll the whole screen of a 24 line screen, the arguments would be (0, 24).\n     *\n     * @param topMargin    First line that is scrolled.\n     * @param bottomMargin One line after the last line that is scrolled.\n     * @param style        the style for the newly exposed line.\n     */\n    fun scrollDownOneLine(topMargin: Int, bottomMargin: Int, style: Long) {\n        if (topMargin > bottomMargin - 1 || topMargin < 0 || bottomMargin > screenRows)\n            throw IllegalArgumentException(\"topMargin=$topMargin, bottomMargin=$bottomMargin, screenRows=$screenRows\")\n\n        blockCopyLinesDown(screenFirstRow, topMargin)\n        blockCopyLinesDown(externalToInternalRow(bottomMargin), screenRows - bottomMargin)\n\n        screenFirstRow = (screenFirstRow + 1) % totalRows\n        if (activeTranscriptRows < totalRows - screenRows) activeTranscriptRows++\n\n        val blankRow = externalToInternalRow(bottomMargin - 1)\n        if (lines[blankRow] == null) {\n            lines[blankRow] = TerminalRow(columns, style)\n        } else {\n            lines[blankRow]!!.clear(style)\n        }\n    }\n\n    /**\n     * Block copy characters from one position in the screen to another. The two positions can overlap. All characters\n     * of the source and destination must be within the bounds of the screen, or else an InvalidParameterException will\n     * be thrown.\n     *\n     * @param sx source X coordinate\n     * @param sy source Y coordinate\n     * @param w  width\n     * @param h  height\n     * @param dx destination X coordinate\n     * @param dy destination Y coordinate\n     */\n    fun blockCopy(sx: Int, sy: Int, w: Int, h: Int, dx: Int, dy: Int) {\n        if (w == 0) return\n        if (sx < 0 || sx + w > columns || sy < 0 || sy + h > screenRows || dx < 0 || dx + w > columns || dy < 0 || dy + h > screenRows)\n            throw IllegalArgumentException()\n        val copyingUp = sy > dy\n        for (y in 0 until h) {\n            val y2 = if (copyingUp) y else (h - (y + 1))\n            val sourceRow = allocateFullLineIfNecessary(externalToInternalRow(sy + y2))\n            allocateFullLineIfNecessary(externalToInternalRow(dy + y2)).copyInterval(sourceRow, sx, sx + w, dx)\n        }\n    }\n\n    /**\n     * Block set characters. All characters must be within the bounds of the screen, or else an\n     * InvalidParameterException will be thrown. Typically this is called with a \"val\" argument of 32 to clear a block\n     * of characters.\n     */\n    fun blockSet(sx: Int, sy: Int, w: Int, h: Int, `val`: Int, style: Long) {\n        if (sx < 0 || sx + w > columns || sy < 0 || sy + h > screenRows) {\n            throw IllegalArgumentException(\n                \"Illegal arguments! blockSet($sx, $sy, $w, $h, $`val`, $columns, $screenRows)\")\n        }\n        for (y in 0 until h)\n            for (x in 0 until w)\n                setChar(sx + x, sy + y, `val`, style)\n    }\n\n    fun allocateFullLineIfNecessary(row: Int): TerminalRow {\n        return lines[row] ?: TerminalRow(columns, 0).also { lines[row] = it }\n    }\n\n    fun setChar(column: Int, row: Int, codePoint: Int, style: Long) {\n        if (row < 0 || row >= screenRows || column < 0 || column >= columns)\n            throw IllegalArgumentException(\"TerminalBuffer.setChar(): row=$row, column=$column, screenRows=$screenRows, columns=$columns\")\n        val internalRow = externalToInternalRow(row)\n        allocateFullLineIfNecessary(internalRow).setChar(column, codePoint, style)\n    }\n\n    fun getStyleAt(externalRow: Int, column: Int): Long {\n        return allocateFullLineIfNecessary(externalToInternalRow(externalRow)).getStyle(column)\n    }\n\n    /** Support for http://vt100.net/docs/vt510-rm/DECCARA and http://vt100.net/docs/vt510-rm/DECCARA */\n    fun setOrClearEffect(bits: Int, setOrClear: Boolean, reverse: Boolean, rectangular: Boolean, leftMargin: Int, rightMargin: Int, top: Int, left: Int,\n                         bottom: Int, right: Int) {\n        for (y in top until bottom) {\n            val line = lines[externalToInternalRow(y)]!!\n            val startOfLine = if (rectangular || y == top) left else leftMargin\n            val endOfLine = if (rectangular || y + 1 == bottom) right else rightMargin\n            for (x in startOfLine until endOfLine) {\n                val currentStyle = line.getStyle(x)\n                val foreColor = TextStyle.decodeForeColor(currentStyle)\n                val backColor = TextStyle.decodeBackColor(currentStyle)\n                var effect = TextStyle.decodeEffect(currentStyle)\n                if (reverse) {\n                    effect = (effect and bits.inv()) or (bits and effect.inv())\n                } else if (setOrClear) {\n                    effect = effect or bits\n                } else {\n                    effect = effect and bits.inv()\n                }\n                line.styles[x] = TextStyle.encode(foreColor, backColor, effect)\n            }\n        }\n    }\n\n    fun clearTranscript() {\n        if (screenFirstRow < activeTranscriptRows) {\n            Arrays.fill(lines, totalRows + screenFirstRow - activeTranscriptRows, totalRows, null)\n            Arrays.fill(lines, 0, screenFirstRow, null)\n        } else {\n            Arrays.fill(lines, screenFirstRow - activeTranscriptRows, screenFirstRow, null)\n        }\n        activeTranscriptRows = 0\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/TerminalEmulator.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\nimport android.util.Base64\nimport timber.log.Timber\nimport java.util.Stack\n\n/**\n * Renders text into a screen. Contains all the terminal-specific knowledge and state. Emulates a subset of the X Window\n * System xterm terminal, which in turn is an emulator for a subset of the Digital Equipment Corporation vt100 terminal.\n *\n * References:\n * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html\n * - http://en.wikipedia.org/wiki/ANSI_escape_code\n * - http://man.he.net/man4/console_codes\n * - http://bazaar.launchpad.net/~leonerd/libvterm/trunk/view/head:/src/state.c\n * - http://www.columbia.edu/~kermit/k95manual/iso2022.html\n * - http://www.vt100.net/docs/vt510-rm/chapter4\n * - http://en.wikipedia.org/wiki/ISO/IEC_2022 - for 7-bit and 8-bit GL GR explanation\n * - http://bjh21.me.uk/all-escapes/all-escapes.txt - extensive!\n * - http://woldlab.caltech.edu/~diane/kde4.10/workingdir/kubuntu/konsole/doc/developer/old-documents/VT100/techref.html\n */\nclass TerminalEmulator(\n    columns: Int,\n    rows: Int,\n    cellWidthPixels: Int,\n    cellHeightPixels: Int,\n    transcriptRows: Int?,\n) {\n\n    companion object {\n        private const val LOG_ESCAPE_SEQUENCES = false\n\n        /** Used for invalid data - http://en.wikipedia.org/wiki/Replacement_character#Replacement_character */\n        const val UNICODE_REPLACEMENT_CHAR = 0xFFFD\n\n        private const val ESC_NONE = 0\n        private const val ESC = 1\n        private const val ESC_POUND = 2\n        private const val ESC_SELECT_LEFT_PAREN = 3\n        private const val ESC_SELECT_RIGHT_PAREN = 4\n        private const val ESC_CSI = 6\n        private const val ESC_CSI_QUESTIONMARK = 7\n        private const val ESC_CSI_DOLLAR = 8\n        private const val ESC_PERCENT = 9\n        private const val ESC_OSC = 10\n        private const val ESC_OSC_ESC = 11\n        private const val ESC_CSI_BIGGERTHAN = 12\n        private const val ESC_P = 13\n        private const val ESC_CSI_QUESTIONMARK_ARG_DOLLAR = 14\n        private const val ESC_CSI_ARGS_SPACE = 15\n        private const val ESC_CSI_ARGS_ASTERIX = 16\n        private const val ESC_CSI_DOUBLE_QUOTE = 17\n        private const val ESC_CSI_SINGLE_QUOTE = 18\n        private const val ESC_CSI_EXCLAMATION = 19\n        private const val ESC_APC = 20\n        private const val ESC_APC_ESCAPE = 21\n        private const val ESC_CSI_UNSUPPORTED_PARAMETER_BYTE = 22\n        private const val ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE = 23\n\n        private const val MAX_ESCAPE_PARAMETERS = 32\n        private const val MAX_OSC_STRING_LENGTH = 8192\n\n        private const val DECSET_BIT_APPLICATION_CURSOR_KEYS = 1\n        private const val DECSET_BIT_REVERSE_VIDEO = 1 shl 1\n        private const val DECSET_BIT_ORIGIN_MODE = 1 shl 2\n        private const val DECSET_BIT_AUTOWRAP = 1 shl 3\n        private const val DECSET_BIT_CURSOR_ENABLED = 1 shl 4\n        private const val DECSET_BIT_APPLICATION_KEYPAD = 1 shl 5\n        private const val DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 shl 6\n        private const val DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT = 1 shl 7\n        private const val DECSET_BIT_SEND_FOCUS_EVENTS = 1 shl 8\n        private const val DECSET_BIT_MOUSE_PROTOCOL_SGR = 1 shl 9\n        private const val DECSET_BIT_BRACKETED_PASTE_MODE = 1 shl 10\n        private const val DECSET_BIT_LEFTRIGHT_MARGIN_MODE = 1 shl 11\n        private const val DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE = 1 shl 12\n\n        const val TERMINAL_TRANSCRIPT_ROWS_MIN = 100\n        const val TERMINAL_TRANSCRIPT_ROWS_MAX = 50000\n        const val DEFAULT_TERMINAL_TRANSCRIPT_ROWS = 2000\n\n        const val TERMINAL_CURSOR_STYLE_BLOCK = 0\n        const val TERMINAL_CURSOR_STYLE_UNDERLINE = 1\n        const val TERMINAL_CURSOR_STYLE_BAR = 2\n        const val DEFAULT_TERMINAL_CURSOR_STYLE = TERMINAL_CURSOR_STYLE_BLOCK\n\n        val TERMINAL_CURSOR_STYLES_LIST = intArrayOf(\n            TERMINAL_CURSOR_STYLE_BLOCK,\n            TERMINAL_CURSOR_STYLE_UNDERLINE,\n            TERMINAL_CURSOR_STYLE_BAR\n        )\n\n        private const val LOG_TAG = \"TerminalEmulator\"\n\n        fun mapDecSetBitToInternalBit(decsetBit: Int): Int = when (decsetBit) {\n            1 -> DECSET_BIT_APPLICATION_CURSOR_KEYS\n            5 -> DECSET_BIT_REVERSE_VIDEO\n            6 -> DECSET_BIT_ORIGIN_MODE\n            7 -> DECSET_BIT_AUTOWRAP\n            25 -> DECSET_BIT_CURSOR_ENABLED\n            66 -> DECSET_BIT_APPLICATION_KEYPAD\n            69 -> DECSET_BIT_LEFTRIGHT_MARGIN_MODE\n            1000 -> DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE\n            1002 -> DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT\n            1004 -> DECSET_BIT_SEND_FOCUS_EVENTS\n            1006 -> DECSET_BIT_MOUSE_PROTOCOL_SGR\n            2004 -> DECSET_BIT_BRACKETED_PASTE_MODE\n            else -> -1\n        }\n    }\n\n    var title: String? = null\n        private set\n    private val titleStack = Stack<String>()\n\n    var cursorRow = 0\n        private set\n    var cursorCol = 0\n        private set\n\n    var mRows: Int = rows\n    var mColumns: Int = columns\n\n    private var mCellWidthPixels: Int = cellWidthPixels\n    private var mCellHeightPixels: Int = cellHeightPixels\n\n    var cursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE\n        private set\n\n    private val mMainBuffer: TerminalBuffer\n    internal val mAltBuffer: TerminalBuffer\n    var screen: TerminalBuffer\n        private set\n\n    var onCopyToClipboard: ((String) -> Unit)? = null\n    var onScreenUpdate: (() -> Unit)? = null\n\n    private var mArgIndex = 0\n    private val mArgs = IntArray(MAX_ESCAPE_PARAMETERS)\n    private var mArgsSubParamsBitSet = 0\n\n    private val mOSCOrDeviceControlArgs = StringBuilder()\n\n    private var mContinueSequence = false\n    private var mEscapeState = ESC_NONE\n\n    private val mSavedStateMain = SavedScreenState()\n    private val mSavedStateAlt = SavedScreenState()\n\n    private var mUseLineDrawingG0 = false\n    private var mUseLineDrawingG1 = false\n    private var mUseLineDrawingUsesG0 = true\n\n    private var mCurrentDecSetFlags = 0\n    private var mSavedDecSetFlags = 0\n\n    private var mInsertMode = false\n    private var mTabStop: BooleanArray\n\n    private var mTopMargin = 0\n    private var mBottomMargin = 0\n    private var mLeftMargin = 0\n    private var mRightMargin = 0\n\n    private var mAboutToAutoWrap = false\n    private var mCursorBlinkingEnabled = false\n    private var mCursorBlinkState = false\n\n    private var mForeColor = 0\n    private var mBackColor = 0\n    private var mUnderlineColor = 0\n    private var mEffect = 0\n\n    var scrollCounter = 0\n        private set\n    var isAutoScrollDisabled = false\n        private set\n\n    private var mUtf8ToFollow = 0\n    private var mUtf8Index = 0\n    private val mUtf8InputBuffer = ByteArray(4)\n    private var mLastEmittedCodePoint = -1\n\n    val mColors = TerminalColors()\n\n    init {\n        mMainBuffer = TerminalBuffer(columns, getTerminalTranscriptRows(transcriptRows), rows)\n        mAltBuffer = TerminalBuffer(columns, rows, rows)\n        screen = mMainBuffer\n        mTabStop = BooleanArray(mColumns)\n        reset()\n    }\n\n    val isAlternateBufferActive: Boolean get() = screen === mAltBuffer\n\n    private fun getTerminalTranscriptRows(transcriptRows: Int?): Int {\n        return if (transcriptRows == null || transcriptRows < TERMINAL_TRANSCRIPT_ROWS_MIN || transcriptRows > TERMINAL_TRANSCRIPT_ROWS_MAX)\n            DEFAULT_TERMINAL_TRANSCRIPT_ROWS\n        else\n            transcriptRows\n    }\n\n    fun resize(columns: Int, rows: Int, cellWidthPixels: Int, cellHeightPixels: Int) {\n        this.mCellWidthPixels = cellWidthPixels\n        this.mCellHeightPixels = cellHeightPixels\n\n        if (mRows == rows && mColumns == columns) {\n            return\n        } else if (columns < 2 || rows < 2) {\n            throw IllegalArgumentException(\"rows=$rows, columns=$columns\")\n        }\n\n        if (mRows != rows) {\n            mRows = rows\n            mTopMargin = 0\n            mBottomMargin = mRows\n        }\n        if (mColumns != columns) {\n            val oldColumns = mColumns\n            mColumns = columns\n            val oldTabStop = mTabStop\n            mTabStop = BooleanArray(mColumns)\n            setDefaultTabStops()\n            val toTransfer = minOf(oldColumns, columns)\n            System.arraycopy(oldTabStop, 0, mTabStop, 0, toTransfer)\n            mLeftMargin = 0\n            mRightMargin = mColumns\n        }\n\n        resizeScreen()\n    }\n\n    private fun resizeScreen() {\n        val cursor = intArrayOf(cursorCol, cursorRow)\n        val newTotalRows = if (screen === mAltBuffer) mRows else mMainBuffer.totalRows\n        screen.resize(mColumns, mRows, newTotalRows, cursor, style, isAlternateBufferActive)\n        cursorCol = cursor[0]\n        cursorRow = cursor[1]\n    }\n\n\n    fun setCursorStyle() {\n        cursorStyle = DEFAULT_TERMINAL_CURSOR_STYLE\n    }\n\n    val isReverseVideo: Boolean get() = isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO)\n\n    val isCursorEnabled: Boolean get() = isDecsetInternalBitSet(DECSET_BIT_CURSOR_ENABLED)\n\n    fun shouldCursorBeVisible(): Boolean {\n        if (!isCursorEnabled) return false\n        return if (mCursorBlinkingEnabled) mCursorBlinkState else true\n    }\n\n    fun setCursorBlinkingEnabled(cursorBlinkingEnabled: Boolean) {\n        this.mCursorBlinkingEnabled = cursorBlinkingEnabled\n    }\n\n    fun setCursorBlinkState(cursorBlinkState: Boolean) {\n        this.mCursorBlinkState = cursorBlinkState\n    }\n\n    fun isKeypadApplicationMode(): Boolean = isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD)\n\n    private fun isDecsetInternalBitSet(bit: Int): Boolean = (mCurrentDecSetFlags and bit) != 0\n\n    private fun setDecsetinternalBit(internalBit: Int, set: Boolean) {\n        if (set) {\n            if (internalBit == DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE) {\n                setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT, false)\n            } else if (internalBit == DECSET_BIT_MOUSE_TRACKING_BUTTON_EVENT) {\n                setDecsetinternalBit(DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE, false)\n            }\n        }\n        if (set) {\n            mCurrentDecSetFlags = mCurrentDecSetFlags or internalBit\n        } else {\n            mCurrentDecSetFlags = mCurrentDecSetFlags and internalBit.inv()\n        }\n    }\n\n    private fun setDefaultTabStops() {\n        for (i in 0 until mColumns)\n            mTabStop[i] = (i and 7) == 0 && i != 0\n    }\n\n    fun append(buffer: ByteArray, length: Int) {\n        for (i in 0 until length)\n            processByte(buffer[i])\n    }\n\n    private fun processByte(byteToProcess: Byte) {\n        if (mUtf8ToFollow > 0) {\n            if ((byteToProcess.toInt() and 0b11000000) == 0b10000000) {\n                mUtf8InputBuffer[mUtf8Index] = byteToProcess\n                mUtf8Index++\n                if (--mUtf8ToFollow == 0) {\n                    val firstByteMask = when (mUtf8Index) {\n                        2 -> 0b00011111\n                        3 -> 0b00001111\n                        else -> 0b00000111\n                    }\n                    var codePoint = mUtf8InputBuffer[0].toInt() and firstByteMask\n                    for (i in 1 until mUtf8Index)\n                        codePoint = (codePoint shl 6) or (mUtf8InputBuffer[i].toInt() and 0b00111111)\n                    if (((codePoint <= 0b1111111) && mUtf8Index > 1) || (codePoint < 0b11111111111 && mUtf8Index > 2)\n                        || (codePoint < 0b1111111111111111 && mUtf8Index > 3)\n                    ) {\n                        codePoint = UNICODE_REPLACEMENT_CHAR\n                    }\n\n                    mUtf8Index = 0\n                    mUtf8ToFollow = 0\n\n                    if (codePoint in 0x80..0x9F) {\n                        // C1 control character, ignore\n                    } else {\n                        when (Character.getType(codePoint).toByte()) {\n                            Character.UNASSIGNED, Character.SURROGATE ->\n                                codePoint = UNICODE_REPLACEMENT_CHAR\n                        }\n                        processCodePoint(codePoint)\n                    }\n                }\n            } else {\n                mUtf8Index = 0\n                mUtf8ToFollow = 0\n                emitCodePoint(UNICODE_REPLACEMENT_CHAR)\n                processByte(byteToProcess)\n            }\n        } else {\n            if ((byteToProcess.toInt() and 0b10000000) == 0) {\n                processCodePoint(byteToProcess.toInt())\n                return\n            } else if ((byteToProcess.toInt() and 0b11100000) == 0b11000000) {\n                mUtf8ToFollow = 1\n            } else if ((byteToProcess.toInt() and 0b11110000) == 0b11100000) {\n                mUtf8ToFollow = 2\n            } else if ((byteToProcess.toInt() and 0b11111000) == 0b11110000) {\n                mUtf8ToFollow = 3\n            } else {\n                processCodePoint(UNICODE_REPLACEMENT_CHAR)\n                return\n            }\n            mUtf8InputBuffer[mUtf8Index] = byteToProcess\n            mUtf8Index++\n        }\n    }\n\n    fun processCodePoint(b: Int) {\n        if (mEscapeState == ESC_APC) {\n            doApc(b)\n            return\n        } else if (mEscapeState == ESC_APC_ESCAPE) {\n            doApcEscape(b)\n            return\n        }\n\n        when (b) {\n            0 -> { /* NUL, do nothing */ }\n            7 -> {\n                if (mEscapeState == ESC_OSC) doOsc(b)\n            }\n            8 -> {\n                if (mLeftMargin == cursorCol) {\n                    val previousRow = cursorRow - 1\n                    if (previousRow >= 0 && screen.getLineWrap(previousRow)) {\n                        screen.clearLineWrap(previousRow)\n                        setCursorRowCol(previousRow, mRightMargin - 1)\n                    }\n                } else {\n                    setCursorCol(cursorCol - 1)\n                }\n            }\n            9 -> cursorCol = nextTabStop(1)\n            10, 11, 12 -> doLinefeed()\n            13 -> setCursorCol(mLeftMargin)\n            14 -> mUseLineDrawingUsesG0 = false\n            15 -> mUseLineDrawingUsesG0 = true\n            24, 26 -> {\n                if (mEscapeState != ESC_NONE) {\n                    mEscapeState = ESC_NONE\n                    emitCodePoint(127)\n                }\n            }\n            27 -> {\n                if (mEscapeState == ESC_P) {\n                    return\n                } else if (mEscapeState != ESC_OSC) {\n                    startEscapeSequence()\n                } else {\n                    doOsc(b)\n                }\n            }\n            else -> {\n                mContinueSequence = false\n                when (mEscapeState) {\n                    ESC_NONE -> if (b >= 32) emitCodePoint(b)\n                    ESC -> doEsc(b)\n                    ESC_POUND -> doEscPound(b)\n                    ESC_SELECT_LEFT_PAREN -> mUseLineDrawingG0 = (b == '0'.code)\n                    ESC_SELECT_RIGHT_PAREN -> mUseLineDrawingG1 = (b == '0'.code)\n                    ESC_CSI -> doCsi(b)\n                    ESC_CSI_UNSUPPORTED_PARAMETER_BYTE, ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE ->\n                        doCsiUnsupportedParameterOrIntermediateByte(b)\n                    ESC_CSI_EXCLAMATION -> {\n                        if (b == 'p'.code) reset() else unknownSequence(b)\n                    }\n                    ESC_CSI_QUESTIONMARK -> doCsiQuestionMark(b)\n                    ESC_CSI_BIGGERTHAN -> doCsiBiggerThan(b)\n                    ESC_CSI_DOLLAR -> {\n                        val originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE)\n                        val effectiveTopMargin = if (originMode) mTopMargin else 0\n                        val effectiveBottomMargin = if (originMode) mBottomMargin else mRows\n                        val effectiveLeftMargin = if (originMode) mLeftMargin else 0\n                        val effectiveRightMargin = if (originMode) mRightMargin else mColumns\n                        when (b) {\n                            'v'.code -> {\n                                val topSource = minOf(getArg(0, 1, true) - 1 + effectiveTopMargin, mRows)\n                                val leftSource = minOf(getArg(1, 1, true) - 1 + effectiveLeftMargin, mColumns)\n                                val bottomSource = minOf(maxOf(getArg(2, mRows, true) + effectiveTopMargin, topSource), mRows)\n                                val rightSource = minOf(maxOf(getArg(3, mColumns, true) + effectiveLeftMargin, leftSource), mColumns)\n                                val destionationTop = minOf(getArg(5, 1, true) - 1 + effectiveTopMargin, mRows)\n                                val destinationLeft = minOf(getArg(6, 1, true) - 1 + effectiveLeftMargin, mColumns)\n                                val heightToCopy = minOf(mRows - destionationTop, bottomSource - topSource)\n                                val widthToCopy = minOf(mColumns - destinationLeft, rightSource - leftSource)\n                                screen.blockCopy(leftSource, topSource, widthToCopy, heightToCopy, destinationLeft, destionationTop)\n                            }\n                            '{'.code, 'x'.code, 'z'.code -> {\n                                val erase = b != 'x'.code\n                                val selective = b == '{'.code\n                                val keepVisualAttributes = erase && selective\n                                var argIndex = 0\n                                val fillChar = if (erase) ' '.code else getArg(argIndex++, -1, true)\n                                if ((fillChar in 32..126) || (fillChar in 160..255)) {\n                                    val top = minOf(getArg(argIndex++, 1, true) + effectiveTopMargin, effectiveBottomMargin + 1)\n                                    val left = minOf(getArg(argIndex++, 1, true) + effectiveLeftMargin, effectiveRightMargin + 1)\n                                    val bottom = minOf(getArg(argIndex++, mRows, true) + effectiveTopMargin, effectiveBottomMargin)\n                                    val right = minOf(getArg(argIndex, mColumns, true) + effectiveLeftMargin, effectiveRightMargin)\n                                    val style = style\n                                    for (row in top - 1 until bottom)\n                                        for (col in left - 1 until right)\n                                            if (!selective || (TextStyle.decodeEffect(screen.getStyleAt(row, col)) and TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)\n                                                screen.setChar(col, row, fillChar, if (keepVisualAttributes) screen.getStyleAt(row, col) else style)\n                                }\n                            }\n                            'r'.code, 't'.code -> {\n                                val reverse = b == 't'.code\n                                val top = minOf(getArg(0, 1, true) - 1, effectiveBottomMargin) + effectiveTopMargin\n                                val left = minOf(getArg(1, 1, true) - 1, effectiveRightMargin) + effectiveLeftMargin\n                                val bottom = minOf(getArg(2, mRows, true) + 1, effectiveBottomMargin - 1) + effectiveTopMargin\n                                val right = minOf(getArg(3, mColumns, true) + 1, effectiveRightMargin - 1) + effectiveLeftMargin\n                                if (mArgIndex >= 4) {\n                                    if (mArgIndex >= mArgs.size) mArgIndex = mArgs.size - 1\n                                    for (i in 4..mArgIndex) {\n                                        var bits = 0\n                                        var setOrClear = true\n                                        when (getArg(i, 0, false)) {\n                                            0 -> {\n                                                bits = (TextStyle.CHARACTER_ATTRIBUTE_BOLD or TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE or TextStyle.CHARACTER_ATTRIBUTE_BLINK\n                                                    or TextStyle.CHARACTER_ATTRIBUTE_INVERSE)\n                                                if (!reverse) setOrClear = false\n                                            }\n                                            1 -> bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD\n                                            4 -> bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE\n                                            5 -> bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK\n                                            7 -> bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE\n                                            22 -> {\n                                                bits = TextStyle.CHARACTER_ATTRIBUTE_BOLD\n                                                setOrClear = false\n                                            }\n                                            24 -> {\n                                                bits = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE\n                                                setOrClear = false\n                                            }\n                                            25 -> {\n                                                bits = TextStyle.CHARACTER_ATTRIBUTE_BLINK\n                                                setOrClear = false\n                                            }\n                                            27 -> {\n                                                bits = TextStyle.CHARACTER_ATTRIBUTE_INVERSE\n                                                setOrClear = false\n                                            }\n                                        }\n                                        if (reverse && !setOrClear) {\n                                            // Reverse attributes in rectangular area ignores non-(1,4,5,7) bits.\n                                        } else {\n                                            screen.setOrClearEffect(\n                                                bits, setOrClear, reverse, isDecsetInternalBitSet(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE),\n                                                effectiveLeftMargin, effectiveRightMargin, top, left, bottom, right\n                                            )\n                                        }\n                                    }\n                                }\n                            }\n                            else -> unknownSequence(b)\n                        }\n                    }\n                    ESC_CSI_DOUBLE_QUOTE -> {\n                        if (b == 'q'.code) {\n                            val arg = getArg0(0)\n                            if (arg == 0 || arg == 2) {\n                                mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_PROTECTED.inv()\n                            } else if (arg == 1) {\n                                mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_PROTECTED\n                            } else {\n                                unknownSequence(b)\n                            }\n                        } else {\n                            unknownSequence(b)\n                        }\n                    }\n                    ESC_CSI_SINGLE_QUOTE -> {\n                        when (b) {\n                            '}'.code -> {\n                                val columnsAfterCursor = mRightMargin - cursorCol\n                                val columnsToInsert = minOf(getArg0(1), columnsAfterCursor)\n                                val columnsToMove = columnsAfterCursor - columnsToInsert\n                                screen.blockCopy(cursorCol, 0, columnsToMove, mRows, cursorCol + columnsToInsert, 0)\n                                blockClear(cursorCol, 0, columnsToInsert, mRows)\n                            }\n                            '~'.code -> {\n                                val columnsAfterCursor = mRightMargin - cursorCol\n                                val columnsToDelete = minOf(getArg0(1), columnsAfterCursor)\n                                val columnsToMove = columnsAfterCursor - columnsToDelete\n                                screen.blockCopy(cursorCol + columnsToDelete, 0, columnsToMove, mRows, cursorCol, 0)\n                            }\n                            else -> unknownSequence(b)\n                        }\n                    }\n                    ESC_PERCENT -> { /* ignore */ }\n                    ESC_OSC -> doOsc(b)\n                    ESC_OSC_ESC -> doOscEsc(b)\n                    ESC_P -> doDeviceControl(b)\n                    ESC_CSI_QUESTIONMARK_ARG_DOLLAR -> {\n                        if (b != 'p'.code) unknownSequence(b)\n                    }\n                    ESC_CSI_ARGS_SPACE -> {\n                        val arg = getArg0(0)\n                        when (b) {\n                            'q'.code -> when (arg) {\n                                0, 1, 2 -> cursorStyle = TERMINAL_CURSOR_STYLE_BLOCK\n                                3, 4 -> cursorStyle = TERMINAL_CURSOR_STYLE_UNDERLINE\n                                5, 6 -> cursorStyle = TERMINAL_CURSOR_STYLE_BAR\n                            }\n                            't'.code, 'u'.code -> { /* Set margin-bell volume - ignore */ }\n                            else -> unknownSequence(b)\n                        }\n                    }\n                    ESC_CSI_ARGS_ASTERIX -> {\n                        val attributeChangeExtent = getArg0(0)\n                        if (b == 'x'.code && attributeChangeExtent in 0..2) {\n                            setDecsetinternalBit(DECSET_BIT_RECTANGULAR_CHANGEATTRIBUTE, attributeChangeExtent == 2)\n                        } else {\n                            unknownSequence(b)\n                        }\n                    }\n                    else -> unknownSequence(b)\n                }\n                if (!mContinueSequence) mEscapeState = ESC_NONE\n            }\n        }\n    }\n\n    private fun doDeviceControl(b: Int) {\n        when (b) {\n            '\\\\'.code -> {\n                val dcs = mOSCOrDeviceControlArgs.toString()\n                if (!dcs.startsWith(\"\\$q\") && !dcs.startsWith(\"+q\")) {\n                    if (LOG_ESCAPE_SEQUENCES)\n                        Timber.tag(LOG_TAG).e(\"Unrecognized device control string: $dcs\")\n                }\n                finishSequence()\n            }\n            else -> {\n                if (mOSCOrDeviceControlArgs.length > MAX_OSC_STRING_LENGTH) {\n                    mOSCOrDeviceControlArgs.clear()\n                    finishSequence()\n                } else {\n                    mOSCOrDeviceControlArgs.appendCodePoint(b)\n                    continueSequence(mEscapeState)\n                }\n            }\n        }\n    }\n\n    private fun doApc(b: Int) {\n        if (b == 27) {\n            continueSequence(ESC_APC_ESCAPE)\n        }\n    }\n\n    private fun doApcEscape(b: Int) {\n        if (b == '\\\\'.code) {\n            finishSequence()\n        } else {\n            continueSequence(ESC_APC)\n        }\n    }\n\n    private fun nextTabStop(numTabs: Int): Int {\n        var remaining = numTabs\n        for (i in cursorCol + 1 until mColumns)\n            if (mTabStop[i] && --remaining == 0) return minOf(i, mRightMargin)\n        return mRightMargin - 1\n    }\n\n    private fun doCsiUnsupportedParameterOrIntermediateByte(b: Int) {\n        if (mEscapeState == ESC_CSI_UNSUPPORTED_PARAMETER_BYTE && b in 0x30..0x3F) {\n            continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE)\n        } else if (b in 0x20..0x2F) {\n            continueSequence(ESC_CSI_UNSUPPORTED_INTERMEDIATE_BYTE)\n        } else if (b in 0x40..0x7E) {\n            finishSequence()\n        } else {\n            unknownSequence(b)\n        }\n    }\n\n    private fun doCsiQuestionMark(b: Int) {\n        when (b) {\n            'J'.code, 'K'.code -> {\n                mAboutToAutoWrap = false\n                val fillChar = ' '.code\n                var startCol = -1\n                var startRow = -1\n                var endCol = -1\n                var endRow = -1\n                val justRow = (b == 'K'.code)\n                when (getArg0(0)) {\n                    0 -> {\n                        startCol = cursorCol; startRow = cursorRow\n                        endCol = mColumns; endRow = if (justRow) cursorRow + 1 else mRows\n                    }\n                    1 -> {\n                        startCol = 0; startRow = if (justRow) cursorRow else 0\n                        endCol = cursorCol + 1; endRow = cursorRow + 1\n                    }\n                    2 -> {\n                        startCol = 0; startRow = if (justRow) cursorRow else 0\n                        endCol = mColumns; endRow = if (justRow) cursorRow + 1 else mRows\n                    }\n                    else -> unknownSequence(b)\n                }\n                val style = style\n                for (row in startRow until endRow) {\n                    for (col in startCol until endCol) {\n                        if ((TextStyle.decodeEffect(screen.getStyleAt(row, col)) and TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) == 0)\n                            screen.setChar(col, row, fillChar, style)\n                    }\n                }\n            }\n            'h'.code, 'l'.code -> {\n                if (mArgIndex >= mArgs.size) mArgIndex = mArgs.size - 1\n                for (i in 0..mArgIndex)\n                    doDecSetOrReset(b == 'h'.code, mArgs[i])\n            }\n            'n'.code -> {\n                finishSequence()\n                return\n            }\n            'r'.code, 's'.code -> {\n                if (mArgIndex >= mArgs.size) mArgIndex = mArgs.size - 1\n                for (i in 0..mArgIndex) {\n                    val externalBit = mArgs[i]\n                    val internalBit = mapDecSetBitToInternalBit(externalBit)\n                    if (internalBit == -1) {\n                        Timber.tag(LOG_TAG).w(\"Ignoring request to save/recall decset bit=$externalBit\")\n                    } else {\n                        if (b == 's'.code) {\n                            mSavedDecSetFlags = mSavedDecSetFlags or internalBit\n                        } else {\n                            doDecSetOrReset((mSavedDecSetFlags and internalBit) != 0, externalBit)\n                        }\n                    }\n                }\n            }\n            '$'.code -> {\n                continueSequence(ESC_CSI_QUESTIONMARK_ARG_DOLLAR)\n                return\n            }\n            else -> parseArg(b)\n        }\n    }\n\n    fun doDecSetOrReset(setting: Boolean, externalBit: Int) {\n        val internalBit = mapDecSetBitToInternalBit(externalBit)\n        if (internalBit != -1) {\n            setDecsetinternalBit(internalBit, setting)\n        }\n        when (externalBit) {\n            1 -> { /* Application Cursor Keys (DECCKM) */ }\n            3 -> {\n                mLeftMargin = 0\n                mTopMargin = 0\n                mBottomMargin = mRows\n                mRightMargin = mColumns\n                setDecsetinternalBit(DECSET_BIT_LEFTRIGHT_MARGIN_MODE, false)\n                blockClear(0, 0, mColumns, mRows)\n                setCursorRowCol(0, 0)\n            }\n            4 -> { /* DECSCLM-Scrolling Mode. Ignore */ }\n            5 -> { /* Reverse video. No action */ }\n            6 -> if (setting) setCursorPosition(0, 0)\n            7, 8, 9, 12, 25 -> { /* Cursor state change - ignored for read-only */ }\n            40, 45, 66 -> { /* Ignore */ }\n            69 -> {\n                if (!setting) {\n                    mLeftMargin = 0\n                    mRightMargin = mColumns\n                }\n            }\n            1000, 1001, 1002, 1003, 1004, 1005, 1006, 1015, 1034 -> { /* Ignore */ }\n            1048 -> if (setting) saveCursor() else restoreCursor()\n            47, 1047, 1049 -> {\n                val newScreen = if (setting) mAltBuffer else mMainBuffer\n                if (newScreen !== screen) {\n                    val resized = !(newScreen.columns == mColumns && newScreen.screenRows == mRows)\n                    if (setting) saveCursor()\n                    screen = newScreen\n                    if (!setting) {\n                        val col = mSavedStateMain.mSavedCursorCol\n                        val row = mSavedStateMain.mSavedCursorRow\n                        restoreCursor()\n                        if (resized) {\n                            cursorCol = col\n                            cursorRow = row\n                        }\n                    }\n                    if (resized) resizeScreen()\n                    if (newScreen === mAltBuffer)\n                        newScreen.blockSet(0, 0, mColumns, mRows, ' '.code, style)\n                }\n            }\n            2004 -> { /* Bracketed paste mode - setting bit is enough */ }\n            else -> unknownParameter(externalBit)\n        }\n    }\n\n    private fun doCsiBiggerThan(b: Int) {\n        when (b) {\n            'c'.code -> { /* Secondary device attributes - ignored for read-only */ }\n            'm'.code -> Timber.tag(LOG_TAG).e(\"(ignored) CSI > MODIFY RESOURCE: ${getArg0(-1)} to ${getArg1(-1)}\")\n            else -> parseArg(b)\n        }\n    }\n\n    private fun startEscapeSequence() {\n        mEscapeState = ESC\n        mArgIndex = 0\n        mArgs.fill(-1)\n        mArgsSubParamsBitSet = 0\n    }\n\n    private fun doLinefeed() {\n        val belowScrollingRegion = cursorRow >= mBottomMargin\n        var newCursorRow = cursorRow + 1\n        if (belowScrollingRegion) {\n            if (cursorRow != mRows - 1) {\n                setCursorRow(newCursorRow)\n            }\n        } else {\n            if (newCursorRow == mBottomMargin) {\n                scrollDownOneLine()\n                newCursorRow = mBottomMargin - 1\n            }\n            setCursorRow(newCursorRow)\n        }\n    }\n\n    private fun continueSequence(state: Int) {\n        mEscapeState = state\n        mContinueSequence = true\n    }\n\n    private fun doEscPound(b: Int) {\n        when (b) {\n            '8'.code -> screen.blockSet(0, 0, mColumns, mRows, 'E'.code, style)\n            else -> unknownSequence(b)\n        }\n    }\n\n    private fun doEsc(b: Int) {\n        when (b) {\n            '#'.code -> continueSequence(ESC_POUND)\n            '('.code -> continueSequence(ESC_SELECT_LEFT_PAREN)\n            ')'.code -> continueSequence(ESC_SELECT_RIGHT_PAREN)\n            '6'.code -> {\n                if (cursorCol > mLeftMargin) {\n                    cursorCol--\n                } else {\n                    val rows = mBottomMargin - mTopMargin\n                    screen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin + 1, mTopMargin)\n                    screen.blockSet(mLeftMargin, mTopMargin, 1, rows, ' '.code, TextStyle.encode(mForeColor, mBackColor, 0))\n                }\n            }\n            '7'.code -> saveCursor()\n            '8'.code -> restoreCursor()\n            '9'.code -> {\n                if (cursorCol < mRightMargin - 1) {\n                    cursorCol++\n                } else {\n                    val rows = mBottomMargin - mTopMargin\n                    screen.blockCopy(mLeftMargin + 1, mTopMargin, mRightMargin - mLeftMargin - 1, rows, mLeftMargin, mTopMargin)\n                    screen.blockSet(mRightMargin - 1, mTopMargin, 1, rows, ' '.code, TextStyle.encode(mForeColor, mBackColor, 0))\n                }\n            }\n            'c'.code -> {\n                reset()\n                mMainBuffer.clearTranscript()\n                blockClear(0, 0, mColumns, mRows)\n                setCursorPosition(0, 0)\n            }\n            'D'.code -> doLinefeed()\n            'E'.code -> {\n                setCursorCol(if (isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE)) mLeftMargin else 0)\n                doLinefeed()\n            }\n            'F'.code -> setCursorRowCol(0, mBottomMargin - 1)\n            'H'.code -> mTabStop[cursorCol] = true\n            'M'.code -> {\n                if (cursorRow <= mTopMargin) {\n                    screen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, mBottomMargin - (mTopMargin + 1), mLeftMargin, mTopMargin + 1)\n                    blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin)\n                } else {\n                    cursorRow--\n                }\n            }\n            'N'.code, '0'.code -> { /* SS2/SS3, ignore */ }\n            'P'.code -> {\n                mOSCOrDeviceControlArgs.clear()\n                continueSequence(ESC_P)\n            }\n            '['.code -> continueSequence(ESC_CSI)\n            '='.code -> setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, true)\n            ']'.code -> {\n                mOSCOrDeviceControlArgs.clear()\n                continueSequence(ESC_OSC)\n            }\n            '>'.code -> setDecsetinternalBit(DECSET_BIT_APPLICATION_KEYPAD, false)\n            '_'.code -> continueSequence(ESC_APC)\n            '%'.code -> continueSequence(ESC_PERCENT)\n            else -> unknownSequence(b)\n        }\n    }\n\n    private fun saveCursor() {\n        val state = if (screen === mMainBuffer) mSavedStateMain else mSavedStateAlt\n        state.mSavedCursorRow = cursorRow\n        state.mSavedCursorCol = cursorCol\n        state.mSavedEffect = mEffect\n        state.mSavedForeColor = mForeColor\n        state.mSavedBackColor = mBackColor\n        state.mSavedDecFlags = mCurrentDecSetFlags\n        state.mUseLineDrawingG0 = mUseLineDrawingG0\n        state.mUseLineDrawingG1 = mUseLineDrawingG1\n        state.mUseLineDrawingUsesG0 = mUseLineDrawingUsesG0\n    }\n\n    private fun restoreCursor() {\n        val state = if (screen === mMainBuffer) mSavedStateMain else mSavedStateAlt\n        setCursorRowCol(state.mSavedCursorRow, state.mSavedCursorCol)\n        mEffect = state.mSavedEffect\n        mForeColor = state.mSavedForeColor\n        mBackColor = state.mSavedBackColor\n        val mask = DECSET_BIT_AUTOWRAP or DECSET_BIT_ORIGIN_MODE\n        mCurrentDecSetFlags = (mCurrentDecSetFlags and mask.inv()) or (state.mSavedDecFlags and mask)\n        mUseLineDrawingG0 = state.mUseLineDrawingG0\n        mUseLineDrawingG1 = state.mUseLineDrawingG1\n        mUseLineDrawingUsesG0 = state.mUseLineDrawingUsesG0\n    }\n\n    private fun doCsi(b: Int) {\n        when (b) {\n            '!'.code -> continueSequence(ESC_CSI_EXCLAMATION)\n            '\"'.code -> continueSequence(ESC_CSI_DOUBLE_QUOTE)\n            '\\''.code -> continueSequence(ESC_CSI_SINGLE_QUOTE)\n            '$'.code -> continueSequence(ESC_CSI_DOLLAR)\n            '*'.code -> continueSequence(ESC_CSI_ARGS_ASTERIX)\n            '@'.code -> {\n                mAboutToAutoWrap = false\n                val columnsAfterCursor = mColumns - cursorCol\n                val spacesToInsert = minOf(getArg0(1), columnsAfterCursor)\n                val charsToMove = columnsAfterCursor - spacesToInsert\n                screen.blockCopy(cursorCol, cursorRow, charsToMove, 1, cursorCol + spacesToInsert, cursorRow)\n                blockClear(cursorCol, cursorRow, spacesToInsert)\n            }\n            'A'.code -> setCursorRow(maxOf(0, cursorRow - getArg0(1)))\n            'B'.code -> setCursorRow(minOf(mRows - 1, cursorRow + getArg0(1)))\n            'C'.code, 'a'.code -> setCursorCol(minOf(mRightMargin - 1, cursorCol + getArg0(1)))\n            'D'.code -> setCursorCol(maxOf(mLeftMargin, cursorCol - getArg0(1)))\n            'E'.code -> setCursorPosition(0, cursorRow + getArg0(1))\n            'F'.code -> setCursorPosition(0, cursorRow - getArg0(1))\n            'G'.code -> setCursorCol(minOf(maxOf(1, getArg0(1)), mColumns) - 1)\n            'H'.code, 'f'.code -> setCursorPosition(getArg1(1) - 1, getArg0(1) - 1)\n            'I'.code -> setCursorCol(nextTabStop(getArg0(1)))\n            'J'.code -> {\n                when (getArg0(0)) {\n                    0 -> {\n                        blockClear(cursorCol, cursorRow, mColumns - cursorCol)\n                        blockClear(0, cursorRow + 1, mColumns, mRows - (cursorRow + 1))\n                    }\n                    1 -> {\n                        blockClear(0, 0, mColumns, cursorRow)\n                        blockClear(0, cursorRow, cursorCol + 1)\n                    }\n                    2 -> blockClear(0, 0, mColumns, mRows)\n                    3 -> mMainBuffer.clearTranscript()\n                    else -> {\n                        unknownSequence(b)\n                        return\n                    }\n                }\n                mAboutToAutoWrap = false\n            }\n            'K'.code -> {\n                when (getArg0(0)) {\n                    0 -> blockClear(cursorCol, cursorRow, mColumns - cursorCol)\n                    1 -> blockClear(0, cursorRow, cursorCol + 1)\n                    2 -> blockClear(0, cursorRow, mColumns)\n                    else -> {\n                        unknownSequence(b)\n                        return\n                    }\n                }\n                mAboutToAutoWrap = false\n            }\n            'L'.code -> {\n                val linesAfterCursor = mBottomMargin - cursorRow\n                val linesToInsert = minOf(getArg0(1), linesAfterCursor)\n                val linesToMove = linesAfterCursor - linesToInsert\n                screen.blockCopy(0, cursorRow, mColumns, linesToMove, 0, cursorRow + linesToInsert)\n                blockClear(0, cursorRow, mColumns, linesToInsert)\n            }\n            'M'.code -> {\n                mAboutToAutoWrap = false\n                val linesAfterCursor = mBottomMargin - cursorRow\n                val linesToDelete = minOf(getArg0(1), linesAfterCursor)\n                val linesToMove = linesAfterCursor - linesToDelete\n                screen.blockCopy(0, cursorRow + linesToDelete, mColumns, linesToMove, 0, cursorRow)\n                blockClear(0, cursorRow + linesToMove, mColumns, linesToDelete)\n            }\n            'P'.code -> {\n                mAboutToAutoWrap = false\n                val cellsAfterCursor = mColumns - cursorCol\n                val cellsToDelete = minOf(getArg0(1), cellsAfterCursor)\n                val cellsToMove = cellsAfterCursor - cellsToDelete\n                screen.blockCopy(cursorCol + cellsToDelete, cursorRow, cellsToMove, 1, cursorCol, cursorRow)\n                blockClear(cursorCol + cellsToMove, cursorRow, cellsToDelete)\n            }\n            'S'.code -> {\n                val linesToScroll = getArg0(1)\n                for (i in 0 until linesToScroll)\n                    scrollDownOneLine()\n            }\n            'T'.code -> {\n                if (mArgIndex == 0) {\n                    val linesToScrollArg = getArg0(1)\n                    val linesBetweenTopAndBottomMargins = mBottomMargin - mTopMargin\n                    val linesToScroll = minOf(linesBetweenTopAndBottomMargins, linesToScrollArg)\n                    screen.blockCopy(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesBetweenTopAndBottomMargins - linesToScroll, mLeftMargin, mTopMargin + linesToScroll)\n                    blockClear(mLeftMargin, mTopMargin, mRightMargin - mLeftMargin, linesToScroll)\n                } else {\n                    unimplementedSequence(b)\n                }\n            }\n            'X'.code -> {\n                mAboutToAutoWrap = false\n                screen.blockSet(cursorCol, cursorRow, minOf(getArg0(1), mColumns - cursorCol), 1, ' '.code, style)\n            }\n            'Z'.code -> {\n                var numberOfTabs = getArg0(1)\n                var newCol = mLeftMargin\n                for (i in cursorCol - 1 downTo 0)\n                    if (mTabStop[i]) {\n                        if (--numberOfTabs == 0) {\n                            newCol = maxOf(i, mLeftMargin)\n                            break\n                        }\n                    }\n                cursorCol = newCol\n            }\n            '?'.code -> continueSequence(ESC_CSI_QUESTIONMARK)\n            '>'.code -> continueSequence(ESC_CSI_BIGGERTHAN)\n            '<'.code, '='.code -> continueSequence(ESC_CSI_UNSUPPORTED_PARAMETER_BYTE)\n            '`'.code -> setCursorColRespectingOriginMode(getArg0(1) - 1)\n            'b'.code -> {\n                if (mLastEmittedCodePoint == -1) return\n                val numRepeat = getArg0(1)\n                for (i in 0 until numRepeat) emitCodePoint(mLastEmittedCodePoint)\n            }\n            'c'.code -> { /* Primary device attributes - ignored for read-only */ }\n            'd'.code -> setCursorRow(minOf(maxOf(1, getArg0(1)), mRows) - 1)\n            'e'.code -> setCursorPosition(cursorCol, cursorRow + getArg0(1))\n            'g'.code -> {\n                when (getArg0(0)) {\n                    0 -> mTabStop[cursorCol] = false\n                    3 -> for (i in 0 until mColumns) mTabStop[i] = false\n                }\n            }\n            'h'.code -> doSetMode(true)\n            'l'.code -> doSetMode(false)\n            'm'.code -> selectGraphicRendition()\n            'n'.code -> { /* Device Status Report - ignored for read-only */ }\n            'r'.code -> {\n                mTopMargin = maxOf(0, minOf(getArg0(1) - 1, mRows - 2))\n                mBottomMargin = maxOf(mTopMargin + 2, minOf(getArg1(mRows), mRows))\n                setCursorPosition(0, 0)\n            }\n            's'.code -> {\n                if (isDecsetInternalBitSet(DECSET_BIT_LEFTRIGHT_MARGIN_MODE)) {\n                    mLeftMargin = minOf(getArg0(1) - 1, mColumns - 2)\n                    mRightMargin = maxOf(mLeftMargin + 1, minOf(getArg1(mColumns), mColumns))\n                    setCursorPosition(0, 0)\n                } else {\n                    saveCursor()\n                }\n            }\n            't'.code -> {\n                when (getArg0(0)) {\n                    22 -> {\n                        titleStack.push(title)\n                        if (titleStack.size > 20) {\n                            titleStack.removeAt(0)\n                        }\n                    }\n                    23 -> if (titleStack.isNotEmpty()) setTitle(titleStack.pop())\n                }\n            }\n            'u'.code -> restoreCursor()\n            ' '.code -> continueSequence(ESC_CSI_ARGS_SPACE)\n            else -> parseArg(b)\n        }\n    }\n\n    private fun selectGraphicRendition() {\n        if (mArgIndex >= mArgs.size) mArgIndex = mArgs.size - 1\n        var i = 0\n        while (i <= mArgIndex) {\n            if ((mArgsSubParamsBitSet and (1 shl i)) != 0) {\n                i++\n                continue\n            }\n\n            var code = getArg(i, 0, false)\n            if (code < 0) {\n                if (mArgIndex > 0) {\n                    i++\n                    continue\n                } else {\n                    code = 0\n                }\n            }\n            when {\n                code == 0 -> {\n                    mForeColor = TextStyle.COLOR_INDEX_FOREGROUND\n                    mBackColor = TextStyle.COLOR_INDEX_BACKGROUND\n                    mEffect = 0\n                }\n                code == 1 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_BOLD\n                code == 2 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_DIM\n                code == 3 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_ITALIC\n                code == 4 -> {\n                    if (i + 1 <= mArgIndex && ((mArgsSubParamsBitSet and (1 shl (i + 1))) != 0)) {\n                        i++\n                        if (mArgs[i] == 0) {\n                            mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE.inv()\n                        } else {\n                            mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE\n                        }\n                    } else {\n                        mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE\n                    }\n                }\n                code == 5 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_BLINK\n                code == 7 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_INVERSE\n                code == 8 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE\n                code == 9 -> mEffect = mEffect or TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH\n                code == 10 -> { /* Exit alt charset (TERM=linux) - ignore */ }\n                code == 11 -> { /* Enter alt charset (TERM=linux) - ignore */ }\n                code == 22 -> mEffect = mEffect and (TextStyle.CHARACTER_ATTRIBUTE_BOLD or TextStyle.CHARACTER_ATTRIBUTE_DIM).inv()\n                code == 23 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_ITALIC.inv()\n                code == 24 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE.inv()\n                code == 25 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_BLINK.inv()\n                code == 27 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_INVERSE.inv()\n                code == 28 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE.inv()\n                code == 29 -> mEffect = mEffect and TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH.inv()\n                code in 30..37 -> mForeColor = code - 30\n                code == 38 || code == 48 || code == 58 -> {\n                    if (i + 2 > mArgIndex) { i++; continue }\n                    val firstArg = mArgs[i + 1]\n                    if (firstArg == 2) {\n                        if (i + 4 > mArgIndex) {\n                            Timber.tag(LOG_TAG).w(\"Too few CSI${code};2 RGB arguments\")\n                        } else {\n                            val red = getArg(i + 2, 0, false)\n                            val green = getArg(i + 3, 0, false)\n                            val blue = getArg(i + 4, 0, false)\n                            if (red < 0 || green < 0 || blue < 0 || red > 255 || green > 255 || blue > 255) {\n                                finishSequenceAndLogError(\"Invalid RGB: $red,$green,$blue\")\n                            } else {\n                                val argbColor = 0xff000000.toInt() or (red shl 16) or (green shl 8) or blue\n                                when (code) {\n                                    38 -> mForeColor = argbColor\n                                    48 -> mBackColor = argbColor\n                                    58 -> mUnderlineColor = argbColor\n                                }\n                            }\n                            i += 4\n                        }\n                    } else if (firstArg == 5) {\n                        val color = getArg(i + 2, 0, false)\n                        i += 2\n                        if (color in 0 until TextStyle.NUM_INDEXED_COLORS) {\n                            when (code) {\n                                38 -> mForeColor = color\n                                48 -> mBackColor = color\n                                58 -> mUnderlineColor = color\n                            }\n                        } else {\n                            if (LOG_ESCAPE_SEQUENCES) Timber.tag(LOG_TAG).w(\"Invalid color index: $color\")\n                        }\n                    } else {\n                        finishSequenceAndLogError(\"Invalid ISO-8613-3 SGR first argument: $firstArg\")\n                    }\n                }\n                code == 39 -> mForeColor = TextStyle.COLOR_INDEX_FOREGROUND\n                code in 40..47 -> mBackColor = code - 40\n                code == 49 -> mBackColor = TextStyle.COLOR_INDEX_BACKGROUND\n                code == 59 -> mUnderlineColor = TextStyle.COLOR_INDEX_FOREGROUND\n                code in 90..97 -> mForeColor = code - 90 + 8\n                code in 100..107 -> mBackColor = code - 100 + 8\n                else -> {\n                    if (LOG_ESCAPE_SEQUENCES)\n                        Timber.tag(LOG_TAG).w(String.format(\"SGR unknown code %d\", code))\n                }\n            }\n            i++\n        }\n    }\n\n    private fun doOsc(b: Int) {\n        when (b) {\n            7 -> doOscSetTextParameters(\"\\u0007\")\n            27 -> continueSequence(ESC_OSC_ESC)\n            else -> collectOSCArgs(b)\n        }\n    }\n\n    private fun doOscEsc(b: Int) {\n        when (b) {\n            '\\\\'.code -> doOscSetTextParameters(\"\\u001b\\\\\")\n            else -> {\n                collectOSCArgs(27)\n                collectOSCArgs(b)\n                continueSequence(ESC_OSC)\n            }\n        }\n    }\n\n    private fun doOscSetTextParameters(bellOrStringTerminator: String) {\n        var value = -1\n        var textParameter = \"\"\n        for (idx in 0 until mOSCOrDeviceControlArgs.length) {\n            val b = mOSCOrDeviceControlArgs[idx]\n            if (b == ';') {\n                textParameter = mOSCOrDeviceControlArgs.substring(idx + 1)\n                break\n            } else if (b in '0'..'9') {\n                value = (if (value < 0) 0 else value * 10) + (b - '0')\n            } else {\n                unknownSequence(b.code)\n                return\n            }\n        }\n\n        when (value) {\n            0, 1, 2 -> setTitle(textParameter)\n            4 -> {\n                var colorIndex = -1\n                var parsingPairStart = -1\n                var i = 0\n                while (true) {\n                    val endOfInput = i == textParameter.length\n                    val ch = if (endOfInput) ';' else textParameter[i]\n                    if (ch == ';') {\n                        if (parsingPairStart < 0) {\n                            parsingPairStart = i + 1\n                        } else {\n                            if (colorIndex < 0 || colorIndex > 255) {\n                                unknownSequence(ch.code)\n                                return\n                            } else {\n                                mColors.tryParseColor(colorIndex, textParameter.substring(parsingPairStart, i))\n                                colorIndex = -1\n                                parsingPairStart = -1\n                            }\n                        }\n                    } else if (parsingPairStart >= 0) {\n                        // Passing through color spec\n                    } else if (parsingPairStart < 0 && ch in '0'..'9') {\n                        colorIndex = (if (colorIndex < 0) 0 else colorIndex * 10) + (ch - '0')\n                    } else {\n                        unknownSequence(ch.code)\n                        return\n                    }\n                    if (endOfInput) break\n                    i++\n                }\n            }\n            10, 11, 12 -> {\n                var specialIndex = TextStyle.COLOR_INDEX_FOREGROUND + (value - 10)\n                var lastSemiIndex = 0\n                var charIndex = 0\n                while (true) {\n                    val endOfInput = charIndex == textParameter.length\n                    if (endOfInput || textParameter[charIndex] == ';') {\n                        try {\n                            val colorSpec = textParameter.substring(lastSemiIndex, charIndex)\n                            if (\"?\" != colorSpec) {\n                                mColors.tryParseColor(specialIndex, colorSpec)\n                            }\n                            specialIndex++\n                            charIndex++\n                            if (endOfInput || specialIndex > TextStyle.COLOR_INDEX_CURSOR || charIndex >= textParameter.length)\n                                break\n                            lastSemiIndex = charIndex\n                        } catch (_: NumberFormatException) {\n                            // Ignore\n                        }\n                    }\n                    charIndex++\n                }\n            }\n            52 -> {\n                val startIndex = textParameter.indexOf(\";\") + 1\n                try {\n                    val clipboardText = String(Base64.decode(textParameter.substring(startIndex), 0), Charsets.UTF_8)\n                    onCopyToClipboard?.invoke(clipboardText)\n                } catch (_: Exception) {\n                    Timber.tag(LOG_TAG).e(\"OSC Manipulate selection, invalid string '$textParameter'\")\n                }\n            }\n            104 -> {\n                if (textParameter.isEmpty()) {\n                    mColors.reset()\n                } else {\n                    var lastIndex = 0\n                    var charIndex = 0\n                    while (true) {\n                        val endOfInput = charIndex == textParameter.length\n                        if (endOfInput || textParameter[charIndex] == ';') {\n                            try {\n                                val colorToReset = textParameter.substring(lastIndex, charIndex).toInt()\n                                mColors.reset(colorToReset)\n                                if (endOfInput) break\n                                charIndex++\n                                lastIndex = charIndex\n                            } catch (_: NumberFormatException) {\n                                // Ignore\n                            }\n                        }\n                        charIndex++\n                    }\n                }\n            }\n            110, 111, 112 -> {\n                mColors.reset(TextStyle.COLOR_INDEX_FOREGROUND + (value - 110))\n            }\n            119 -> { /* Reset highlight color - ignore */ }\n            else -> unknownParameter(value)\n        }\n        finishSequence()\n    }\n\n    private fun blockClear(sx: Int, sy: Int, w: Int) {\n        blockClear(sx, sy, w, 1)\n    }\n\n    private fun blockClear(sx: Int, sy: Int, w: Int, h: Int) {\n        screen.blockSet(sx, sy, w, h, ' '.code, style)\n    }\n\n    private val style: Long get() = TextStyle.encode(mForeColor, mBackColor, mEffect)\n\n    private fun doSetMode(newValue: Boolean) {\n        val modeBit = getArg0(0)\n        when (modeBit) {\n            4 -> mInsertMode = newValue\n            20 -> unknownParameter(modeBit)\n            34 -> { /* Normal cursor visibility - ignore */ }\n            else -> unknownParameter(modeBit)\n        }\n    }\n\n    private fun setCursorPosition(x: Int, y: Int) {\n        val originMode = isDecsetInternalBitSet(DECSET_BIT_ORIGIN_MODE)\n        val effectiveTopMargin = if (originMode) mTopMargin else 0\n        val effectiveBottomMargin = if (originMode) mBottomMargin else mRows\n        val effectiveLeftMargin = if (originMode) mLeftMargin else 0\n        val effectiveRightMargin = if (originMode) mRightMargin else mColumns\n        val newRow = maxOf(effectiveTopMargin, minOf(effectiveTopMargin + y, effectiveBottomMargin - 1))\n        val newCol = maxOf(effectiveLeftMargin, minOf(effectiveLeftMargin + x, effectiveRightMargin - 1))\n        setCursorRowCol(newRow, newCol)\n    }\n\n    private fun scrollDownOneLine() {\n        scrollCounter++\n        val currentStyle = style\n        if (mLeftMargin != 0 || mRightMargin != mColumns) {\n            screen.blockCopy(mLeftMargin, mTopMargin + 1, mRightMargin - mLeftMargin, mBottomMargin - mTopMargin - 1, mLeftMargin, mTopMargin)\n            screen.blockSet(mLeftMargin, mBottomMargin - 1, mRightMargin - mLeftMargin, 1, ' '.code, currentStyle)\n        } else {\n            screen.scrollDownOneLine(mTopMargin, mBottomMargin, currentStyle)\n        }\n    }\n\n    private fun parseArg(b: Int) {\n        if (b >= '0'.code && b <= '9'.code) {\n            if (mArgIndex < mArgs.size) {\n                val oldValue = mArgs[mArgIndex]\n                val thisDigit = b - '0'.code\n                val value: Int = if (oldValue >= 0) {\n                    oldValue * 10 + thisDigit\n                } else {\n                    thisDigit\n                }\n                mArgs[mArgIndex] = if (value > 9999) 9999 else value\n            }\n            continueSequence(mEscapeState)\n        } else if (b == ';'.code || b == ':'.code) {\n            if (mArgIndex + 1 < mArgs.size) {\n                mArgIndex++\n                if (b == ':'.code) {\n                    mArgsSubParamsBitSet = mArgsSubParamsBitSet or (1 shl mArgIndex)\n                }\n            } else {\n                logError(\"Too many parameters when in state: $mEscapeState\")\n            }\n            continueSequence(mEscapeState)\n        } else {\n            unknownSequence(b)\n        }\n    }\n\n    private fun getArg0(defaultValue: Int): Int = getArg(0, defaultValue, true)\n\n    private fun getArg1(defaultValue: Int): Int = getArg(1, defaultValue, true)\n\n    private fun getArg(index: Int, defaultValue: Int, treatZeroAsDefault: Boolean): Int {\n        var result = mArgs[index]\n        if (result < 0 || (result == 0 && treatZeroAsDefault)) {\n            result = defaultValue\n        }\n        return result\n    }\n\n    private fun collectOSCArgs(b: Int) {\n        if (mOSCOrDeviceControlArgs.length < MAX_OSC_STRING_LENGTH) {\n            mOSCOrDeviceControlArgs.appendCodePoint(b)\n            continueSequence(mEscapeState)\n        } else {\n            unknownSequence(b)\n        }\n    }\n\n    private fun unimplementedSequence(b: Int) {\n        logError(\"Unimplemented sequence char '${b.toChar()}' (U+${String.format(\"%04x\", b)})\")\n        finishSequence()\n    }\n\n    private fun unknownSequence(b: Int) {\n        logError(\"Unknown sequence char '${b.toChar()}' (numeric value=$b)\")\n        finishSequence()\n    }\n\n    private fun unknownParameter(parameter: Int) {\n        logError(\"Unknown parameter: $parameter\")\n        finishSequence()\n    }\n\n    private fun logError(errorType: String) {\n        if (LOG_ESCAPE_SEQUENCES) {\n            val buf = StringBuilder()\n            buf.append(errorType)\n            buf.append(\", escapeState=\")\n            buf.append(mEscapeState)\n            var firstArg = true\n            if (mArgIndex >= mArgs.size) mArgIndex = mArgs.size - 1\n            for (i in 0..mArgIndex) {\n                val value = mArgs[i]\n                if (value >= 0) {\n                    if (firstArg) {\n                        firstArg = false\n                        buf.append(\", args={\")\n                    } else {\n                        buf.append(',')\n                    }\n                    buf.append(value)\n                }\n            }\n            if (!firstArg) buf.append('}')\n            finishSequenceAndLogError(buf.toString())\n        }\n    }\n\n    private fun finishSequenceAndLogError(error: String) {\n        if (LOG_ESCAPE_SEQUENCES) Timber.tag(LOG_TAG).w(error)\n        finishSequence()\n    }\n\n    private fun finishSequence() {\n        mEscapeState = ESC_NONE\n    }\n\n    private fun emitCodePoint(codePoint: Int) {\n        var cp = codePoint\n        mLastEmittedCodePoint = cp\n        if (if (mUseLineDrawingUsesG0) mUseLineDrawingG0 else mUseLineDrawingG1) {\n            when (cp) {\n                '_'.code -> cp = ' '.code\n                '`'.code -> cp = '\\u25C6'.code // Diamond\n                '0'.code -> cp = '\\u2588'.code // Solid block\n                'a'.code -> cp = '\\u2592'.code // Checker board\n                'b'.code -> cp = '\\u2409'.code // Horizontal tab\n                'c'.code -> cp = '\\u240C'.code // Form feed\n                'd'.code -> cp = '\\r'.code     // Carriage return\n                'e'.code -> cp = '\\u240A'.code // Linefeed\n                'f'.code -> cp = '\\u00B0'.code // Degree\n                'g'.code -> cp = '\\u00B1'.code // Plus-minus\n                'h'.code -> cp = '\\n'.code     // Newline\n                'i'.code -> cp = '\\u240B'.code // Vertical tab\n                'j'.code -> cp = '\\u2518'.code // Lower right corner\n                'k'.code -> cp = '\\u2510'.code // Upper right corner\n                'l'.code -> cp = '\\u250C'.code // Upper left corner\n                'm'.code -> cp = '\\u2514'.code // Lower left corner\n                'n'.code -> cp = '\\u253C'.code // Crossing lines\n                'o'.code -> cp = '\\u23BA'.code // Horizontal line - scan 1\n                'p'.code -> cp = '\\u23BB'.code // Horizontal line - scan 3\n                'q'.code -> cp = '\\u2500'.code // Horizontal line - scan 5\n                'r'.code -> cp = '\\u23BC'.code // Horizontal line - scan 7\n                's'.code -> cp = '\\u23BD'.code // Horizontal line - scan 9\n                't'.code -> cp = '\\u251C'.code // T facing rightwards\n                'u'.code -> cp = '\\u2524'.code // T facing leftwards\n                'v'.code -> cp = '\\u2534'.code // T facing upwards\n                'w'.code -> cp = '\\u252C'.code // T facing downwards\n                'x'.code -> cp = '\\u2502'.code // Vertical line\n                'y'.code -> cp = '\\u2264'.code // Less than or equal to\n                'z'.code -> cp = '\\u2265'.code // Greater than or equal to\n                '{'.code -> cp = '\\u03C0'.code // Pi\n                '|'.code -> cp = '\\u2260'.code // Not equal to\n                '}'.code -> cp = '\\u00A3'.code // UK pound\n                '~'.code -> cp = '\\u00B7'.code // Centered dot\n            }\n        }\n\n        val autoWrap = isDecsetInternalBitSet(DECSET_BIT_AUTOWRAP)\n        val displayWidth = WcWidth.width(cp)\n        val cursorInLastColumn = cursorCol == mRightMargin - 1\n\n        if (autoWrap) {\n            if (cursorInLastColumn && ((mAboutToAutoWrap && displayWidth == 1) || displayWidth == 2)) {\n                screen.setLineWrap(cursorRow)\n                cursorCol = mLeftMargin\n                if (cursorRow + 1 < mBottomMargin) {\n                    cursorRow++\n                } else {\n                    scrollDownOneLine()\n                }\n            }\n        } else if (cursorInLastColumn && displayWidth == 2) {\n            return\n        }\n\n        if (mInsertMode && displayWidth > 0) {\n            val destCol = cursorCol + displayWidth\n            if (destCol < mRightMargin)\n                screen.blockCopy(cursorCol, cursorRow, mRightMargin - destCol, 1, destCol, cursorRow)\n        }\n\n        val offsetDueToCombiningChar = if (displayWidth <= 0 && cursorCol > 0 && !mAboutToAutoWrap) 1 else 0\n        var column = cursorCol - offsetDueToCombiningChar\n\n        if (column < 0) column = 0\n        screen.setChar(column, cursorRow, cp, style)\n\n        if (autoWrap && displayWidth > 0)\n            mAboutToAutoWrap = (cursorCol == mRightMargin - displayWidth)\n\n        cursorCol = minOf(cursorCol + displayWidth, mRightMargin - 1)\n    }\n\n    private fun setCursorRow(row: Int) {\n        cursorRow = row\n        mAboutToAutoWrap = false\n    }\n\n    private fun setCursorCol(col: Int) {\n        cursorCol = col\n        mAboutToAutoWrap = false\n    }\n\n    private fun setCursorColRespectingOriginMode(col: Int) {\n        setCursorPosition(col, cursorRow)\n    }\n\n    private fun setCursorRowCol(row: Int, col: Int) {\n        cursorRow = maxOf(0, minOf(row, mRows - 1))\n        cursorCol = maxOf(0, minOf(col, mColumns - 1))\n        mAboutToAutoWrap = false\n    }\n\n\n    fun clearScrollCounter() {\n        scrollCounter = 0\n    }\n\n\n    fun toggleAutoScrollDisabled() {\n        isAutoScrollDisabled = !isAutoScrollDisabled\n    }\n\n    fun reset() {\n        setCursorStyle()\n        mArgIndex = 0\n        mContinueSequence = false\n        mEscapeState = ESC_NONE\n        mInsertMode = false\n        mTopMargin = 0\n        mLeftMargin = 0\n        mBottomMargin = mRows\n        mRightMargin = mColumns\n        mAboutToAutoWrap = false\n        mForeColor = TextStyle.COLOR_INDEX_FOREGROUND\n        mSavedStateMain.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND\n        mSavedStateAlt.mSavedForeColor = TextStyle.COLOR_INDEX_FOREGROUND\n        mBackColor = TextStyle.COLOR_INDEX_BACKGROUND\n        mSavedStateMain.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND\n        mSavedStateAlt.mSavedBackColor = TextStyle.COLOR_INDEX_BACKGROUND\n        setDefaultTabStops()\n\n        mUseLineDrawingG0 = false\n        mUseLineDrawingG1 = false\n        mUseLineDrawingUsesG0 = true\n\n        mSavedStateMain.mSavedCursorRow = 0\n        mSavedStateMain.mSavedCursorCol = 0\n        mSavedStateMain.mSavedEffect = 0\n        mSavedStateMain.mSavedDecFlags = 0\n        mSavedStateAlt.mSavedCursorRow = 0\n        mSavedStateAlt.mSavedCursorCol = 0\n        mSavedStateAlt.mSavedEffect = 0\n        mSavedStateAlt.mSavedDecFlags = 0\n        mCurrentDecSetFlags = 0\n        setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true)\n        setDecsetinternalBit(DECSET_BIT_CURSOR_ENABLED, true)\n        mSavedDecSetFlags = mCurrentDecSetFlags\n        mSavedStateMain.mSavedDecFlags = mCurrentDecSetFlags\n        mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags\n\n        mUtf8Index = 0\n        mUtf8ToFollow = 0\n\n        mColors.reset()\n    }\n\n    fun getSelectedText(x1: Int, y1: Int, x2: Int, y2: Int): String =\n        screen.getSelectedText(x1, y1, x2, y2)\n\n\n    private fun setTitle(newTitle: String?) {\n        title = newTitle\n    }\n\n    internal class SavedScreenState {\n        var mSavedCursorRow = 0\n        var mSavedCursorCol = 0\n        var mSavedEffect = 0\n        var mSavedForeColor = 0\n        var mSavedBackColor = 0\n        var mSavedDecFlags = 0\n        var mUseLineDrawingG0 = false\n        var mUseLineDrawingG1 = false\n        var mUseLineDrawingUsesG0 = true\n    }\n\n    override fun toString(): String =\n        \"TerminalEmulator[size=${screen.columns}x${screen.screenRows}, margins={$mTopMargin,$mRightMargin,$mBottomMargin,$mLeftMargin}]\"\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/TerminalProcess.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\nimport android.os.Handler\nimport android.os.Looper\nimport com.topjohnwu.superuser.Shell\nimport timber.log.Timber\n\nprivate val busyboxPath: String by lazy {\n    Shell.cmd(\"readlink /proc/self/exe\").exec().out.firstOrNull()\n        ?: \"/data/adb/magisk/busybox\"\n}\n\nprivate val mainHandler = Handler(Looper.getMainLooper())\n\nfun TerminalEmulator.appendOnMain(bytes: ByteArray, len: Int) {\n    mainHandler.post {\n        append(bytes, len)\n        onScreenUpdate?.invoke()\n    }\n}\n\nfun TerminalEmulator.appendLineOnMain(line: String) {\n    val bytes = \"$line\\r\\n\".toByteArray(Charsets.UTF_8)\n    appendOnMain(bytes, bytes.size)\n}\n\n/**\n * Run a command as root inside a PTY (via busybox script).\n * Reads raw bytes from the process and feeds them to the terminal emulator.\n * Must be called from a background thread.\n * Returns true if the process exits with code 0.\n */\nfun runSuCommand(emulator: TerminalEmulator, command: String): Boolean {\n    return try {\n        val cols = emulator.mColumns\n        val rows = emulator.mRows\n        val wrappedCmd = \"export TERM=xterm-256color; stty cols $cols rows $rows 2>/dev/null; $command\"\n        val escapedCmd = wrappedCmd.replace(\"'\", \"'\\\\''\")\n\n        val process = ProcessBuilder(\n            \"su\", \"-c\",\n            \"$busyboxPath script -q -c '$escapedCmd' /dev/null\"\n        ).redirectErrorStream(true).start()\n\n        process.outputStream.close()\n\n        val buffer = ByteArray(4096)\n        process.inputStream.use { input ->\n            while (true) {\n                val n = input.read(buffer)\n                if (n == -1) break\n                emulator.appendOnMain(buffer.copyOf(n), n)\n            }\n        }\n\n        process.waitFor() == 0\n    } catch (e: Exception) {\n        Timber.e(e, \"runSuCommand failed\")\n        emulator.appendLineOnMain(\"! Error: ${e.message}\")\n        false\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/TerminalRow.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\nimport java.util.Arrays\n\n/**\n * A row in a terminal, composed of a fixed number of cells.\n *\n * The text in the row is stored in a char[] array, [text], for quick access during rendering.\n */\nclass TerminalRow(private val columns: Int, style: Long) {\n\n    /**\n     * Max combining characters that can exist in a column, that are separate from the base character\n     * itself. Any additional combining characters will be ignored and not added to the column.\n     *\n     * There does not seem to be limit in unicode standard for max number of combination characters\n     * that can be combined but such characters are primarily under 10.\n     *\n     * \"Section 3.6 Combination\" of unicode standard contains combining characters info.\n     * - https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf\n     * - https://en.wikipedia.org/wiki/Combining_character#Unicode_ranges\n     * - https://stackoverflow.com/questions/71237212/what-is-the-maximum-number-of-unicode-combined-characters-that-may-be-needed-to\n     *\n     * UAX15-D3 Stream-Safe Text Format limits to max 30 combining characters.\n     * > The value of 30 is chosen to be significantly beyond what is required for any linguistic or technical usage.\n     * > While it would have been feasible to chose a smaller number, this value provides a very wide margin,\n     * > yet is well within the buffer size limits of practical implementations.\n     * - https://unicode.org/reports/tr15/#Stream_Safe_Text_Format\n     * - https://stackoverflow.com/a/11983435/14686958\n     *\n     * We choose the value 15 because it should be enough for terminal based applications and keep\n     * the memory usage low for a terminal row, won't affect performance or cause terminal to\n     * lag or hang, and will keep malicious applications from causing harm. The value can be\n     * increased if ever needed for legitimate applications.\n     */\n    companion object {\n        private const val SPARE_CAPACITY_FACTOR = 1.5f\n        private const val MAX_COMBINING_CHARACTERS_PER_COLUMN = 15\n    }\n\n    /** The text filling this terminal row. */\n    var text: CharArray = CharArray((SPARE_CAPACITY_FACTOR * columns).toInt())\n\n    /** The number of java chars used in [text]. */\n    private var _spaceUsed: Short = 0\n\n    /** If this row has been line wrapped due to text output at the end of line. */\n    var lineWrap: Boolean = false\n\n    /** The style bits of each cell in the row. See [TextStyle]. */\n    val styles: LongArray = LongArray(columns)\n\n    /** If this row might contain chars with width != 1, used for deactivating fast path */\n    var hasNonOneWidthOrSurrogateChars: Boolean = false\n\n    init {\n        clear(style)\n    }\n\n    /** NOTE: The sourceX2 is exclusive. */\n    fun copyInterval(line: TerminalRow, sourceX1: Int, sourceX2: Int, destinationX: Int) {\n        hasNonOneWidthOrSurrogateChars = hasNonOneWidthOrSurrogateChars or line.hasNonOneWidthOrSurrogateChars\n        val x1 = line.findStartOfColumn(sourceX1)\n        val x2 = line.findStartOfColumn(sourceX2)\n        var startingFromSecondHalfOfWideChar = sourceX1 > 0 && line.wideDisplayCharacterStartingAt(sourceX1 - 1)\n        val sourceChars = if (this === line) line.text.copyOf() else line.text\n        var latestNonCombiningWidth = 0\n        var destX = destinationX\n        var srcX1 = sourceX1\n        var i = x1\n        while (i < x2) {\n            val sourceChar = sourceChars[i]\n            var codePoint: Int\n            if (Character.isHighSurrogate(sourceChar)) {\n                i++\n                codePoint = Character.toCodePoint(sourceChar, sourceChars[i])\n            } else {\n                codePoint = sourceChar.code\n            }\n            if (startingFromSecondHalfOfWideChar) {\n                codePoint = ' '.code\n                startingFromSecondHalfOfWideChar = false\n            }\n            val w = WcWidth.width(codePoint)\n            if (w > 0) {\n                destX += latestNonCombiningWidth\n                srcX1 += latestNonCombiningWidth\n                latestNonCombiningWidth = w\n            }\n            setChar(destX, codePoint, line.getStyle(srcX1))\n            i++\n        }\n    }\n\n    val spaceUsed: Int get() = _spaceUsed.toInt()\n\n    /** Note that the column may end of second half of wide character. */\n    fun findStartOfColumn(column: Int): Int {\n        if (column == columns) return spaceUsed\n\n        var currentColumn = 0\n        var currentCharIndex = 0\n        while (true) {\n            var newCharIndex = currentCharIndex\n            val c = text[newCharIndex++]\n            val isHigh = Character.isHighSurrogate(c)\n            val codePoint = if (isHigh) Character.toCodePoint(c, text[newCharIndex++]) else c.code\n            val wcwidth = WcWidth.width(codePoint)\n            if (wcwidth > 0) {\n                currentColumn += wcwidth\n                if (currentColumn == column) {\n                    while (newCharIndex < _spaceUsed) {\n                        if (Character.isHighSurrogate(text[newCharIndex])) {\n                            if (WcWidth.width(Character.toCodePoint(text[newCharIndex], text[newCharIndex + 1])) <= 0) {\n                                newCharIndex += 2\n                            } else {\n                                break\n                            }\n                        } else if (WcWidth.width(text[newCharIndex].code) <= 0) {\n                            newCharIndex++\n                        } else {\n                            break\n                        }\n                    }\n                    return newCharIndex\n                } else if (currentColumn > column) {\n                    return currentCharIndex\n                }\n            }\n            currentCharIndex = newCharIndex\n        }\n    }\n\n    private fun wideDisplayCharacterStartingAt(column: Int): Boolean {\n        var currentCharIndex = 0\n        var currentColumn = 0\n        while (currentCharIndex < _spaceUsed) {\n            val c = text[currentCharIndex++]\n            val codePoint = if (Character.isHighSurrogate(c)) Character.toCodePoint(c, text[currentCharIndex++]) else c.code\n            val wcwidth = WcWidth.width(codePoint)\n            if (wcwidth > 0) {\n                if (currentColumn == column && wcwidth == 2) return true\n                currentColumn += wcwidth\n                if (currentColumn > column) return false\n            }\n        }\n        return false\n    }\n\n    fun clear(style: Long) {\n        Arrays.fill(text, ' ')\n        Arrays.fill(styles, style)\n        _spaceUsed = columns.toShort()\n        hasNonOneWidthOrSurrogateChars = false\n    }\n\n    // https://github.com/steven676/Android-Terminal-Emulator/commit/9a47042620bec87617f0b4f5d50568535668fe26\n    fun setChar(columnToSet: Int, codePoint: Int, style: Long) {\n        if (columnToSet < 0 || columnToSet >= styles.size)\n            throw IllegalArgumentException(\"TerminalRow.setChar(): columnToSet=$columnToSet, codePoint=$codePoint, style=$style\")\n\n        styles[columnToSet] = style\n\n        val newCodePointDisplayWidth = WcWidth.width(codePoint)\n\n        // Fast path when we don't have any chars with width != 1\n        if (!hasNonOneWidthOrSurrogateChars) {\n            if (codePoint >= Character.MIN_SUPPLEMENTARY_CODE_POINT || newCodePointDisplayWidth != 1) {\n                hasNonOneWidthOrSurrogateChars = true\n            } else {\n                text[columnToSet] = codePoint.toChar()\n                return\n            }\n        }\n\n        val newIsCombining = newCodePointDisplayWidth <= 0\n\n        val wasExtraColForWideChar = columnToSet > 0 && wideDisplayCharacterStartingAt(columnToSet - 1)\n\n        var col = columnToSet\n        if (newIsCombining) {\n            if (wasExtraColForWideChar) col--\n        } else {\n            if (wasExtraColForWideChar) setChar(col - 1, ' '.code, style)\n            val overwritingWideCharInNextColumn = newCodePointDisplayWidth == 2 && wideDisplayCharacterStartingAt(col + 1)\n            if (overwritingWideCharInNextColumn) setChar(col + 1, ' '.code, style)\n        }\n\n        var textArray = text\n        val oldStartOfColumnIndex = findStartOfColumn(col)\n        val oldCodePointDisplayWidth = WcWidth.width(textArray, oldStartOfColumnIndex)\n\n        val oldCharactersUsedForColumn: Int\n        if (col + oldCodePointDisplayWidth < columns) {\n            val oldEndOfColumnIndex = findStartOfColumn(col + oldCodePointDisplayWidth)\n            oldCharactersUsedForColumn = oldEndOfColumnIndex - oldStartOfColumnIndex\n        } else {\n            oldCharactersUsedForColumn = _spaceUsed - oldStartOfColumnIndex\n        }\n\n        if (newIsCombining) {\n            val combiningCharsCount = WcWidth.zeroWidthCharsCount(textArray, oldStartOfColumnIndex, oldStartOfColumnIndex + oldCharactersUsedForColumn)\n            if (combiningCharsCount >= MAX_COMBINING_CHARACTERS_PER_COLUMN)\n                return\n        }\n\n        var newCharactersUsedForColumn = Character.charCount(codePoint)\n        if (newIsCombining) {\n            newCharactersUsedForColumn += oldCharactersUsedForColumn\n        }\n\n        val oldNextColumnIndex = oldStartOfColumnIndex + oldCharactersUsedForColumn\n        val newNextColumnIndex = oldStartOfColumnIndex + newCharactersUsedForColumn\n\n        val javaCharDifference = newCharactersUsedForColumn - oldCharactersUsedForColumn\n        if (javaCharDifference > 0) {\n            val oldCharactersAfterColumn = _spaceUsed - oldNextColumnIndex\n            if (_spaceUsed + javaCharDifference > textArray.size) {\n                val newText = CharArray(textArray.size + columns)\n                System.arraycopy(textArray, 0, newText, 0, oldNextColumnIndex)\n                System.arraycopy(textArray, oldNextColumnIndex, newText, newNextColumnIndex, oldCharactersAfterColumn)\n                text = newText\n                textArray = newText\n            } else {\n                System.arraycopy(textArray, oldNextColumnIndex, textArray, newNextColumnIndex, oldCharactersAfterColumn)\n            }\n        } else if (javaCharDifference < 0) {\n            System.arraycopy(textArray, oldNextColumnIndex, textArray, newNextColumnIndex, _spaceUsed - oldNextColumnIndex)\n        }\n        _spaceUsed = (_spaceUsed + javaCharDifference).toShort()\n\n        Character.toChars(codePoint, textArray, oldStartOfColumnIndex + if (newIsCombining) oldCharactersUsedForColumn else 0)\n\n        if (oldCodePointDisplayWidth == 2 && newCodePointDisplayWidth == 1) {\n            if (_spaceUsed + 1 > textArray.size) {\n                val newText = CharArray(textArray.size + columns)\n                System.arraycopy(textArray, 0, newText, 0, newNextColumnIndex)\n                System.arraycopy(textArray, newNextColumnIndex, newText, newNextColumnIndex + 1, _spaceUsed - newNextColumnIndex)\n                text = newText\n                textArray = newText\n            } else {\n                System.arraycopy(textArray, newNextColumnIndex, textArray, newNextColumnIndex + 1, _spaceUsed - newNextColumnIndex)\n            }\n            textArray[newNextColumnIndex] = ' '\n            ++_spaceUsed\n        } else if (oldCodePointDisplayWidth == 1 && newCodePointDisplayWidth == 2) {\n            if (col == columns - 1) {\n                throw IllegalArgumentException(\"Cannot put wide character in last column\")\n            } else if (col == columns - 2) {\n                _spaceUsed = newNextColumnIndex.toShort()\n            } else {\n                val newNextNextColumnIndex = newNextColumnIndex + if (Character.isHighSurrogate(textArray[newNextColumnIndex])) 2 else 1\n                val nextLen = newNextNextColumnIndex - newNextColumnIndex\n                System.arraycopy(textArray, newNextNextColumnIndex, textArray, newNextColumnIndex, _spaceUsed - newNextNextColumnIndex)\n                _spaceUsed = (_spaceUsed - nextLen).toShort()\n            }\n        }\n    }\n\n    internal fun isBlank(): Boolean {\n        for (charIndex in 0 until spaceUsed)\n            if (text[charIndex] != ' ') return false\n        return true\n    }\n\n    fun getStyle(column: Int): Long = styles[column]\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/TerminalStyle.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\nimport android.graphics.Color\nimport java.util.Properties\nimport kotlin.math.floor\nimport kotlin.math.pow\nimport kotlin.math.sqrt\n\nobject TextStyle {\n\n    const val CHARACTER_ATTRIBUTE_BOLD = 1\n    const val CHARACTER_ATTRIBUTE_ITALIC = 1 shl 1\n    const val CHARACTER_ATTRIBUTE_UNDERLINE = 1 shl 2\n    const val CHARACTER_ATTRIBUTE_BLINK = 1 shl 3\n    const val CHARACTER_ATTRIBUTE_INVERSE = 1 shl 4\n    const val CHARACTER_ATTRIBUTE_INVISIBLE = 1 shl 5\n    const val CHARACTER_ATTRIBUTE_STRIKETHROUGH = 1 shl 6\n    const val CHARACTER_ATTRIBUTE_PROTECTED = 1 shl 7\n    const val CHARACTER_ATTRIBUTE_DIM = 1 shl 8\n    private const val CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND = 1 shl 9\n    private const val CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND = 1 shl 10\n\n    const val COLOR_INDEX_FOREGROUND = 256\n    const val COLOR_INDEX_BACKGROUND = 257\n    const val COLOR_INDEX_CURSOR = 258\n    const val NUM_INDEXED_COLORS = 259\n\n    val NORMAL = encode(COLOR_INDEX_FOREGROUND, COLOR_INDEX_BACKGROUND, 0)\n\n    fun encode(foreColor: Int, backColor: Int, effect: Int): Long {\n        var result = (effect and 0b111111111).toLong()\n        if (foreColor and 0xff000000.toInt() == 0xff000000.toInt()) {\n            result = result or CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND.toLong() or ((foreColor.toLong() and 0x00ffffffL) shl 40)\n        } else {\n            result = result or ((foreColor.toLong() and 0b111111111L) shl 40)\n        }\n        if (backColor and 0xff000000.toInt() == 0xff000000.toInt()) {\n            result = result or CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND.toLong() or ((backColor.toLong() and 0x00ffffffL) shl 16)\n        } else {\n            result = result or ((backColor.toLong() and 0b111111111L) shl 16)\n        }\n        return result\n    }\n\n    fun decodeForeColor(style: Long): Int {\n        return if (style and CHARACTER_ATTRIBUTE_TRUECOLOR_FOREGROUND.toLong() == 0L) {\n            ((style ushr 40) and 0b111111111L).toInt()\n        } else {\n            0xff000000.toInt() or ((style ushr 40) and 0x00ffffffL).toInt()\n        }\n    }\n\n    fun decodeBackColor(style: Long): Int {\n        return if (style and CHARACTER_ATTRIBUTE_TRUECOLOR_BACKGROUND.toLong() == 0L) {\n            ((style ushr 16) and 0b111111111L).toInt()\n        } else {\n            0xff000000.toInt() or ((style ushr 16) and 0x00ffffffL).toInt()\n        }\n    }\n\n    fun decodeEffect(style: Long): Int {\n        return (style and 0b11111111111L).toInt()\n    }\n}\n\n/**\n * Color scheme for a terminal with default colors, which may be overridden (and then reset) from the shell using\n * Operating System Control (OSC) sequences.\n */\nclass TerminalColorScheme {\n\n    val defaultColors: IntArray = IntArray(TextStyle.NUM_INDEXED_COLORS)\n\n    init {\n        reset()\n    }\n\n    fun updateWith(props: Properties) {\n        reset()\n        var cursorPropExists = false\n        for ((keyObj, valueObj) in props) {\n            val key = keyObj as String\n            val value = valueObj as String\n            val colorIndex: Int = when {\n                key == \"foreground\" -> TextStyle.COLOR_INDEX_FOREGROUND\n                key == \"background\" -> TextStyle.COLOR_INDEX_BACKGROUND\n                key == \"cursor\" -> {\n                    cursorPropExists = true\n                    TextStyle.COLOR_INDEX_CURSOR\n                }\n                key.startsWith(\"color\") -> {\n                    try {\n                        key.substring(5).toInt()\n                    } catch (_: NumberFormatException) {\n                        throw IllegalArgumentException(\"Invalid property: '$key'\")\n                    }\n                }\n                else -> throw IllegalArgumentException(\"Invalid property: '$key'\")\n            }\n\n            val colorValue = TerminalColors.parse(value)\n            if (colorValue == 0) {\n                throw IllegalArgumentException(\"Property '$key' has invalid color: '$value'\")\n            }\n\n            defaultColors[colorIndex] = colorValue\n        }\n\n        if (!cursorPropExists) {\n            setCursorColorForBackground()\n        }\n    }\n\n    fun setCursorColorForBackground() {\n        val backgroundColor = defaultColors[TextStyle.COLOR_INDEX_BACKGROUND]\n        val brightness = TerminalColors.perceivedBrightness(backgroundColor)\n        if (brightness > 0) {\n            defaultColors[TextStyle.COLOR_INDEX_CURSOR] = if (brightness < 130) {\n                0xffffffff.toInt()\n            } else {\n                0xff000000.toInt()\n            }\n        }\n    }\n\n    private fun reset() {\n        System.arraycopy(DEFAULT_COLORSCHEME, 0, defaultColors, 0, TextStyle.NUM_INDEXED_COLORS)\n    }\n\n    companion object {\n        private val DEFAULT_COLORSCHEME = longArrayOf(\n            // 16 original colors. First 8 are dim.\n            0xff000000, // black\n            0xffcd0000, // dim red\n            0xff00cd00, // dim green\n            0xffcdcd00, // dim yellow\n            0xff6495ed, // dim blue\n            0xffcd00cd, // dim magenta\n            0xff00cdcd, // dim cyan\n            0xffe5e5e5, // dim white\n            // Second 8 are bright:\n            0xff7f7f7f, // medium grey\n            0xffff0000, // bright red\n            0xff00ff00, // bright green\n            0xffffff00, // bright yellow\n            0xff5c5cff, // light blue\n            0xffff00ff, // bright magenta\n            0xff00ffff, // bright cyan\n            0xffffffffL, // bright white\n\n            // 216 color cube, six shades of each color:\n            0xff000000, 0xff00005f, 0xff000087, 0xff0000af, 0xff0000d7, 0xff0000ff, 0xff005f00, 0xff005f5f, 0xff005f87, 0xff005faf, 0xff005fd7, 0xff005fff,\n            0xff008700, 0xff00875f, 0xff008787, 0xff0087af, 0xff0087d7, 0xff0087ff, 0xff00af00, 0xff00af5f, 0xff00af87, 0xff00afaf, 0xff00afd7, 0xff00afff,\n            0xff00d700, 0xff00d75f, 0xff00d787, 0xff00d7af, 0xff00d7d7, 0xff00d7ff, 0xff00ff00, 0xff00ff5f, 0xff00ff87, 0xff00ffaf, 0xff00ffd7, 0xff00ffff,\n            0xff5f0000, 0xff5f005f, 0xff5f0087, 0xff5f00af, 0xff5f00d7, 0xff5f00ff, 0xff5f5f00, 0xff5f5f5f, 0xff5f5f87, 0xff5f5faf, 0xff5f5fd7, 0xff5f5fff,\n            0xff5f8700, 0xff5f875f, 0xff5f8787, 0xff5f87af, 0xff5f87d7, 0xff5f87ff, 0xff5faf00, 0xff5faf5f, 0xff5faf87, 0xff5fafaf, 0xff5fafd7, 0xff5fafff,\n            0xff5fd700, 0xff5fd75f, 0xff5fd787, 0xff5fd7af, 0xff5fd7d7, 0xff5fd7ff, 0xff5fff00, 0xff5fff5f, 0xff5fff87, 0xff5fffaf, 0xff5fffd7, 0xff5fffff,\n            0xff870000, 0xff87005f, 0xff870087, 0xff8700af, 0xff8700d7, 0xff8700ff, 0xff875f00, 0xff875f5f, 0xff875f87, 0xff875faf, 0xff875fd7, 0xff875fff,\n            0xff878700, 0xff87875f, 0xff878787, 0xff8787af, 0xff8787d7, 0xff8787ff, 0xff87af00, 0xff87af5f, 0xff87af87, 0xff87afaf, 0xff87afd7, 0xff87afff,\n            0xff87d700, 0xff87d75f, 0xff87d787, 0xff87d7af, 0xff87d7d7, 0xff87d7ff, 0xff87ff00, 0xff87ff5f, 0xff87ff87, 0xff87ffaf, 0xff87ffd7, 0xff87ffff,\n            0xffaf0000, 0xffaf005f, 0xffaf0087, 0xffaf00af, 0xffaf00d7, 0xffaf00ff, 0xffaf5f00, 0xffaf5f5f, 0xffaf5f87, 0xffaf5faf, 0xffaf5fd7, 0xffaf5fff,\n            0xffaf8700, 0xffaf875f, 0xffaf8787, 0xffaf87af, 0xffaf87d7, 0xffaf87ff, 0xffafaf00, 0xffafaf5f, 0xffafaf87, 0xffafafaf, 0xffafafd7, 0xffafafff,\n            0xffafd700, 0xffafd75f, 0xffafd787, 0xffafd7af, 0xffafd7d7, 0xffafd7ff, 0xffafff00, 0xffafff5f, 0xffafff87, 0xffafffaf, 0xffafffd7, 0xffafffff,\n            0xffd70000, 0xffd7005f, 0xffd70087, 0xffd700af, 0xffd700d7, 0xffd700ff, 0xffd75f00, 0xffd75f5f, 0xffd75f87, 0xffd75faf, 0xffd75fd7, 0xffd75fff,\n            0xffd78700, 0xffd7875f, 0xffd78787, 0xffd787af, 0xffd787d7, 0xffd787ff, 0xffd7af00, 0xffd7af5f, 0xffd7af87, 0xffd7afaf, 0xffd7afd7, 0xffd7afff,\n            0xffd7d700, 0xffd7d75f, 0xffd7d787, 0xffd7d7af, 0xffd7d7d7, 0xffd7d7ff, 0xffd7ff00, 0xffd7ff5f, 0xffd7ff87, 0xffd7ffaf, 0xffd7ffd7, 0xffd7ffff,\n            0xffff0000, 0xffff005f, 0xffff0087, 0xffff00af, 0xffff00d7, 0xffff00ff, 0xffff5f00, 0xffff5f5f, 0xffff5f87, 0xffff5faf, 0xffff5fd7, 0xffff5fff,\n            0xffff8700, 0xffff875f, 0xffff8787, 0xffff87af, 0xffff87d7, 0xffff87ff, 0xffffaf00, 0xffffaf5f, 0xffffaf87, 0xffffafaf, 0xffffafd7, 0xffffafff,\n            0xffffd700, 0xffffd75f, 0xffffd787, 0xffffd7af, 0xffffd7d7, 0xffffd7ff, 0xffffff00, 0xffffff5f, 0xffffff87, 0xffffffaf, 0xffffffd7, 0xffffffffL,\n\n            // 24 grey scale ramp:\n            0xff080808, 0xff121212, 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff626262, 0xff6c6c6c, 0xff767676,\n            0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6, 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee,\n\n            // COLOR_INDEX_DEFAULT_FOREGROUND, COLOR_INDEX_DEFAULT_BACKGROUND and COLOR_INDEX_DEFAULT_CURSOR:\n            0xffffffffL, 0xff000000L, 0xffffffffL\n        ).map { it.toInt() }.toIntArray()\n    }\n}\n\n/** Current terminal colors (if different from default). */\nclass TerminalColors {\n\n    val currentColors: IntArray = IntArray(TextStyle.NUM_INDEXED_COLORS)\n\n    init {\n        reset()\n    }\n\n    fun reset(index: Int) {\n        currentColors[index] = COLOR_SCHEME.defaultColors[index]\n    }\n\n    fun reset() {\n        System.arraycopy(COLOR_SCHEME.defaultColors, 0, currentColors, 0, TextStyle.NUM_INDEXED_COLORS)\n    }\n\n    fun tryParseColor(intoIndex: Int, textParameter: String) {\n        val c = parse(textParameter)\n        if (c != 0) currentColors[intoIndex] = c\n    }\n\n    companion object {\n        val COLOR_SCHEME = TerminalColorScheme()\n\n        internal fun parse(c: String): Int {\n            return try {\n                val (skipInitial, skipBetween) = when {\n                    c[0] == '#' -> 1 to 0\n                    c.startsWith(\"rgb:\") -> 4 to 1\n                    else -> return 0\n                }\n                val charsForColors = c.length - skipInitial - 2 * skipBetween\n                if (charsForColors % 3 != 0) return 0\n                val componentLength = charsForColors / 3\n                val mult = 255.0 / (2.0.pow(componentLength * 4) - 1)\n\n                var currentPosition = skipInitial\n                val rString = c.substring(currentPosition, currentPosition + componentLength)\n                currentPosition += componentLength + skipBetween\n                val gString = c.substring(currentPosition, currentPosition + componentLength)\n                currentPosition += componentLength + skipBetween\n                val bString = c.substring(currentPosition, currentPosition + componentLength)\n\n                val r = (rString.toInt(16) * mult).toInt()\n                val g = (gString.toInt(16) * mult).toInt()\n                val b = (bString.toInt(16) * mult).toInt()\n                (0xFF shl 24) or (r shl 16) or (g shl 8) or b\n            } catch (_: NumberFormatException) {\n                0\n            } catch (_: IndexOutOfBoundsException) {\n                0\n            }\n        }\n\n        fun perceivedBrightness(color: Int): Int {\n            return floor(\n                sqrt(\n                    Color.red(color).toDouble().pow(2) * 0.241 +\n                        Color.green(color).toDouble().pow(2) * 0.691 +\n                        Color.blue(color).toDouble().pow(2) * 0.068\n                )\n            ).toInt()\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/terminal/WcWidth.kt",
    "content": "package com.topjohnwu.magisk.terminal\n\n/**\n * Implementation of wcwidth(3) for Unicode 15.\n *\n * Implementation from https://github.com/jquast/wcwidth but we return 0 for unprintable characters.\n *\n * IMPORTANT:\n * Must be kept in sync with the following:\n * https://github.com/termux/wcwidth\n * https://github.com/termux/libandroid-support\n * https://github.com/termux/termux-packages/tree/master/packages/libandroid-support\n */\nobject WcWidth {\n\n    // From https://github.com/jquast/wcwidth/blob/master/wcwidth/table_zero.py\n    // from https://github.com/jquast/wcwidth/pull/64\n    // at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):\n    private val ZERO_WIDTH = arrayOf(\n        intArrayOf(0x00300, 0x0036f),  // Combining Grave Accent  ..Combining Latin Small Le\n        intArrayOf(0x00483, 0x00489),  // Combining Cyrillic Titlo..Combining Cyrillic Milli\n        intArrayOf(0x00591, 0x005bd),  // Hebrew Accent Etnahta   ..Hebrew Point Meteg\n        intArrayOf(0x005bf, 0x005bf),  // Hebrew Point Rafe       ..Hebrew Point Rafe\n        intArrayOf(0x005c1, 0x005c2),  // Hebrew Point Shin Dot   ..Hebrew Point Sin Dot\n        intArrayOf(0x005c4, 0x005c5),  // Hebrew Mark Upper Dot   ..Hebrew Mark Lower Dot\n        intArrayOf(0x005c7, 0x005c7),  // Hebrew Point Qamats Qata..Hebrew Point Qamats Qata\n        intArrayOf(0x00610, 0x0061a),  // Arabic Sign Sallallahou ..Arabic Small Kasra\n        intArrayOf(0x0064b, 0x0065f),  // Arabic Fathatan         ..Arabic Wavy Hamza Below\n        intArrayOf(0x00670, 0x00670),  // Arabic Letter Superscrip..Arabic Letter Superscrip\n        intArrayOf(0x006d6, 0x006dc),  // Arabic Small High Ligatu..Arabic Small High Seen\n        intArrayOf(0x006df, 0x006e4),  // Arabic Small High Rounde..Arabic Small High Madda\n        intArrayOf(0x006e7, 0x006e8),  // Arabic Small High Yeh   ..Arabic Small High Noon\n        intArrayOf(0x006ea, 0x006ed),  // Arabic Empty Centre Low ..Arabic Small Low Meem\n        intArrayOf(0x00711, 0x00711),  // Syriac Letter Superscrip..Syriac Letter Superscrip\n        intArrayOf(0x00730, 0x0074a),  // Syriac Pthaha Above     ..Syriac Barrekh\n        intArrayOf(0x007a6, 0x007b0),  // Thaana Abafili          ..Thaana Sukun\n        intArrayOf(0x007eb, 0x007f3),  // Nko Combining Short High..Nko Combining Double Dot\n        intArrayOf(0x007fd, 0x007fd),  // Nko Dantayalan          ..Nko Dantayalan\n        intArrayOf(0x00816, 0x00819),  // Samaritan Mark In       ..Samaritan Mark Dagesh\n        intArrayOf(0x0081b, 0x00823),  // Samaritan Mark Epentheti..Samaritan Vowel Sign A\n        intArrayOf(0x00825, 0x00827),  // Samaritan Vowel Sign Sho..Samaritan Vowel Sign U\n        intArrayOf(0x00829, 0x0082d),  // Samaritan Vowel Sign Lon..Samaritan Mark Nequdaa\n        intArrayOf(0x00859, 0x0085b),  // Mandaic Affrication Mark..Mandaic Gemination Mark\n        intArrayOf(0x00898, 0x0089f),  // Arabic Small High Word A..Arabic Half Madda Over M\n        intArrayOf(0x008ca, 0x008e1),  // Arabic Small High Farsi ..Arabic Small High Sign S\n        intArrayOf(0x008e3, 0x00902),  // Arabic Turned Damma Belo..Devanagari Sign Anusvara\n        intArrayOf(0x0093a, 0x0093a),  // Devanagari Vowel Sign Oe..Devanagari Vowel Sign Oe\n        intArrayOf(0x0093c, 0x0093c),  // Devanagari Sign Nukta   ..Devanagari Sign Nukta\n        intArrayOf(0x00941, 0x00948),  // Devanagari Vowel Sign U ..Devanagari Vowel Sign Ai\n        intArrayOf(0x0094d, 0x0094d),  // Devanagari Sign Virama  ..Devanagari Sign Virama\n        intArrayOf(0x00951, 0x00957),  // Devanagari Stress Sign U..Devanagari Vowel Sign Uu\n        intArrayOf(0x00962, 0x00963),  // Devanagari Vowel Sign Vo..Devanagari Vowel Sign Vo\n        intArrayOf(0x00981, 0x00981),  // Bengali Sign Candrabindu..Bengali Sign Candrabindu\n        intArrayOf(0x009bc, 0x009bc),  // Bengali Sign Nukta      ..Bengali Sign Nukta\n        intArrayOf(0x009c1, 0x009c4),  // Bengali Vowel Sign U    ..Bengali Vowel Sign Vocal\n        intArrayOf(0x009cd, 0x009cd),  // Bengali Sign Virama     ..Bengali Sign Virama\n        intArrayOf(0x009e2, 0x009e3),  // Bengali Vowel Sign Vocal..Bengali Vowel Sign Vocal\n        intArrayOf(0x009fe, 0x009fe),  // Bengali Sandhi Mark     ..Bengali Sandhi Mark\n        intArrayOf(0x00a01, 0x00a02),  // Gurmukhi Sign Adak Bindi..Gurmukhi Sign Bindi\n        intArrayOf(0x00a3c, 0x00a3c),  // Gurmukhi Sign Nukta     ..Gurmukhi Sign Nukta\n        intArrayOf(0x00a41, 0x00a42),  // Gurmukhi Vowel Sign U   ..Gurmukhi Vowel Sign Uu\n        intArrayOf(0x00a47, 0x00a48),  // Gurmukhi Vowel Sign Ee  ..Gurmukhi Vowel Sign Ai\n        intArrayOf(0x00a4b, 0x00a4d),  // Gurmukhi Vowel Sign Oo  ..Gurmukhi Sign Virama\n        intArrayOf(0x00a51, 0x00a51),  // Gurmukhi Sign Udaat     ..Gurmukhi Sign Udaat\n        intArrayOf(0x00a70, 0x00a71),  // Gurmukhi Tippi          ..Gurmukhi Addak\n        intArrayOf(0x00a75, 0x00a75),  // Gurmukhi Sign Yakash    ..Gurmukhi Sign Yakash\n        intArrayOf(0x00a81, 0x00a82),  // Gujarati Sign Candrabind..Gujarati Sign Anusvara\n        intArrayOf(0x00abc, 0x00abc),  // Gujarati Sign Nukta     ..Gujarati Sign Nukta\n        intArrayOf(0x00ac1, 0x00ac5),  // Gujarati Vowel Sign U   ..Gujarati Vowel Sign Cand\n        intArrayOf(0x00ac7, 0x00ac8),  // Gujarati Vowel Sign E   ..Gujarati Vowel Sign Ai\n        intArrayOf(0x00acd, 0x00acd),  // Gujarati Sign Virama    ..Gujarati Sign Virama\n        intArrayOf(0x00ae2, 0x00ae3),  // Gujarati Vowel Sign Voca..Gujarati Vowel Sign Voca\n        intArrayOf(0x00afa, 0x00aff),  // Gujarati Sign Sukun     ..Gujarati Sign Two-circle\n        intArrayOf(0x00b01, 0x00b01),  // Oriya Sign Candrabindu  ..Oriya Sign Candrabindu\n        intArrayOf(0x00b3c, 0x00b3c),  // Oriya Sign Nukta        ..Oriya Sign Nukta\n        intArrayOf(0x00b3f, 0x00b3f),  // Oriya Vowel Sign I      ..Oriya Vowel Sign I\n        intArrayOf(0x00b41, 0x00b44),  // Oriya Vowel Sign U      ..Oriya Vowel Sign Vocalic\n        intArrayOf(0x00b4d, 0x00b4d),  // Oriya Sign Virama       ..Oriya Sign Virama\n        intArrayOf(0x00b55, 0x00b56),  // Oriya Sign Overline     ..Oriya Ai Length Mark\n        intArrayOf(0x00b62, 0x00b63),  // Oriya Vowel Sign Vocalic..Oriya Vowel Sign Vocalic\n        intArrayOf(0x00b82, 0x00b82),  // Tamil Sign Anusvara     ..Tamil Sign Anusvara\n        intArrayOf(0x00bc0, 0x00bc0),  // Tamil Vowel Sign Ii     ..Tamil Vowel Sign Ii\n        intArrayOf(0x00bcd, 0x00bcd),  // Tamil Sign Virama       ..Tamil Sign Virama\n        intArrayOf(0x00c00, 0x00c00),  // Telugu Sign Combining Ca..Telugu Sign Combining Ca\n        intArrayOf(0x00c04, 0x00c04),  // Telugu Sign Combining An..Telugu Sign Combining An\n        intArrayOf(0x00c3c, 0x00c3c),  // Telugu Sign Nukta       ..Telugu Sign Nukta\n        intArrayOf(0x00c3e, 0x00c40),  // Telugu Vowel Sign Aa    ..Telugu Vowel Sign Ii\n        intArrayOf(0x00c46, 0x00c48),  // Telugu Vowel Sign E     ..Telugu Vowel Sign Ai\n        intArrayOf(0x00c4a, 0x00c4d),  // Telugu Vowel Sign O     ..Telugu Sign Virama\n        intArrayOf(0x00c55, 0x00c56),  // Telugu Length Mark      ..Telugu Ai Length Mark\n        intArrayOf(0x00c62, 0x00c63),  // Telugu Vowel Sign Vocali..Telugu Vowel Sign Vocali\n        intArrayOf(0x00c81, 0x00c81),  // Kannada Sign Candrabindu..Kannada Sign Candrabindu\n        intArrayOf(0x00cbc, 0x00cbc),  // Kannada Sign Nukta      ..Kannada Sign Nukta\n        intArrayOf(0x00cbf, 0x00cbf),  // Kannada Vowel Sign I    ..Kannada Vowel Sign I\n        intArrayOf(0x00cc6, 0x00cc6),  // Kannada Vowel Sign E    ..Kannada Vowel Sign E\n        intArrayOf(0x00ccc, 0x00ccd),  // Kannada Vowel Sign Au   ..Kannada Sign Virama\n        intArrayOf(0x00ce2, 0x00ce3),  // Kannada Vowel Sign Vocal..Kannada Vowel Sign Vocal\n        intArrayOf(0x00d00, 0x00d01),  // Malayalam Sign Combining..Malayalam Sign Candrabin\n        intArrayOf(0x00d3b, 0x00d3c),  // Malayalam Sign Vertical ..Malayalam Sign Circular\n        intArrayOf(0x00d41, 0x00d44),  // Malayalam Vowel Sign U  ..Malayalam Vowel Sign Voc\n        intArrayOf(0x00d4d, 0x00d4d),  // Malayalam Sign Virama   ..Malayalam Sign Virama\n        intArrayOf(0x00d62, 0x00d63),  // Malayalam Vowel Sign Voc..Malayalam Vowel Sign Voc\n        intArrayOf(0x00d81, 0x00d81),  // Sinhala Sign Candrabindu..Sinhala Sign Candrabindu\n        intArrayOf(0x00dca, 0x00dca),  // Sinhala Sign Al-lakuna  ..Sinhala Sign Al-lakuna\n        intArrayOf(0x00dd2, 0x00dd4),  // Sinhala Vowel Sign Ketti..Sinhala Vowel Sign Ketti\n        intArrayOf(0x00dd6, 0x00dd6),  // Sinhala Vowel Sign Diga ..Sinhala Vowel Sign Diga\n        intArrayOf(0x00e31, 0x00e31),  // Thai Character Mai Han-a..Thai Character Mai Han-a\n        intArrayOf(0x00e34, 0x00e3a),  // Thai Character Sara I   ..Thai Character Phinthu\n        intArrayOf(0x00e47, 0x00e4e),  // Thai Character Maitaikhu..Thai Character Yamakkan\n        intArrayOf(0x00eb1, 0x00eb1),  // Lao Vowel Sign Mai Kan  ..Lao Vowel Sign Mai Kan\n        intArrayOf(0x00eb4, 0x00ebc),  // Lao Vowel Sign I        ..Lao Semivowel Sign Lo\n        intArrayOf(0x00ec8, 0x00ece),  // Lao Tone Mai Ek         ..(nil)\n        intArrayOf(0x00f18, 0x00f19),  // Tibetan Astrological Sig..Tibetan Astrological Sig\n        intArrayOf(0x00f35, 0x00f35),  // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung\n        intArrayOf(0x00f37, 0x00f37),  // Tibetan Mark Ngas Bzung ..Tibetan Mark Ngas Bzung\n        intArrayOf(0x00f39, 0x00f39),  // Tibetan Mark Tsa -phru  ..Tibetan Mark Tsa -phru\n        intArrayOf(0x00f71, 0x00f7e),  // Tibetan Vowel Sign Aa   ..Tibetan Sign Rjes Su Nga\n        intArrayOf(0x00f80, 0x00f84),  // Tibetan Vowel Sign Rever..Tibetan Mark Halanta\n        intArrayOf(0x00f86, 0x00f87),  // Tibetan Sign Lci Rtags  ..Tibetan Sign Yang Rtags\n        intArrayOf(0x00f8d, 0x00f97),  // Tibetan Subjoined Sign L..Tibetan Subjoined Letter\n        intArrayOf(0x00f99, 0x00fbc),  // Tibetan Subjoined Letter..Tibetan Subjoined Letter\n        intArrayOf(0x00fc6, 0x00fc6),  // Tibetan Symbol Padma Gda..Tibetan Symbol Padma Gda\n        intArrayOf(0x0102d, 0x01030),  // Myanmar Vowel Sign I    ..Myanmar Vowel Sign Uu\n        intArrayOf(0x01032, 0x01037),  // Myanmar Vowel Sign Ai   ..Myanmar Sign Dot Below\n        intArrayOf(0x01039, 0x0103a),  // Myanmar Sign Virama     ..Myanmar Sign Asat\n        intArrayOf(0x0103d, 0x0103e),  // Myanmar Consonant Sign M..Myanmar Consonant Sign M\n        intArrayOf(0x01058, 0x01059),  // Myanmar Vowel Sign Vocal..Myanmar Vowel Sign Vocal\n        intArrayOf(0x0105e, 0x01060),  // Myanmar Consonant Sign M..Myanmar Consonant Sign M\n        intArrayOf(0x01071, 0x01074),  // Myanmar Vowel Sign Geba ..Myanmar Vowel Sign Kayah\n        intArrayOf(0x01082, 0x01082),  // Myanmar Consonant Sign S..Myanmar Consonant Sign S\n        intArrayOf(0x01085, 0x01086),  // Myanmar Vowel Sign Shan ..Myanmar Vowel Sign Shan\n        intArrayOf(0x0108d, 0x0108d),  // Myanmar Sign Shan Counci..Myanmar Sign Shan Counci\n        intArrayOf(0x0109d, 0x0109d),  // Myanmar Vowel Sign Aiton..Myanmar Vowel Sign Aiton\n        intArrayOf(0x0135d, 0x0135f),  // Ethiopic Combining Gemin..Ethiopic Combining Gemin\n        intArrayOf(0x01712, 0x01714),  // Tagalog Vowel Sign I    ..Tagalog Sign Virama\n        intArrayOf(0x01732, 0x01733),  // Hanunoo Vowel Sign I    ..Hanunoo Vowel Sign U\n        intArrayOf(0x01752, 0x01753),  // Buhid Vowel Sign I      ..Buhid Vowel Sign U\n        intArrayOf(0x01772, 0x01773),  // Tagbanwa Vowel Sign I   ..Tagbanwa Vowel Sign U\n        intArrayOf(0x017b4, 0x017b5),  // Khmer Vowel Inherent Aq ..Khmer Vowel Inherent Aa\n        intArrayOf(0x017b7, 0x017bd),  // Khmer Vowel Sign I      ..Khmer Vowel Sign Ua\n        intArrayOf(0x017c6, 0x017c6),  // Khmer Sign Nikahit      ..Khmer Sign Nikahit\n        intArrayOf(0x017c9, 0x017d3),  // Khmer Sign Muusikatoan  ..Khmer Sign Bathamasat\n        intArrayOf(0x017dd, 0x017dd),  // Khmer Sign Atthacan     ..Khmer Sign Atthacan\n        intArrayOf(0x0180b, 0x0180d),  // Mongolian Free Variation..Mongolian Free Variation\n        intArrayOf(0x0180f, 0x0180f),  // Mongolian Free Variation..Mongolian Free Variation\n        intArrayOf(0x01885, 0x01886),  // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal\n        intArrayOf(0x018a9, 0x018a9),  // Mongolian Letter Ali Gal..Mongolian Letter Ali Gal\n        intArrayOf(0x01920, 0x01922),  // Limbu Vowel Sign A      ..Limbu Vowel Sign U\n        intArrayOf(0x01927, 0x01928),  // Limbu Vowel Sign E      ..Limbu Vowel Sign O\n        intArrayOf(0x01932, 0x01932),  // Limbu Small Letter Anusv..Limbu Small Letter Anusv\n        intArrayOf(0x01939, 0x0193b),  // Limbu Sign Mukphreng    ..Limbu Sign Sa-i\n        intArrayOf(0x01a17, 0x01a18),  // Buginese Vowel Sign I   ..Buginese Vowel Sign U\n        intArrayOf(0x01a1b, 0x01a1b),  // Buginese Vowel Sign Ae  ..Buginese Vowel Sign Ae\n        intArrayOf(0x01a56, 0x01a56),  // Tai Tham Consonant Sign ..Tai Tham Consonant Sign\n        intArrayOf(0x01a58, 0x01a5e),  // Tai Tham Sign Mai Kang L..Tai Tham Consonant Sign\n        intArrayOf(0x01a60, 0x01a60),  // Tai Tham Sign Sakot     ..Tai Tham Sign Sakot\n        intArrayOf(0x01a62, 0x01a62),  // Tai Tham Vowel Sign Mai ..Tai Tham Vowel Sign Mai\n        intArrayOf(0x01a65, 0x01a6c),  // Tai Tham Vowel Sign I   ..Tai Tham Vowel Sign Oa B\n        intArrayOf(0x01a73, 0x01a7c),  // Tai Tham Vowel Sign Oa A..Tai Tham Sign Khuen-lue\n        intArrayOf(0x01a7f, 0x01a7f),  // Tai Tham Combining Crypt..Tai Tham Combining Crypt\n        intArrayOf(0x01ab0, 0x01ace),  // Combining Doubled Circum..Combining Latin Small Le\n        intArrayOf(0x01b00, 0x01b03),  // Balinese Sign Ulu Ricem ..Balinese Sign Surang\n        intArrayOf(0x01b34, 0x01b34),  // Balinese Sign Rerekan   ..Balinese Sign Rerekan\n        intArrayOf(0x01b36, 0x01b3a),  // Balinese Vowel Sign Ulu ..Balinese Vowel Sign Ra R\n        intArrayOf(0x01b3c, 0x01b3c),  // Balinese Vowel Sign La L..Balinese Vowel Sign La L\n        intArrayOf(0x01b42, 0x01b42),  // Balinese Vowel Sign Pepe..Balinese Vowel Sign Pepe\n        intArrayOf(0x01b6b, 0x01b73),  // Balinese Musical Symbol ..Balinese Musical Symbol\n        intArrayOf(0x01b80, 0x01b81),  // Sundanese Sign Panyecek ..Sundanese Sign Panglayar\n        intArrayOf(0x01ba2, 0x01ba5),  // Sundanese Consonant Sign..Sundanese Vowel Sign Pan\n        intArrayOf(0x01ba8, 0x01ba9),  // Sundanese Vowel Sign Pam..Sundanese Vowel Sign Pan\n        intArrayOf(0x01bab, 0x01bad),  // Sundanese Sign Virama   ..Sundanese Consonant Sign\n        intArrayOf(0x01be6, 0x01be6),  // Batak Sign Tompi        ..Batak Sign Tompi\n        intArrayOf(0x01be8, 0x01be9),  // Batak Vowel Sign Pakpak ..Batak Vowel Sign Ee\n        intArrayOf(0x01bed, 0x01bed),  // Batak Vowel Sign Karo O ..Batak Vowel Sign Karo O\n        intArrayOf(0x01bef, 0x01bf1),  // Batak Vowel Sign U For S..Batak Consonant Sign H\n        intArrayOf(0x01c2c, 0x01c33),  // Lepcha Vowel Sign E     ..Lepcha Consonant Sign T\n        intArrayOf(0x01c36, 0x01c37),  // Lepcha Sign Ran         ..Lepcha Sign Nukta\n        intArrayOf(0x01cd0, 0x01cd2),  // Vedic Tone Karshana     ..Vedic Tone Prenkha\n        intArrayOf(0x01cd4, 0x01ce0),  // Vedic Sign Yajurvedic Mi..Vedic Tone Rigvedic Kash\n        intArrayOf(0x01ce2, 0x01ce8),  // Vedic Sign Visarga Svari..Vedic Sign Visarga Anuda\n        intArrayOf(0x01ced, 0x01ced),  // Vedic Sign Tiryak       ..Vedic Sign Tiryak\n        intArrayOf(0x01cf4, 0x01cf4),  // Vedic Tone Candra Above ..Vedic Tone Candra Above\n        intArrayOf(0x01cf8, 0x01cf9),  // Vedic Tone Ring Above   ..Vedic Tone Double Ring A\n        intArrayOf(0x01dc0, 0x01dff),  // Combining Dotted Grave A..Combining Right Arrowhea\n        intArrayOf(0x020d0, 0x020f0),  // Combining Left Harpoon A..Combining Asterisk Above\n        intArrayOf(0x02cef, 0x02cf1),  // Coptic Combining Ni Abov..Coptic Combining Spiritu\n        intArrayOf(0x02d7f, 0x02d7f),  // Tifinagh Consonant Joine..Tifinagh Consonant Joine\n        intArrayOf(0x02de0, 0x02dff),  // Combining Cyrillic Lette..Combining Cyrillic Lette\n        intArrayOf(0x0302a, 0x0302d),  // Ideographic Level Tone M..Ideographic Entering Ton\n        intArrayOf(0x03099, 0x0309a),  // Combining Katakana-hirag..Combining Katakana-hirag\n        intArrayOf(0x0a66f, 0x0a672),  // Combining Cyrillic Vzmet..Combining Cyrillic Thous\n        intArrayOf(0x0a674, 0x0a67d),  // Combining Cyrillic Lette..Combining Cyrillic Payer\n        intArrayOf(0x0a69e, 0x0a69f),  // Combining Cyrillic Lette..Combining Cyrillic Lette\n        intArrayOf(0x0a6f0, 0x0a6f1),  // Bamum Combining Mark Koq..Bamum Combining Mark Tuk\n        intArrayOf(0x0a802, 0x0a802),  // Syloti Nagri Sign Dvisva..Syloti Nagri Sign Dvisva\n        intArrayOf(0x0a806, 0x0a806),  // Syloti Nagri Sign Hasant..Syloti Nagri Sign Hasant\n        intArrayOf(0x0a80b, 0x0a80b),  // Syloti Nagri Sign Anusva..Syloti Nagri Sign Anusva\n        intArrayOf(0x0a825, 0x0a826),  // Syloti Nagri Vowel Sign ..Syloti Nagri Vowel Sign\n        intArrayOf(0x0a82c, 0x0a82c),  // Syloti Nagri Sign Altern..Syloti Nagri Sign Altern\n        intArrayOf(0x0a8c4, 0x0a8c5),  // Saurashtra Sign Virama  ..Saurashtra Sign Candrabi\n        intArrayOf(0x0a8e0, 0x0a8f1),  // Combining Devanagari Dig..Combining Devanagari Sig\n        intArrayOf(0x0a8ff, 0x0a8ff),  // Devanagari Vowel Sign Ay..Devanagari Vowel Sign Ay\n        intArrayOf(0x0a926, 0x0a92d),  // Kayah Li Vowel Ue       ..Kayah Li Tone Calya Plop\n        intArrayOf(0x0a947, 0x0a951),  // Rejang Vowel Sign I     ..Rejang Consonant Sign R\n        intArrayOf(0x0a980, 0x0a982),  // Javanese Sign Panyangga ..Javanese Sign Layar\n        intArrayOf(0x0a9b3, 0x0a9b3),  // Javanese Sign Cecak Telu..Javanese Sign Cecak Telu\n        intArrayOf(0x0a9b6, 0x0a9b9),  // Javanese Vowel Sign Wulu..Javanese Vowel Sign Suku\n        intArrayOf(0x0a9bc, 0x0a9bd),  // Javanese Vowel Sign Pepe..Javanese Consonant Sign\n        intArrayOf(0x0a9e5, 0x0a9e5),  // Myanmar Sign Shan Saw   ..Myanmar Sign Shan Saw\n        intArrayOf(0x0aa29, 0x0aa2e),  // Cham Vowel Sign Aa      ..Cham Vowel Sign Oe\n        intArrayOf(0x0aa31, 0x0aa32),  // Cham Vowel Sign Au      ..Cham Vowel Sign Ue\n        intArrayOf(0x0aa35, 0x0aa36),  // Cham Consonant Sign La  ..Cham Consonant Sign Wa\n        intArrayOf(0x0aa43, 0x0aa43),  // Cham Consonant Sign Fina..Cham Consonant Sign Fina\n        intArrayOf(0x0aa4c, 0x0aa4c),  // Cham Consonant Sign Fina..Cham Consonant Sign Fina\n        intArrayOf(0x0aa7c, 0x0aa7c),  // Myanmar Sign Tai Laing T..Myanmar Sign Tai Laing T\n        intArrayOf(0x0aab0, 0x0aab0),  // Tai Viet Mai Kang       ..Tai Viet Mai Kang\n        intArrayOf(0x0aab2, 0x0aab4),  // Tai Viet Vowel I        ..Tai Viet Vowel U\n        intArrayOf(0x0aab7, 0x0aab8),  // Tai Viet Mai Khit       ..Tai Viet Vowel Ia\n        intArrayOf(0x0aabe, 0x0aabf),  // Tai Viet Vowel Am       ..Tai Viet Tone Mai Ek\n        intArrayOf(0x0aac1, 0x0aac1),  // Tai Viet Tone Mai Tho   ..Tai Viet Tone Mai Tho\n        intArrayOf(0x0aaec, 0x0aaed),  // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign\n        intArrayOf(0x0aaf6, 0x0aaf6),  // Meetei Mayek Virama     ..Meetei Mayek Virama\n        intArrayOf(0x0abe5, 0x0abe5),  // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign\n        intArrayOf(0x0abe8, 0x0abe8),  // Meetei Mayek Vowel Sign ..Meetei Mayek Vowel Sign\n        intArrayOf(0x0abed, 0x0abed),  // Meetei Mayek Apun Iyek  ..Meetei Mayek Apun Iyek\n        intArrayOf(0x0fb1e, 0x0fb1e),  // Hebrew Point Judeo-spani..Hebrew Point Judeo-spani\n        intArrayOf(0x0fe00, 0x0fe0f),  // Variation Selector-1    ..Variation Selector-16\n        intArrayOf(0x0fe20, 0x0fe2f),  // Combining Ligature Left ..Combining Cyrillic Titlo\n        intArrayOf(0x101fd, 0x101fd),  // Phaistos Disc Sign Combi..Phaistos Disc Sign Combi\n        intArrayOf(0x102e0, 0x102e0),  // Coptic Epact Thousands M..Coptic Epact Thousands M\n        intArrayOf(0x10376, 0x1037a),  // Combining Old Permic Let..Combining Old Permic Let\n        intArrayOf(0x10a01, 0x10a03),  // Kharoshthi Vowel Sign I ..Kharoshthi Vowel Sign Vo\n        intArrayOf(0x10a05, 0x10a06),  // Kharoshthi Vowel Sign E ..Kharoshthi Vowel Sign O\n        intArrayOf(0x10a0c, 0x10a0f),  // Kharoshthi Vowel Length ..Kharoshthi Sign Visarga\n        intArrayOf(0x10a38, 0x10a3a),  // Kharoshthi Sign Bar Abov..Kharoshthi Sign Dot Belo\n        intArrayOf(0x10a3f, 0x10a3f),  // Kharoshthi Virama       ..Kharoshthi Virama\n        intArrayOf(0x10ae5, 0x10ae6),  // Manichaean Abbreviation ..Manichaean Abbreviation\n        intArrayOf(0x10d24, 0x10d27),  // Hanifi Rohingya Sign Har..Hanifi Rohingya Sign Tas\n        intArrayOf(0x10eab, 0x10eac),  // Yezidi Combining Hamza M..Yezidi Combining Madda M\n        intArrayOf(0x10efd, 0x10eff),  // (nil)                   ..(nil)\n        intArrayOf(0x10f46, 0x10f50),  // Sogdian Combining Dot Be..Sogdian Combining Stroke\n        intArrayOf(0x10f82, 0x10f85),  // Old Uyghur Combining Dot..Old Uyghur Combining Two\n        intArrayOf(0x11001, 0x11001),  // Brahmi Sign Anusvara    ..Brahmi Sign Anusvara\n        intArrayOf(0x11038, 0x11046),  // Brahmi Vowel Sign Aa    ..Brahmi Virama\n        intArrayOf(0x11070, 0x11070),  // Brahmi Sign Old Tamil Vi..Brahmi Sign Old Tamil Vi\n        intArrayOf(0x11073, 0x11074),  // Brahmi Vowel Sign Old Ta..Brahmi Vowel Sign Old Ta\n        intArrayOf(0x1107f, 0x11081),  // Brahmi Number Joiner    ..Kaithi Sign Anusvara\n        intArrayOf(0x110b3, 0x110b6),  // Kaithi Vowel Sign U     ..Kaithi Vowel Sign Ai\n        intArrayOf(0x110b9, 0x110ba),  // Kaithi Sign Virama      ..Kaithi Sign Nukta\n        intArrayOf(0x110c2, 0x110c2),  // Kaithi Vowel Sign Vocali..Kaithi Vowel Sign Vocali\n        intArrayOf(0x11100, 0x11102),  // Chakma Sign Candrabindu ..Chakma Sign Visarga\n        intArrayOf(0x11127, 0x1112b),  // Chakma Vowel Sign A     ..Chakma Vowel Sign Uu\n        intArrayOf(0x1112d, 0x11134),  // Chakma Vowel Sign Ai    ..Chakma Maayyaa\n        intArrayOf(0x11173, 0x11173),  // Mahajani Sign Nukta     ..Mahajani Sign Nukta\n        intArrayOf(0x11180, 0x11181),  // Sharada Sign Candrabindu..Sharada Sign Anusvara\n        intArrayOf(0x111b6, 0x111be),  // Sharada Vowel Sign U    ..Sharada Vowel Sign O\n        intArrayOf(0x111c9, 0x111cc),  // Sharada Sandhi Mark     ..Sharada Extra Short Vowe\n        intArrayOf(0x111cf, 0x111cf),  // Sharada Sign Inverted Ca..Sharada Sign Inverted Ca\n        intArrayOf(0x1122f, 0x11231),  // Khojki Vowel Sign U     ..Khojki Vowel Sign Ai\n        intArrayOf(0x11234, 0x11234),  // Khojki Sign Anusvara    ..Khojki Sign Anusvara\n        intArrayOf(0x11236, 0x11237),  // Khojki Sign Nukta       ..Khojki Sign Shadda\n        intArrayOf(0x1123e, 0x1123e),  // Khojki Sign Sukun       ..Khojki Sign Sukun\n        intArrayOf(0x11241, 0x11241),  // (nil)                   ..(nil)\n        intArrayOf(0x112df, 0x112df),  // Khudawadi Sign Anusvara ..Khudawadi Sign Anusvara\n        intArrayOf(0x112e3, 0x112ea),  // Khudawadi Vowel Sign U  ..Khudawadi Sign Virama\n        intArrayOf(0x11300, 0x11301),  // Grantha Sign Combining A..Grantha Sign Candrabindu\n        intArrayOf(0x1133b, 0x1133c),  // Combining Bindu Below   ..Grantha Sign Nukta\n        intArrayOf(0x11340, 0x11340),  // Grantha Vowel Sign Ii   ..Grantha Vowel Sign Ii\n        intArrayOf(0x11366, 0x1136c),  // Combining Grantha Digit ..Combining Grantha Digit\n        intArrayOf(0x11370, 0x11374),  // Combining Grantha Letter..Combining Grantha Letter\n        intArrayOf(0x11438, 0x1143f),  // Newa Vowel Sign U       ..Newa Vowel Sign Ai\n        intArrayOf(0x11442, 0x11444),  // Newa Sign Virama        ..Newa Sign Anusvara\n        intArrayOf(0x11446, 0x11446),  // Newa Sign Nukta         ..Newa Sign Nukta\n        intArrayOf(0x1145e, 0x1145e),  // Newa Sandhi Mark        ..Newa Sandhi Mark\n        intArrayOf(0x114b3, 0x114b8),  // Tirhuta Vowel Sign U    ..Tirhuta Vowel Sign Vocal\n        intArrayOf(0x114ba, 0x114ba),  // Tirhuta Vowel Sign Short..Tirhuta Vowel Sign Short\n        intArrayOf(0x114bf, 0x114c0),  // Tirhuta Sign Candrabindu..Tirhuta Sign Anusvara\n        intArrayOf(0x114c2, 0x114c3),  // Tirhuta Sign Virama     ..Tirhuta Sign Nukta\n        intArrayOf(0x115b2, 0x115b5),  // Siddham Vowel Sign U    ..Siddham Vowel Sign Vocal\n        intArrayOf(0x115bc, 0x115bd),  // Siddham Sign Candrabindu..Siddham Sign Anusvara\n        intArrayOf(0x115bf, 0x115c0),  // Siddham Sign Virama     ..Siddham Sign Nukta\n        intArrayOf(0x115dc, 0x115dd),  // Siddham Vowel Sign Alter..Siddham Vowel Sign Alter\n        intArrayOf(0x11633, 0x1163a),  // Modi Vowel Sign U       ..Modi Vowel Sign Ai\n        intArrayOf(0x1163d, 0x1163d),  // Modi Sign Anusvara      ..Modi Sign Anusvara\n        intArrayOf(0x1163f, 0x11640),  // Modi Sign Virama        ..Modi Sign Ardhacandra\n        intArrayOf(0x116ab, 0x116ab),  // Takri Sign Anusvara     ..Takri Sign Anusvara\n        intArrayOf(0x116ad, 0x116ad),  // Takri Vowel Sign Aa     ..Takri Vowel Sign Aa\n        intArrayOf(0x116b0, 0x116b5),  // Takri Vowel Sign U      ..Takri Vowel Sign Au\n        intArrayOf(0x116b7, 0x116b7),  // Takri Sign Nukta        ..Takri Sign Nukta\n        intArrayOf(0x1171d, 0x1171f),  // Ahom Consonant Sign Medi..Ahom Consonant Sign Medi\n        intArrayOf(0x11722, 0x11725),  // Ahom Vowel Sign I       ..Ahom Vowel Sign Uu\n        intArrayOf(0x11727, 0x1172b),  // Ahom Vowel Sign Aw      ..Ahom Sign Killer\n        intArrayOf(0x1182f, 0x11837),  // Dogra Vowel Sign U      ..Dogra Sign Anusvara\n        intArrayOf(0x11839, 0x1183a),  // Dogra Sign Virama       ..Dogra Sign Nukta\n        intArrayOf(0x1193b, 0x1193c),  // Dives Akuru Sign Anusvar..Dives Akuru Sign Candrab\n        intArrayOf(0x1193e, 0x1193e),  // Dives Akuru Virama      ..Dives Akuru Virama\n        intArrayOf(0x11943, 0x11943),  // Dives Akuru Sign Nukta  ..Dives Akuru Sign Nukta\n        intArrayOf(0x119d4, 0x119d7),  // Nandinagari Vowel Sign U..Nandinagari Vowel Sign V\n        intArrayOf(0x119da, 0x119db),  // Nandinagari Vowel Sign E..Nandinagari Vowel Sign A\n        intArrayOf(0x119e0, 0x119e0),  // Nandinagari Sign Virama ..Nandinagari Sign Virama\n        intArrayOf(0x11a01, 0x11a0a),  // Zanabazar Square Vowel S..Zanabazar Square Vowel L\n        intArrayOf(0x11a33, 0x11a38),  // Zanabazar Square Final C..Zanabazar Square Sign An\n        intArrayOf(0x11a3b, 0x11a3e),  // Zanabazar Square Cluster..Zanabazar Square Cluster\n        intArrayOf(0x11a47, 0x11a47),  // Zanabazar Square Subjoin..Zanabazar Square Subjoin\n        intArrayOf(0x11a51, 0x11a56),  // Soyombo Vowel Sign I    ..Soyombo Vowel Sign Oe\n        intArrayOf(0x11a59, 0x11a5b),  // Soyombo Vowel Sign Vocal..Soyombo Vowel Length Mar\n        intArrayOf(0x11a8a, 0x11a96),  // Soyombo Final Consonant ..Soyombo Sign Anusvara\n        intArrayOf(0x11a98, 0x11a99),  // Soyombo Gemination Mark ..Soyombo Subjoiner\n        intArrayOf(0x11c30, 0x11c36),  // Bhaiksuki Vowel Sign I  ..Bhaiksuki Vowel Sign Voc\n        intArrayOf(0x11c38, 0x11c3d),  // Bhaiksuki Vowel Sign E  ..Bhaiksuki Sign Anusvara\n        intArrayOf(0x11c3f, 0x11c3f),  // Bhaiksuki Sign Virama   ..Bhaiksuki Sign Virama\n        intArrayOf(0x11c92, 0x11ca7),  // Marchen Subjoined Letter..Marchen Subjoined Letter\n        intArrayOf(0x11caa, 0x11cb0),  // Marchen Subjoined Letter..Marchen Vowel Sign Aa\n        intArrayOf(0x11cb2, 0x11cb3),  // Marchen Vowel Sign U    ..Marchen Vowel Sign E\n        intArrayOf(0x11cb5, 0x11cb6),  // Marchen Sign Anusvara   ..Marchen Sign Candrabindu\n        intArrayOf(0x11d31, 0x11d36),  // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign\n        intArrayOf(0x11d3a, 0x11d3a),  // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign\n        intArrayOf(0x11d3c, 0x11d3d),  // Masaram Gondi Vowel Sign..Masaram Gondi Vowel Sign\n        intArrayOf(0x11d3f, 0x11d45),  // Masaram Gondi Vowel Sign..Masaram Gondi Virama\n        intArrayOf(0x11d47, 0x11d47),  // Masaram Gondi Ra-kara   ..Masaram Gondi Ra-kara\n        intArrayOf(0x11d90, 0x11d91),  // Gunjala Gondi Vowel Sign..Gunjala Gondi Vowel Sign\n        intArrayOf(0x11d95, 0x11d95),  // Gunjala Gondi Sign Anusv..Gunjala Gondi Sign Anusv\n        intArrayOf(0x11d97, 0x11d97),  // Gunjala Gondi Virama    ..Gunjala Gondi Virama\n        intArrayOf(0x11ef3, 0x11ef4),  // Makasar Vowel Sign I    ..Makasar Vowel Sign U\n        intArrayOf(0x11f00, 0x11f01),  // (nil)                   ..(nil)\n        intArrayOf(0x11f36, 0x11f3a),  // (nil)                   ..(nil)\n        intArrayOf(0x11f40, 0x11f40),  // (nil)                   ..(nil)\n        intArrayOf(0x11f42, 0x11f42),  // (nil)                   ..(nil)\n        intArrayOf(0x13440, 0x13440),  // (nil)                   ..(nil)\n        intArrayOf(0x13447, 0x13455),  // (nil)                   ..(nil)\n        intArrayOf(0x16af0, 0x16af4),  // Bassa Vah Combining High..Bassa Vah Combining High\n        intArrayOf(0x16b30, 0x16b36),  // Pahawh Hmong Mark Cim Tu..Pahawh Hmong Mark Cim Ta\n        intArrayOf(0x16f4f, 0x16f4f),  // Miao Sign Consonant Modi..Miao Sign Consonant Modi\n        intArrayOf(0x16f8f, 0x16f92),  // Miao Tone Right         ..Miao Tone Below\n        intArrayOf(0x16fe4, 0x16fe4),  // Khitan Small Script Fill..Khitan Small Script Fill\n        intArrayOf(0x1bc9d, 0x1bc9e),  // Duployan Thick Letter Se..Duployan Double Mark\n        intArrayOf(0x1cf00, 0x1cf2d),  // Znamenny Combining Mark ..Znamenny Combining Mark\n        intArrayOf(0x1cf30, 0x1cf46),  // Znamenny Combining Tonal..Znamenny Priznak Modifie\n        intArrayOf(0x1d167, 0x1d169),  // Musical Symbol Combining..Musical Symbol Combining\n        intArrayOf(0x1d17b, 0x1d182),  // Musical Symbol Combining..Musical Symbol Combining\n        intArrayOf(0x1d185, 0x1d18b),  // Musical Symbol Combining..Musical Symbol Combining\n        intArrayOf(0x1d1aa, 0x1d1ad),  // Musical Symbol Combining..Musical Symbol Combining\n        intArrayOf(0x1d242, 0x1d244),  // Combining Greek Musical ..Combining Greek Musical\n        intArrayOf(0x1da00, 0x1da36),  // Signwriting Head Rim    ..Signwriting Air Sucking\n        intArrayOf(0x1da3b, 0x1da6c),  // Signwriting Mouth Closed..Signwriting Excitement\n        intArrayOf(0x1da75, 0x1da75),  // Signwriting Upper Body T..Signwriting Upper Body T\n        intArrayOf(0x1da84, 0x1da84),  // Signwriting Location Hea..Signwriting Location Hea\n        intArrayOf(0x1da9b, 0x1da9f),  // Signwriting Fill Modifie..Signwriting Fill Modifie\n        intArrayOf(0x1daa1, 0x1daaf),  // Signwriting Rotation Mod..Signwriting Rotation Mod\n        intArrayOf(0x1e000, 0x1e006),  // Combining Glagolitic Let..Combining Glagolitic Let\n        intArrayOf(0x1e008, 0x1e018),  // Combining Glagolitic Let..Combining Glagolitic Let\n        intArrayOf(0x1e01b, 0x1e021),  // Combining Glagolitic Let..Combining Glagolitic Let\n        intArrayOf(0x1e023, 0x1e024),  // Combining Glagolitic Let..Combining Glagolitic Let\n        intArrayOf(0x1e026, 0x1e02a),  // Combining Glagolitic Let..Combining Glagolitic Let\n        intArrayOf(0x1e08f, 0x1e08f),  // (nil)                   ..(nil)\n        intArrayOf(0x1e130, 0x1e136),  // Nyiakeng Puachue Hmong T..Nyiakeng Puachue Hmong T\n        intArrayOf(0x1e2ae, 0x1e2ae),  // Toto Sign Rising Tone   ..Toto Sign Rising Tone\n        intArrayOf(0x1e2ec, 0x1e2ef),  // Wancho Tone Tup         ..Wancho Tone Koini\n        intArrayOf(0x1e4ec, 0x1e4ef),  // (nil)                   ..(nil)\n        intArrayOf(0x1e8d0, 0x1e8d6),  // Mende Kikakui Combining ..Mende Kikakui Combining\n        intArrayOf(0x1e944, 0x1e94a),  // Adlam Alif Lengthener   ..Adlam Nukta\n        intArrayOf(0xe0100, 0xe01ef),  // Variation Selector-17   ..Variation Selector-256\n    )\n\n    // https://github.com/jquast/wcwidth/blob/master/wcwidth/table_wide.py\n    // from https://github.com/jquast/wcwidth/pull/64\n    // at commit 1b9b6585b0080ea5cb88dc9815796505724793fe (2022-12-16):\n    private val WIDE_EASTASIAN = arrayOf(\n        intArrayOf(0x01100, 0x0115f),  // Hangul Choseong Kiyeok  ..Hangul Choseong Filler\n        intArrayOf(0x0231a, 0x0231b),  // Watch                   ..Hourglass\n        intArrayOf(0x02329, 0x0232a),  // Left-pointing Angle Brac..Right-pointing Angle Bra\n        intArrayOf(0x023e9, 0x023ec),  // Black Right-pointing Dou..Black Down-pointing Doub\n        intArrayOf(0x023f0, 0x023f0),  // Alarm Clock             ..Alarm Clock\n        intArrayOf(0x023f3, 0x023f3),  // Hourglass With Flowing S..Hourglass With Flowing S\n        intArrayOf(0x025fd, 0x025fe),  // White Medium Small Squar..Black Medium Small Squar\n        intArrayOf(0x02614, 0x02615),  // Umbrella With Rain Drops..Hot Beverage\n        intArrayOf(0x02648, 0x02653),  // Aries                   ..Pisces\n        intArrayOf(0x0267f, 0x0267f),  // Wheelchair Symbol       ..Wheelchair Symbol\n        intArrayOf(0x02693, 0x02693),  // Anchor                  ..Anchor\n        intArrayOf(0x026a1, 0x026a1),  // High Voltage Sign       ..High Voltage Sign\n        intArrayOf(0x026aa, 0x026ab),  // Medium White Circle     ..Medium Black Circle\n        intArrayOf(0x026bd, 0x026be),  // Soccer Ball             ..Baseball\n        intArrayOf(0x026c4, 0x026c5),  // Snowman Without Snow    ..Sun Behind Cloud\n        intArrayOf(0x026ce, 0x026ce),  // Ophiuchus               ..Ophiuchus\n        intArrayOf(0x026d4, 0x026d4),  // No Entry                ..No Entry\n        intArrayOf(0x026ea, 0x026ea),  // Church                  ..Church\n        intArrayOf(0x026f2, 0x026f3),  // Fountain                ..Flag In Hole\n        intArrayOf(0x026f5, 0x026f5),  // Sailboat                ..Sailboat\n        intArrayOf(0x026fa, 0x026fa),  // Tent                    ..Tent\n        intArrayOf(0x026fd, 0x026fd),  // Fuel Pump               ..Fuel Pump\n        intArrayOf(0x02705, 0x02705),  // White Heavy Check Mark  ..White Heavy Check Mark\n        intArrayOf(0x0270a, 0x0270b),  // Raised Fist             ..Raised Hand\n        intArrayOf(0x02728, 0x02728),  // Sparkles                ..Sparkles\n        intArrayOf(0x0274c, 0x0274c),  // Cross Mark              ..Cross Mark\n        intArrayOf(0x0274e, 0x0274e),  // Negative Squared Cross M..Negative Squared Cross M\n        intArrayOf(0x02753, 0x02755),  // Black Question Mark Orna..White Exclamation Mark O\n        intArrayOf(0x02757, 0x02757),  // Heavy Exclamation Mark S..Heavy Exclamation Mark S\n        intArrayOf(0x02795, 0x02797),  // Heavy Plus Sign         ..Heavy Division Sign\n        intArrayOf(0x027b0, 0x027b0),  // Curly Loop              ..Curly Loop\n        intArrayOf(0x027bf, 0x027bf),  // Double Curly Loop       ..Double Curly Loop\n        intArrayOf(0x02b1b, 0x02b1c),  // Black Large Square      ..White Large Square\n        intArrayOf(0x02b50, 0x02b50),  // White Medium Star       ..White Medium Star\n        intArrayOf(0x02b55, 0x02b55),  // Heavy Large Circle      ..Heavy Large Circle\n        intArrayOf(0x02e80, 0x02e99),  // Cjk Radical Repeat      ..Cjk Radical Rap\n        intArrayOf(0x02e9b, 0x02ef3),  // Cjk Radical Choke       ..Cjk Radical C-simplified\n        intArrayOf(0x02f00, 0x02fd5),  // Kangxi Radical One      ..Kangxi Radical Flute\n        intArrayOf(0x02ff0, 0x02ffb),  // Ideographic Description ..Ideographic Description\n        intArrayOf(0x03000, 0x0303e),  // Ideographic Space       ..Ideographic Variation In\n        intArrayOf(0x03041, 0x03096),  // Hiragana Letter Small A ..Hiragana Letter Small Ke\n        intArrayOf(0x03099, 0x030ff),  // Combining Katakana-hirag..Katakana Digraph Koto\n        intArrayOf(0x03105, 0x0312f),  // Bopomofo Letter B       ..Bopomofo Letter Nn\n        intArrayOf(0x03131, 0x0318e),  // Hangul Letter Kiyeok    ..Hangul Letter Araeae\n        intArrayOf(0x03190, 0x031e3),  // Ideographic Annotation L..Cjk Stroke Q\n        intArrayOf(0x031f0, 0x0321e),  // Katakana Letter Small Ku..Parenthesized Korean Cha\n        intArrayOf(0x03220, 0x03247),  // Parenthesized Ideograph ..Circled Ideograph Koto\n        intArrayOf(0x03250, 0x04dbf),  // Partnership Sign        ..Cjk Unified Ideograph-4d\n        intArrayOf(0x04e00, 0x0a48c),  // Cjk Unified Ideograph-4e..Yi Syllable Yyr\n        intArrayOf(0x0a490, 0x0a4c6),  // Yi Radical Qot          ..Yi Radical Ke\n        intArrayOf(0x0a960, 0x0a97c),  // Hangul Choseong Tikeut-m..Hangul Choseong Ssangyeo\n        intArrayOf(0x0ac00, 0x0d7a3),  // Hangul Syllable Ga      ..Hangul Syllable Hih\n        intArrayOf(0x0f900, 0x0faff),  // Cjk Compatibility Ideogr..(nil)\n        intArrayOf(0x0fe10, 0x0fe19),  // Presentation Form For Ve..Presentation Form For Ve\n        intArrayOf(0x0fe30, 0x0fe52),  // Presentation Form For Ve..Small Full Stop\n        intArrayOf(0x0fe54, 0x0fe66),  // Small Semicolon         ..Small Equals Sign\n        intArrayOf(0x0fe68, 0x0fe6b),  // Small Reverse Solidus   ..Small Commercial At\n        intArrayOf(0x0ff01, 0x0ff60),  // Fullwidth Exclamation Ma..Fullwidth Right White Pa\n        intArrayOf(0x0ffe0, 0x0ffe6),  // Fullwidth Cent Sign     ..Fullwidth Won Sign\n        intArrayOf(0x16fe0, 0x16fe4),  // Tangut Iteration Mark   ..Khitan Small Script Fill\n        intArrayOf(0x16ff0, 0x16ff1),  // Vietnamese Alternate Rea..Vietnamese Alternate Rea\n        intArrayOf(0x17000, 0x187f7),  // (nil)                   ..(nil)\n        intArrayOf(0x18800, 0x18cd5),  // Tangut Component-001    ..Khitan Small Script Char\n        intArrayOf(0x18d00, 0x18d08),  // (nil)                   ..(nil)\n        intArrayOf(0x1aff0, 0x1aff3),  // Katakana Letter Minnan T..Katakana Letter Minnan T\n        intArrayOf(0x1aff5, 0x1affb),  // Katakana Letter Minnan T..Katakana Letter Minnan N\n        intArrayOf(0x1affd, 0x1affe),  // Katakana Letter Minnan N..Katakana Letter Minnan N\n        intArrayOf(0x1b000, 0x1b122),  // Katakana Letter Archaic ..Katakana Letter Archaic\n        intArrayOf(0x1b132, 0x1b132),  // (nil)                   ..(nil)\n        intArrayOf(0x1b150, 0x1b152),  // Hiragana Letter Small Wi..Hiragana Letter Small Wo\n        intArrayOf(0x1b155, 0x1b155),  // (nil)                   ..(nil)\n        intArrayOf(0x1b164, 0x1b167),  // Katakana Letter Small Wi..Katakana Letter Small N\n        intArrayOf(0x1b170, 0x1b2fb),  // Nushu Character-1b170   ..Nushu Character-1b2fb\n        intArrayOf(0x1f004, 0x1f004),  // Mahjong Tile Red Dragon ..Mahjong Tile Red Dragon\n        intArrayOf(0x1f0cf, 0x1f0cf),  // Playing Card Black Joker..Playing Card Black Joker\n        intArrayOf(0x1f18e, 0x1f18e),  // Negative Squared Ab     ..Negative Squared Ab\n        intArrayOf(0x1f191, 0x1f19a),  // Squared Cl              ..Squared Vs\n        intArrayOf(0x1f200, 0x1f202),  // Square Hiragana Hoka    ..Squared Katakana Sa\n        intArrayOf(0x1f210, 0x1f23b),  // Squared Cjk Unified Ideo..Squared Cjk Unified Ideo\n        intArrayOf(0x1f240, 0x1f248),  // Tortoise Shell Bracketed..Tortoise Shell Bracketed\n        intArrayOf(0x1f250, 0x1f251),  // Circled Ideograph Advant..Circled Ideograph Accept\n        intArrayOf(0x1f260, 0x1f265),  // Rounded Symbol For Fu   ..Rounded Symbol For Cai\n        intArrayOf(0x1f300, 0x1f320),  // Cyclone                 ..Shooting Star\n        intArrayOf(0x1f32d, 0x1f335),  // Hot Dog                 ..Cactus\n        intArrayOf(0x1f337, 0x1f37c),  // Tulip                   ..Baby Bottle\n        intArrayOf(0x1f37e, 0x1f393),  // Bottle With Popping Cork..Graduation Cap\n        intArrayOf(0x1f3a0, 0x1f3ca),  // Carousel Horse          ..Swimmer\n        intArrayOf(0x1f3cf, 0x1f3d3),  // Cricket Bat And Ball    ..Table Tennis Paddle And\n        intArrayOf(0x1f3e0, 0x1f3f0),  // House Building          ..European Castle\n        intArrayOf(0x1f3f4, 0x1f3f4),  // Waving Black Flag       ..Waving Black Flag\n        intArrayOf(0x1f3f8, 0x1f43e),  // Badminton Racquet And Sh..Paw Prints\n        intArrayOf(0x1f440, 0x1f440),  // Eyes                    ..Eyes\n        intArrayOf(0x1f442, 0x1f4fc),  // Ear                     ..Videocassette\n        intArrayOf(0x1f4ff, 0x1f53d),  // Prayer Beads            ..Down-pointing Small Red\n        intArrayOf(0x1f54b, 0x1f54e),  // Kaaba                   ..Menorah With Nine Branch\n        intArrayOf(0x1f550, 0x1f567),  // Clock Face One Oclock   ..Clock Face Twelve-thirty\n        intArrayOf(0x1f57a, 0x1f57a),  // Man Dancing             ..Man Dancing\n        intArrayOf(0x1f595, 0x1f596),  // Reversed Hand With Middl..Raised Hand With Part Be\n        intArrayOf(0x1f5a4, 0x1f5a4),  // Black Heart             ..Black Heart\n        intArrayOf(0x1f5fb, 0x1f64f),  // Mount Fuji              ..Person With Folded Hands\n        intArrayOf(0x1f680, 0x1f6c5),  // Rocket                  ..Left Luggage\n        intArrayOf(0x1f6cc, 0x1f6cc),  // Sleeping Accommodation  ..Sleeping Accommodation\n        intArrayOf(0x1f6d0, 0x1f6d2),  // Place Of Worship        ..Shopping Trolley\n        intArrayOf(0x1f6d5, 0x1f6d7),  // Hindu Temple            ..Elevator\n        intArrayOf(0x1f6dc, 0x1f6df),  // (nil)                   ..Ring Buoy\n        intArrayOf(0x1f6eb, 0x1f6ec),  // Airplane Departure      ..Airplane Arriving\n        intArrayOf(0x1f6f4, 0x1f6fc),  // Scooter                 ..Roller Skate\n        intArrayOf(0x1f7e0, 0x1f7eb),  // Large Orange Circle     ..Large Brown Square\n        intArrayOf(0x1f7f0, 0x1f7f0),  // Heavy Equals Sign       ..Heavy Equals Sign\n        intArrayOf(0x1f90c, 0x1f93a),  // Pinched Fingers         ..Fencer\n        intArrayOf(0x1f93c, 0x1f945),  // Wrestlers               ..Goal Net\n        intArrayOf(0x1f947, 0x1f9ff),  // First Place Medal       ..Nazar Amulet\n        intArrayOf(0x1fa70, 0x1fa7c),  // Ballet Shoes            ..Crutch\n        intArrayOf(0x1fa80, 0x1fa88),  // Yo-yo                   ..(nil)\n        intArrayOf(0x1fa90, 0x1fabd),  // Ringed Planet           ..(nil)\n        intArrayOf(0x1fabf, 0x1fac5),  // (nil)                   ..Person With Crown\n        intArrayOf(0x1face, 0x1fadb),  // (nil)                   ..(nil)\n        intArrayOf(0x1fae0, 0x1fae8),  // Melting Face            ..(nil)\n        intArrayOf(0x1faf0, 0x1faf8),  // Hand With Index Finger A..(nil)\n        intArrayOf(0x20000, 0x2fffd),  // Cjk Unified Ideograph-20..(nil)\n        intArrayOf(0x30000, 0x3fffd),  // Cjk Unified Ideograph-30..(nil)\n    )\n\n    private fun intable(table: Array<IntArray>, c: Int): Boolean {\n        if (c < table[0][0]) return false\n        var bot = 0\n        var top = table.size - 1\n        while (top >= bot) {\n            val mid = (bot + top) / 2\n            if (table[mid][1] < c) {\n                bot = mid + 1\n            } else if (table[mid][0] > c) {\n                top = mid - 1\n            } else {\n                return true\n            }\n        }\n        return false\n    }\n\n    /** Return the terminal display width of a code point: 0, 1 or 2. */\n    fun width(ucs: Int): Int {\n        if (ucs == 0 ||\n            ucs == 0x034F ||\n            (ucs in 0x200B..0x200F) ||\n            ucs == 0x2028 ||\n            ucs == 0x2029 ||\n            (ucs in 0x202A..0x202E) ||\n            (ucs in 0x2060..0x2063)) {\n            return 0\n        }\n\n        // C0/C1 control characters\n        // Termux change: Return 0 instead of -1.\n        if (ucs < 32 || (ucs in 0x07F until 0x0A0)) return 0\n\n        if (intable(ZERO_WIDTH, ucs)) return 0\n\n        return if (intable(WIDE_EASTASIAN, ucs)) 2 else 1\n    }\n\n    /** The width at an index position in a java char array. */\n    fun width(chars: CharArray, index: Int): Int {\n        val c = chars[index]\n        return if (Character.isHighSurrogate(c)) width(Character.toCodePoint(c, chars[index + 1])) else width(c.code)\n    }\n\n    /**\n     * The zero width characters count like combining characters in the `chars` array from start\n     * index to end index (exclusive).\n     */\n    fun zeroWidthCharsCount(chars: CharArray, start: Int, end: Int): Int {\n        if (start < 0 || start >= chars.size) return 0\n        var count = 0\n        var i = start\n        while (i < end && i < chars.size) {\n            if (Character.isHighSurrogate(chars[i])) {\n                if (width(Character.toCodePoint(chars[i], chars[i + 1])) <= 0) {\n                    count++\n                }\n                i += 2\n            } else {\n                if (width(chars[i].code) <= 0) {\n                    count++\n                }\n                i++\n            }\n        }\n        return count\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt",
    "content": "package com.topjohnwu.magisk.ui\n\nimport android.Manifest\nimport android.Manifest.permission.REQUEST_INSTALL_PACKAGES\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ApplicationInfo\nimport android.content.res.Resources\nimport android.graphics.Color\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.WindowManager\nimport android.widget.Toast\nimport androidx.activity.compose.setContent\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.ui.Modifier\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.content.res.use\nimport androidx.core.view.WindowCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator\nimport androidx.navigation3.runtime.entryProvider\nimport androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator\nimport androidx.navigation3.ui.NavDisplay\nimport com.topjohnwu.magisk.arch.VMFactory\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.base.ActivityExtension\nimport com.topjohnwu.magisk.core.base.SplashController\nimport com.topjohnwu.magisk.core.base.SplashScreenHost\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.reflectField\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.wrap\nimport com.topjohnwu.magisk.ui.deny.DenyListScreen\nimport com.topjohnwu.magisk.ui.deny.DenyListViewModel\nimport com.topjohnwu.magisk.ui.flash.FlashScreen\nimport com.topjohnwu.magisk.ui.flash.FlashUtils\nimport com.topjohnwu.magisk.ui.flash.FlashViewModel\nimport com.topjohnwu.magisk.ui.module.ActionScreen\nimport com.topjohnwu.magisk.ui.module.ActionViewModel\nimport com.topjohnwu.magisk.ui.navigation.LocalNavigator\nimport com.topjohnwu.magisk.ui.navigation.Navigator\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport com.topjohnwu.magisk.ui.navigation.rememberNavigator\nimport com.topjohnwu.magisk.ui.superuser.SuperuserDetailScreen\nimport com.topjohnwu.magisk.ui.superuser.SuperuserViewModel\nimport com.topjohnwu.magisk.ui.theme.MagiskTheme\nimport com.topjohnwu.magisk.ui.theme.Theme\nimport com.topjohnwu.magisk.view.Shortcuts\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.launch\nimport top.yukonga.miuix.kmp.utils.MiuixPopupUtils.Companion.MiuixPopupHost\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass MainActivity : AppCompatActivity(), SplashScreenHost {\n\n    override val extension = ActivityExtension(this)\n    override val splashController = SplashController(this)\n\n    private val intentState = MutableStateFlow(0)\n    internal val showInvalidState = MutableStateFlow(false)\n    internal val showUnsupported = MutableStateFlow<List<Pair<Int, Int>>>(emptyList())\n    internal val showShortcutPrompt = MutableStateFlow(false)\n\n    init {\n        AppCompatDelegate.setDefaultNightMode(Config.darkTheme)\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base.wrap())\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        extension.onCreate(savedInstanceState)\n        if (isRunningAsStub) {\n            val delegate = delegate\n            val clz = delegate.javaClass\n            clz.reflectField(\"mActivityHandlesConfigFlagsChecked\").set(delegate, true)\n            clz.reflectField(\"mActivityHandlesConfigFlags\").set(delegate, 0)\n        }\n\n        setTheme(Theme.selected.themeRes)\n        splashController.preOnCreate()\n        super.onCreate(savedInstanceState)\n        splashController.onCreate(savedInstanceState)\n\n        setupWindow()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        splashController.onResume()\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        extension.onSaveInstanceState(outState)\n    }\n\n    private fun setupWindow() {\n        obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))\n            .use { it.getDrawable(0) }\n            .also { window.setBackgroundDrawable(it) }\n\n        WindowCompat.setDecorFitsSystemWindows(window, false)\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            window?.decorView?.post {\n                if ((window.decorView.rootWindowInsets?.systemWindowInsetBottom\n                        ?: 0) < Resources.getSystem().displayMetrics.density * 40) {\n                    window.navigationBarColor = Color.TRANSPARENT\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                        window.navigationBarDividerColor = Color.TRANSPARENT\n                    }\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n                        window.isNavigationBarContrastEnforced = false\n                        window.isStatusBarContrastEnforced = false\n                    }\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    override fun onCreateUi(savedInstanceState: Bundle?) {\n        showUnsupportedMessage()\n        askForHomeShortcut()\n\n        if (Config.checkUpdate) {\n            extension.withPermission(Manifest.permission.POST_NOTIFICATIONS) {\n                Config.checkUpdate = it\n            }\n        }\n\n        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)\n\n        val initialTab = getInitialTab(intent)\n\n        setContent {\n            MagiskTheme {\n                Box(modifier = Modifier.fillMaxSize()) {\n                    val navigator = rememberNavigator(Route.Main)\n                    CompositionLocalProvider(LocalNavigator provides navigator) {\n                        HandleFlashIntent(navigator)\n\n                        NavDisplay(\n                            backStack = navigator.backStack,\n                            onBack = { navigator.pop() },\n                            entryDecorators = listOf(\n                                rememberSaveableStateHolderNavEntryDecorator(),\n                                rememberViewModelStoreNavEntryDecorator()\n                            ),\n                            entryProvider = entryProvider {\n                                entry<Route.Main> {\n                                    MainScreen(initialTab = initialTab)\n                                }\n                                entry<Route.DenyList> { _ ->\n                                    val vm: DenyListViewModel = androidx.lifecycle.viewmodel.compose.viewModel(factory = VMFactory)\n                                    LaunchedEffect(Unit) { vm.startLoading() }\n                                    DenyListScreen(vm, onBack = { navigator.pop() })\n                                }\n                                entry<Route.Flash> { key ->\n                                    val vm: FlashViewModel = androidx.lifecycle.viewmodel.compose.viewModel(factory = VMFactory)\n                                    LaunchedEffect(key) {\n                                        if (vm.flashAction.isEmpty()) {\n                                            vm.flashAction = key.action\n                                            vm.flashUri = key.additionalData?.let { Uri.parse(it) }\n                                            vm.startFlashing()\n                                        }\n                                    }\n                                    FlashScreen(vm, action = key.action, onBack = { navigator.pop() })\n                                }\n                                entry<Route.SuperuserDetail> { key ->\n                                    val vm: SuperuserViewModel = androidx.lifecycle.viewmodel.compose.viewModel(\n                                        viewModelStoreOwner = this@MainActivity, factory = VMFactory\n                                    )\n                                    LaunchedEffect(Unit) {\n                                        vm.authenticate = { onSuccess ->\n                                            extension.withAuthentication { if (it) onSuccess() }\n                                        }\n                                    }\n                                    SuperuserDetailScreen(uid = key.uid, viewModel = vm, onBack = { navigator.pop() })\n                                }\n                                entry<Route.Action> { key ->\n                                    val vm: ActionViewModel = androidx.lifecycle.viewmodel.compose.viewModel(factory = VMFactory)\n                                    LaunchedEffect(key) {\n                                        if (vm.actionId.isEmpty()) {\n                                            vm.actionId = key.id\n                                            vm.actionName = key.name\n                                            vm.startRunAction()\n                                        }\n                                    }\n                                    ActionScreen(vm, actionName = key.name, onBack = { navigator.pop() })\n                                }\n                            }\n                        )\n                    }\n                    MainActivityDialogs(activity = this@MainActivity)\n                    MiuixPopupHost()\n                }\n            }\n        }\n    }\n\n    @Composable\n    private fun HandleFlashIntent(navigator: Navigator) {\n        val intentVersion by intentState.collectAsState()\n        LaunchedEffect(intentVersion) {\n            val currentIntent = intent ?: return@LaunchedEffect\n            if (currentIntent.action == FlashUtils.INTENT_FLASH) {\n                val action = currentIntent.getStringExtra(FlashUtils.EXTRA_FLASH_ACTION)\n                    ?: return@LaunchedEffect\n                val uri = currentIntent.getStringExtra(FlashUtils.EXTRA_FLASH_URI)\n                navigator.push(Route.Flash(action, uri))\n                currentIntent.action = null\n            }\n        }\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        setIntent(intent)\n        intentState.value += 1\n    }\n\n    private fun getInitialTab(intent: Intent?): Int {\n        val section = if (intent?.action == Intent.ACTION_APPLICATION_PREFERENCES) {\n            Const.Nav.SETTINGS\n        } else {\n            intent?.getStringExtra(Const.Key.OPEN_SECTION)\n        }\n        return when (section) {\n            Const.Nav.SUPERUSER -> Tab.SUPERUSER.ordinal\n            Const.Nav.MODULES -> Tab.MODULES.ordinal\n            Const.Nav.SETTINGS -> Tab.SETTINGS.ordinal\n            else -> Tab.HOME.ordinal\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    override fun showInvalidStateMessage() {\n        showInvalidState.value = true\n    }\n\n    internal fun handleInvalidStateInstall() {\n        extension.withPermission(REQUEST_INSTALL_PACKAGES) {\n            if (!it) {\n                toast(CoreR.string.install_unknown_denied, Toast.LENGTH_SHORT)\n                showInvalidState.value = true\n            } else {\n                lifecycleScope.launch {\n                    if (!AppMigration.restoreApp(this@MainActivity)) {\n                        toast(CoreR.string.failure, Toast.LENGTH_LONG)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun showUnsupportedMessage() {\n        val messages = mutableListOf<Pair<Int, Int>>()\n\n        if (Info.env.isUnsupported) {\n            messages.add(CoreR.string.unsupport_magisk_title to CoreR.string.unsupport_magisk_msg)\n        }\n        if (!Info.isEmulator && Info.env.isActive && System.getenv(\"PATH\")\n                ?.split(':')\n                ?.filterNot { java.io.File(\"$it/magisk\").exists() }\n                ?.any { java.io.File(\"$it/su\").exists() } == true) {\n            messages.add(CoreR.string.unsupport_general_title to CoreR.string.unsupport_other_su_msg)\n        }\n        if (applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0) {\n            messages.add(CoreR.string.unsupport_general_title to CoreR.string.unsupport_system_app_msg)\n        }\n        if (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE != 0) {\n            messages.add(CoreR.string.unsupport_general_title to CoreR.string.unsupport_external_storage_msg)\n        }\n\n        if (messages.isNotEmpty()) {\n            showUnsupported.value = messages\n        }\n    }\n\n    private fun askForHomeShortcut() {\n        if (isRunningAsStub && !Config.askedHome &&\n            ShortcutManagerCompat.isRequestPinShortcutSupported(this)) {\n            Config.askedHome = true\n            showShortcutPrompt.value = true\n        }\n    }\n}\n\n@Composable\nprivate fun MainActivityDialogs(activity: MainActivity) {\n    val showInvalid by activity.showInvalidState.collectAsState()\n    val unsupportedMessages by activity.showUnsupported.collectAsState()\n    val showShortcut by activity.showShortcutPrompt.collectAsState()\n\n    val invalidDialog = com.topjohnwu.magisk.ui.component.rememberConfirmDialog(\n        onConfirm = {\n            activity.showInvalidState.value = false\n            activity.handleInvalidStateInstall()\n        },\n        onDismiss = {}\n    )\n\n    LaunchedEffect(showInvalid) {\n        if (showInvalid) {\n            invalidDialog.showConfirm(\n                title = activity.getString(CoreR.string.unsupport_nonroot_stub_title),\n                content = activity.getString(CoreR.string.unsupport_nonroot_stub_msg),\n                confirm = activity.getString(CoreR.string.install),\n            )\n        }\n    }\n\n    for ((index, pair) in unsupportedMessages.withIndex()) {\n        val (titleRes, msgRes) = pair\n        val show = rememberSaveable { androidx.compose.runtime.mutableStateOf(true) }\n        com.topjohnwu.magisk.ui.component.rememberConfirmDialog(\n            onConfirm = { show.value = false },\n        ).also { dialog ->\n            LaunchedEffect(Unit) {\n                dialog.showConfirm(\n                    title = activity.getString(titleRes),\n                    content = activity.getString(msgRes),\n                )\n            }\n        }\n    }\n\n    val shortcutDialog = com.topjohnwu.magisk.ui.component.rememberConfirmDialog(\n        onConfirm = {\n            activity.showShortcutPrompt.value = false\n            Shortcuts.addHomeIcon(activity)\n        },\n        onDismiss = { activity.showShortcutPrompt.value = false }\n    )\n\n    LaunchedEffect(showShortcut) {\n        if (showShortcut) {\n            shortcutDialog.showConfirm(\n                title = activity.getString(CoreR.string.add_shortcut_title),\n                content = activity.getString(CoreR.string.add_shortcut_msg),\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/MainScreen.kt",
    "content": "package com.topjohnwu.magisk.ui\n\nimport androidx.compose.animation.animateColorAsState\nimport androidx.compose.animation.core.tween\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.draw.shadow\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.res.vectorResource\nimport androidx.compose.ui.semantics.Role\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.VMFactory\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.ui.home.HomeScreen\nimport com.topjohnwu.magisk.ui.home.HomeViewModel\nimport com.topjohnwu.magisk.ui.install.InstallViewModel\nimport com.topjohnwu.magisk.ui.log.LogScreen\nimport com.topjohnwu.magisk.ui.log.LogViewModel\nimport com.topjohnwu.magisk.ui.module.ModuleScreen\nimport com.topjohnwu.magisk.ui.module.ModuleViewModel\nimport com.topjohnwu.magisk.ui.navigation.CollectNavEvents\nimport com.topjohnwu.magisk.ui.navigation.LocalNavigator\nimport com.topjohnwu.magisk.ui.settings.SettingsScreen\nimport com.topjohnwu.magisk.ui.settings.SettingsViewModel\nimport com.topjohnwu.magisk.ui.superuser.SuperuserScreen\nimport com.topjohnwu.magisk.ui.superuser.SuperuserViewModel\nimport kotlinx.coroutines.launch\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\nenum class Tab(val titleRes: Int, val iconRes: Int) {\n    MODULES(CoreR.string.modules, R.drawable.ic_module_outlined_md2),\n    SUPERUSER(CoreR.string.superuser, R.drawable.ic_superuser_outlined_md2),\n    HOME(CoreR.string.section_home, R.drawable.ic_home_outlined_md2),\n    LOG(CoreR.string.logs, R.drawable.ic_bug_outlined_md2),\n    SETTINGS(CoreR.string.settings, R.drawable.ic_settings_outlined_md2);\n}\n\n@Composable\nfun MainScreen(initialTab: Int = Tab.HOME.ordinal) {\n    val navigator = LocalNavigator.current\n    val visibleTabs = remember {\n        Tab.entries.filter { tab ->\n            when (tab) {\n                Tab.SUPERUSER -> Info.showSuperUser\n                Tab.MODULES -> Info.env.isActive && LocalModule.loaded()\n                else -> true\n            }\n        }\n    }\n    val initialPage = visibleTabs.indexOf(Tab.entries[initialTab]).coerceAtLeast(0)\n    val pagerState = rememberPagerState(initialPage = initialPage, pageCount = { visibleTabs.size })\n\n    Box(modifier = Modifier.fillMaxSize()) {\n        HorizontalPager(\n            state = pagerState,\n            modifier = Modifier.fillMaxSize(),\n            beyondViewportPageCount = visibleTabs.size - 1,\n            userScrollEnabled = true,\n        ) { page ->\n            when (visibleTabs[page]) {\n                Tab.HOME -> {\n                    val vm: HomeViewModel = viewModel(factory = VMFactory)\n                    val installVm: InstallViewModel = viewModel(factory = VMFactory)\n                    LaunchedEffect(Unit) { vm.startLoading() }\n                    CollectNavEvents(vm, navigator)\n                    CollectNavEvents(installVm, navigator)\n                    HomeScreen(vm, installVm)\n                }\n                Tab.SUPERUSER -> {\n                    val activity = LocalContext.current as MainActivity\n                    val vm: SuperuserViewModel = viewModel(viewModelStoreOwner = activity, factory = VMFactory)\n                    LaunchedEffect(Unit) {\n                        vm.authenticate = { onSuccess ->\n                            activity.extension.withAuthentication { if (it) onSuccess() }\n                        }\n                        vm.startLoading()\n                    }\n                    SuperuserScreen(vm)\n                }\n                Tab.LOG -> {\n                    val vm: LogViewModel = viewModel(factory = VMFactory)\n                    LaunchedEffect(Unit) { vm.startLoading() }\n                    LogScreen(vm)\n                }\n                Tab.MODULES -> {\n                    val vm: ModuleViewModel = viewModel(factory = VMFactory)\n                    LaunchedEffect(Unit) { vm.startLoading() }\n                    CollectNavEvents(vm, navigator)\n                    ModuleScreen(vm)\n                }\n                Tab.SETTINGS -> {\n                    val activity = LocalContext.current as MainActivity\n                    val vm: SettingsViewModel = viewModel(factory = VMFactory)\n                    LaunchedEffect(Unit) {\n                        vm.authenticate = { onSuccess ->\n                            activity.extension.withAuthentication { if (it) onSuccess() }\n                        }\n                    }\n                    CollectNavEvents(vm, navigator)\n                    SettingsScreen(vm)\n                }\n            }\n        }\n\n        FloatingNavigationBar(\n            pagerState = pagerState,\n            visibleTabs = visibleTabs,\n            modifier = Modifier.align(Alignment.BottomCenter)\n        )\n    }\n}\n\n@Composable\nprivate fun FloatingNavigationBar(\n    pagerState: PagerState,\n    visibleTabs: List<Tab>,\n    modifier: Modifier = Modifier\n) {\n    val scope = rememberCoroutineScope()\n    val shape = RoundedCornerShape(28.dp)\n    val navBarInset = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()\n\n    Row(\n        modifier = modifier\n            .padding(bottom = navBarInset + 12.dp, start = 24.dp, end = 24.dp)\n            .shadow(elevation = 6.dp, shape = shape)\n            .clip(shape)\n            .background(MiuixTheme.colorScheme.surfaceContainer)\n            .fillMaxWidth()\n            .height(64.dp)\n            .padding(horizontal = 4.dp),\n        horizontalArrangement = Arrangement.SpaceEvenly,\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        visibleTabs.forEachIndexed { index, tab ->\n            FloatingNavItem(\n                icon = ImageVector.vectorResource(tab.iconRes),\n                label = stringResource(tab.titleRes),\n                selected = pagerState.currentPage == index,\n                enabled = true,\n                onClick = { scope.launch { pagerState.animateScrollToPage(index) } },\n                modifier = Modifier.weight(1f)\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun FloatingNavItem(\n    icon: ImageVector,\n    label: String,\n    selected: Boolean,\n    enabled: Boolean,\n    onClick: () -> Unit,\n    modifier: Modifier = Modifier\n) {\n    val contentColor by animateColorAsState(\n        targetValue = when {\n            !enabled -> MiuixTheme.colorScheme.disabledOnSecondaryVariant\n            selected -> MiuixTheme.colorScheme.primary\n            else -> MiuixTheme.colorScheme.onSurfaceVariantActions\n        },\n        animationSpec = tween(200),\n        label = \"navItemColor\"\n    )\n\n    Column(\n        modifier = modifier\n            .clickable(\n                enabled = enabled,\n                indication = null,\n                interactionSource = remember { MutableInteractionSource() },\n                role = Role.Tab,\n                onClick = onClick,\n            ),\n        horizontalAlignment = Alignment.CenterHorizontally,\n        verticalArrangement = Arrangement.Center,\n    ) {\n        Icon(\n            imageVector = icon,\n            contentDescription = label,\n            modifier = Modifier.size(24.dp),\n            tint = contentColor,\n        )\n        Spacer(Modifier.height(2.dp))\n        Text(\n            text = label,\n            fontSize = 11.sp,\n            color = contentColor,\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/component/Dialog.kt",
    "content": "package com.topjohnwu.magisk.ui.component\n\nimport android.widget.TextView\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.heightIn\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.rememberUpdatedState\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.layout.Layout\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.viewinterop.AndroidView\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport kotlinx.coroutines.CancellableContinuation\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.flow.consumeAsFlow\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport top.yukonga.miuix.kmp.basic.ButtonDefaults\nimport top.yukonga.miuix.kmp.basic.InfiniteProgressIndicator\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TextButton\nimport top.yukonga.miuix.kmp.extra.SuperDialog\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport java.io.IOException\nimport kotlin.coroutines.resume\n\nsealed interface ConfirmResult {\n    data object Confirmed : ConfirmResult\n    data object Canceled : ConfirmResult\n}\n\ndata class DialogVisuals(\n    val title: String = \"\",\n    val content: String? = null,\n    val markdown: Boolean = false,\n    val confirm: String? = null,\n    val dismiss: String? = null,\n)\n\ninterface LoadingDialogHandle {\n    suspend fun <R> withLoading(block: suspend () -> R): R\n}\n\ninterface ConfirmDialogHandle {\n    fun showConfirm(\n        title: String,\n        content: String? = null,\n        markdown: Boolean = false,\n        confirm: String? = null,\n        dismiss: String? = null\n    )\n\n    suspend fun awaitConfirm(\n        title: String,\n        content: String? = null,\n        markdown: Boolean = false,\n        confirm: String? = null,\n        dismiss: String? = null\n    ): ConfirmResult\n}\n\nprivate class LoadingDialogHandleImpl(\n    private val visible: MutableState<Boolean>,\n    private val coroutineScope: CoroutineScope\n) : LoadingDialogHandle {\n    override suspend fun <R> withLoading(block: suspend () -> R): R {\n        return coroutineScope.async {\n            try {\n                visible.value = true\n                block()\n            } finally {\n                visible.value = false\n            }\n        }.await()\n    }\n}\n\nprivate class ConfirmDialogHandleImpl(\n    private val visible: MutableState<Boolean>,\n    private val coroutineScope: CoroutineScope,\n    private val callback: ConfirmCallback,\n    private val resultChannel: Channel<ConfirmResult>\n) : ConfirmDialogHandle {\n\n    var visuals by mutableStateOf(DialogVisuals())\n        private set\n\n    private var awaitContinuation: CancellableContinuation<ConfirmResult>? = null\n\n    init {\n        coroutineScope.launch {\n            resultChannel\n                .consumeAsFlow()\n                .onEach { result ->\n                    awaitContinuation?.let {\n                        awaitContinuation = null\n                        if (it.isActive) it.resume(result)\n                    }\n                }\n                .onEach { visible.value = false }\n                .collect { result ->\n                    when (result) {\n                        ConfirmResult.Confirmed -> callback.onConfirm?.invoke()\n                        ConfirmResult.Canceled -> callback.onDismiss?.invoke()\n                    }\n                }\n        }\n    }\n\n    override fun showConfirm(\n        title: String,\n        content: String?,\n        markdown: Boolean,\n        confirm: String?,\n        dismiss: String?\n    ) {\n        coroutineScope.launch {\n            visuals = DialogVisuals(title, content, markdown, confirm, dismiss)\n            visible.value = true\n        }\n    }\n\n    override suspend fun awaitConfirm(\n        title: String,\n        content: String?,\n        markdown: Boolean,\n        confirm: String?,\n        dismiss: String?\n    ): ConfirmResult {\n        coroutineScope.launch {\n            visuals = DialogVisuals(title, content, markdown, confirm, dismiss)\n            visible.value = true\n        }\n        return suspendCancellableCoroutine { cont ->\n            awaitContinuation = cont.apply {\n                invokeOnCancellation { visible.value = false }\n            }\n        }\n    }\n}\n\ninterface ConfirmCallback {\n    val onConfirm: (() -> Unit)?\n    val onDismiss: (() -> Unit)?\n}\n\n@Composable\nfun rememberConfirmCallback(\n    onConfirm: (() -> Unit)? = null,\n    onDismiss: (() -> Unit)? = null\n): ConfirmCallback {\n    val currentOnConfirm by rememberUpdatedState(onConfirm)\n    val currentOnDismiss by rememberUpdatedState(onDismiss)\n    return remember {\n        object : ConfirmCallback {\n            override val onConfirm get() = currentOnConfirm\n            override val onDismiss get() = currentOnDismiss\n        }\n    }\n}\n\n@Composable\nfun rememberLoadingDialog(): LoadingDialogHandle {\n    val visible = remember { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n    LoadingDialog(visible)\n    return remember { LoadingDialogHandleImpl(visible, scope) }\n}\n\n@Composable\nfun rememberConfirmDialog(\n    onConfirm: (() -> Unit)? = null,\n    onDismiss: (() -> Unit)? = null\n): ConfirmDialogHandle {\n    return rememberConfirmDialog(rememberConfirmCallback(onConfirm, onDismiss))\n}\n\n@Composable\nfun rememberConfirmDialog(callback: ConfirmCallback): ConfirmDialogHandle {\n    val visible = rememberSaveable { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n    val resultChannel = remember { Channel<ConfirmResult>() }\n\n    val handle = remember {\n        ConfirmDialogHandleImpl(visible, scope, callback, resultChannel)\n    }\n\n    if (visible.value) {\n        ConfirmDialogContent(\n            visuals = handle.visuals,\n            confirm = { scope.launch { resultChannel.send(ConfirmResult.Confirmed) } },\n            dismiss = { scope.launch { resultChannel.send(ConfirmResult.Canceled) } },\n            showDialog = visible\n        )\n    }\n\n    return handle\n}\n\n@Composable\nprivate fun LoadingDialog(showDialog: MutableState<Boolean>) {\n    SuperDialog(\n        show = showDialog,\n        onDismissRequest = {},\n        content = {\n            Box(\n                modifier = Modifier.fillMaxWidth(),\n                contentAlignment = Alignment.CenterStart\n            ) {\n                Row(\n                    verticalAlignment = Alignment.CenterVertically,\n                    horizontalArrangement = Arrangement.Start,\n                ) {\n                    InfiniteProgressIndicator(\n                        color = MiuixTheme.colorScheme.onBackground\n                    )\n                    Text(\n                        modifier = Modifier.padding(start = 12.dp),\n                        text = stringResource(com.topjohnwu.magisk.core.R.string.loading),\n                    )\n                }\n            }\n        }\n    )\n}\n\n@Composable\nprivate fun ConfirmDialogContent(\n    visuals: DialogVisuals,\n    confirm: () -> Unit,\n    dismiss: () -> Unit,\n    showDialog: MutableState<Boolean>\n) {\n    SuperDialog(\n        modifier = Modifier.windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Top)),\n        show = showDialog,\n        title = visuals.title,\n        onDismissRequest = {\n            dismiss()\n            showDialog.value = false\n        },\n        content = {\n            Layout(\n                content = {\n                    visuals.content?.let { content ->\n                        if (visuals.markdown) {\n                            MarkdownText(content)\n                        } else {\n                            Text(\n                                text = content,\n                                color = MiuixTheme.colorScheme.onSurface,\n                            )\n                        }\n                    }\n                    Row(\n                        horizontalArrangement = Arrangement.SpaceBetween,\n                        modifier = Modifier.padding(top = 12.dp)\n                    ) {\n                        TextButton(\n                            text = visuals.dismiss\n                                ?: stringResource(android.R.string.cancel),\n                            onClick = {\n                                dismiss()\n                                showDialog.value = false\n                            },\n                            modifier = Modifier.weight(1f)\n                        )\n                        Spacer(Modifier.width(20.dp))\n                        TextButton(\n                            text = visuals.confirm\n                                ?: stringResource(android.R.string.ok),\n                            onClick = {\n                                confirm()\n                                showDialog.value = false\n                            },\n                            modifier = Modifier.weight(1f),\n                            colors = ButtonDefaults.textButtonColorsPrimary()\n                        )\n                    }\n                }\n            ) { measurables, constraints ->\n                if (measurables.size != 2) {\n                    val button = measurables[0].measure(constraints)\n                    layout(constraints.maxWidth, button.height) {\n                        button.place(0, 0)\n                    }\n                } else {\n                    val button = measurables[1].measure(constraints)\n                    val content = measurables[0].measure(\n                        constraints.copy(maxHeight = constraints.maxHeight - button.height)\n                    )\n                    layout(constraints.maxWidth, content.height + button.height) {\n                        content.place(0, 0)\n                        button.place(0, content.height)\n                    }\n                }\n            }\n        }\n    )\n}\n\n@Composable\nfun MarkdownText(text: String) {\n    val contentColor = MiuixTheme.colorScheme.onBackground.toArgb()\n    AndroidView(\n        factory = { context ->\n            TextView(context).apply {\n                setTextColor(contentColor)\n                ServiceLocator.markwon.setMarkdown(this, text)\n            }\n        },\n        update = { textView ->\n            textView.setTextColor(contentColor)\n            ServiceLocator.markwon.setMarkdown(textView, text)\n        },\n        modifier = Modifier\n            .fillMaxWidth()\n            .heightIn(max = 300.dp)\n    )\n}\n\n@Composable\nfun MarkdownTextAsync(getMarkdownText: suspend () -> String) {\n    var mdText by remember { mutableStateOf<String?>(null) }\n    var error by remember { mutableStateOf(false) }\n\n    LaunchedEffect(Unit) {\n        try {\n            mdText = withContext(Dispatchers.IO) { getMarkdownText() }\n        } catch (e: IOException) {\n            Timber.e(e)\n            error = true\n        }\n    }\n\n    when {\n        error -> Text(stringResource(com.topjohnwu.magisk.core.R.string.download_file_error))\n        mdText != null -> MarkdownText(mdText!!)\n        else -> Box(\n            modifier = Modifier.fillMaxWidth(),\n            contentAlignment = Alignment.Center\n        ) {\n            InfiniteProgressIndicator(color = MiuixTheme.colorScheme.onBackground)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/component/MenuPositionProvider.kt",
    "content": "package com.topjohnwu.magisk.ui.component\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.unit.IntRect\nimport androidx.compose.ui.unit.IntSize\nimport androidx.compose.ui.unit.LayoutDirection\nimport androidx.compose.ui.unit.dp\nimport top.yukonga.miuix.kmp.basic.PopupPositionProvider\n\nobject ListPopupDefaults {\n    val MenuPositionProvider = object : PopupPositionProvider {\n        override fun calculatePosition(\n            anchorBounds: IntRect,\n            windowBounds: IntRect,\n            layoutDirection: LayoutDirection,\n            popupContentSize: IntSize,\n            popupMargin: IntRect,\n            alignment: PopupPositionProvider.Align,\n        ): IntOffset {\n            val resolved = alignment.resolve(layoutDirection)\n            val offsetX: Int\n            val offsetY: Int\n            when (resolved) {\n                PopupPositionProvider.Align.TopStart -> {\n                    offsetX = anchorBounds.left + popupMargin.left\n                    offsetY = anchorBounds.bottom + popupMargin.top\n                }\n                PopupPositionProvider.Align.TopEnd -> {\n                    offsetX = anchorBounds.right - popupContentSize.width - popupMargin.right\n                    offsetY = anchorBounds.bottom + popupMargin.top\n                }\n                PopupPositionProvider.Align.BottomStart -> {\n                    offsetX = anchorBounds.left + popupMargin.left\n                    offsetY = anchorBounds.top - popupContentSize.height - popupMargin.bottom\n                }\n                PopupPositionProvider.Align.BottomEnd -> {\n                    offsetX = anchorBounds.right - popupContentSize.width - popupMargin.right\n                    offsetY = anchorBounds.top - popupContentSize.height - popupMargin.bottom\n                }\n                else -> {\n                    offsetX = if (resolved == PopupPositionProvider.Align.End) {\n                        anchorBounds.right - popupContentSize.width - popupMargin.right\n                    } else {\n                        anchorBounds.left + popupMargin.left\n                    }\n                    offsetY = if (windowBounds.bottom - anchorBounds.bottom > popupContentSize.height) {\n                        anchorBounds.bottom + popupMargin.bottom\n                    } else if (anchorBounds.top - windowBounds.top > popupContentSize.height) {\n                        anchorBounds.top - popupContentSize.height - popupMargin.top\n                    } else {\n                        anchorBounds.top + anchorBounds.height / 2 - popupContentSize.height / 2\n                    }\n                }\n            }\n            return IntOffset(\n                x = offsetX.coerceIn(\n                    windowBounds.left,\n                    (windowBounds.right - popupContentSize.width - popupMargin.right)\n                        .coerceAtLeast(windowBounds.left),\n                ),\n                y = offsetY.coerceIn(\n                    (windowBounds.top + popupMargin.top)\n                        .coerceAtMost(windowBounds.bottom - popupContentSize.height - popupMargin.bottom),\n                    windowBounds.bottom - popupContentSize.height - popupMargin.bottom,\n                ),\n            )\n        }\n\n        override fun getMargins(): PaddingValues = PaddingValues(start = 20.dp)\n    }\n}\n\nprivate fun PopupPositionProvider.Align.resolve(layoutDirection: LayoutDirection): PopupPositionProvider.Align {\n    if (layoutDirection == LayoutDirection.Ltr) return this\n    return when (this) {\n        PopupPositionProvider.Align.Start -> PopupPositionProvider.Align.End\n        PopupPositionProvider.Align.End -> PopupPositionProvider.Align.Start\n        PopupPositionProvider.Align.TopStart -> PopupPositionProvider.Align.TopEnd\n        PopupPositionProvider.Align.TopEnd -> PopupPositionProvider.Align.TopStart\n        PopupPositionProvider.Align.BottomStart -> PopupPositionProvider.Align.BottomEnd\n        PopupPositionProvider.Align.BottomEnd -> PopupPositionProvider.Align.BottomStart\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/deny/AppProcessInfo.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.ComponentInfo\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.GET_ACTIVITIES\nimport android.content.pm.PackageManager.GET_PROVIDERS\nimport android.content.pm.PackageManager.GET_RECEIVERS\nimport android.content.pm.PackageManager.GET_SERVICES\nimport android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport android.content.pm.ServiceInfo\nimport android.graphics.drawable.Drawable\nimport androidx.core.os.ProcessCompat\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport java.util.Locale\nimport java.util.TreeSet\n\nclass CmdlineListItem(line: String) {\n    val packageName: String\n    val process: String\n\n    init {\n        val split = line.split(Regex(\"\\\\|\"), 2)\n        packageName = split[0]\n        process = split.getOrElse(1) { packageName }\n    }\n}\n\nconst val ISOLATED_MAGIC = \"isolated\"\n\n@SuppressLint(\"InlinedApi\")\nclass AppProcessInfo(\n    private val info: ApplicationInfo,\n    pm: PackageManager,\n    denyList: List<CmdlineListItem>\n) : Comparable<AppProcessInfo> {\n\n    private val denyList = denyList.filter {\n        it.packageName == info.packageName || it.packageName == ISOLATED_MAGIC\n    }\n\n    val label = info.getLabel(pm)\n    val iconImage: Drawable = runCatching { info.loadIcon(pm) }.getOrDefault(pm.defaultActivityIcon)\n    val packageName: String get() = info.packageName\n    var firstInstallTime: Long = 0L\n        private set\n    var lastUpdateTime: Long = 0L\n        private set\n    val processes = fetchProcesses(pm)\n\n    override fun compareTo(other: AppProcessInfo) = comparator.compare(this, other)\n\n    fun isSystemApp() = info.flags and ApplicationInfo.FLAG_SYSTEM != 0\n\n    fun isApp() = ProcessCompat.isApplicationUid(info.uid)\n\n    private fun createProcess(name: String, pkg: String = info.packageName) =\n        ProcessInfo(name, pkg, denyList.any { it.process == name && it.packageName == pkg })\n\n    private fun ComponentInfo.getProcName(): String = processName\n        ?: applicationInfo.processName\n        ?: applicationInfo.packageName\n\n    private val ServiceInfo.isIsolated get() = (flags and ServiceInfo.FLAG_ISOLATED_PROCESS) != 0\n    private val ServiceInfo.useAppZygote get() = (flags and ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0\n\n    private fun Array<out ComponentInfo>?.toProcessList() =\n        orEmpty().map { createProcess(it.getProcName()) }\n\n    private fun Array<ServiceInfo>?.toProcessList(): List<ProcessInfo> {\n        if (this == null) return emptyList()\n        val result = mutableListOf<ProcessInfo>()\n        var hasIsolated = false\n        for (si in this) {\n            if (si.isIsolated) {\n                if (si.useAppZygote) {\n                    val proc = info.processName ?: info.packageName\n                    result.add(createProcess(\"${proc}_zygote\"))\n                } else {\n                    hasIsolated = true\n                }\n            } else {\n                result.add(createProcess(si.getProcName()))\n            }\n        }\n        if (hasIsolated) {\n            val prefix = \"${info.processName ?: info.packageName}:\"\n            val isEnabled = denyList.any {\n                it.packageName == ISOLATED_MAGIC && it.process.startsWith(prefix)\n            }\n            result.add(ProcessInfo(prefix, ISOLATED_MAGIC, isEnabled))\n        }\n        return result\n    }\n\n    private fun fetchProcesses(pm: PackageManager): Collection<ProcessInfo> {\n        val flag = MATCH_DISABLED_COMPONENTS or MATCH_UNINSTALLED_PACKAGES or\n            GET_ACTIVITIES or GET_SERVICES or GET_RECEIVERS or GET_PROVIDERS\n        val packageInfo = try {\n            pm.getPackageInfo(info.packageName, flag)\n        } catch (e: Exception) {\n            // Exceed binder data transfer limit, parse the package locally\n            pm.getPackageArchiveInfo(info.sourceDir, flag) ?: return emptyList()\n        }\n\n        firstInstallTime = packageInfo.firstInstallTime\n        lastUpdateTime = packageInfo.lastUpdateTime\n\n        val processSet = TreeSet<ProcessInfo>(compareBy({ it.name }, { it.isIsolated }))\n        processSet += packageInfo.activities.toProcessList()\n        processSet += packageInfo.services.toProcessList()\n        processSet += packageInfo.receivers.toProcessList()\n        processSet += packageInfo.providers.toProcessList()\n        return processSet\n    }\n\n    companion object {\n        private val comparator = compareBy<AppProcessInfo>(\n            { it.label.lowercase(Locale.ROOT) },\n            { it.info.packageName }\n        )\n    }\n}\n\ndata class ProcessInfo(\n    val name: String,\n    val packageName: String,\n    var isEnabled: Boolean\n) {\n    val isIsolated = packageName == ISOLATED_MAGIC\n    val isAppZygote = name.endsWith(\"_zygote\")\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.state.ToggleableState\nimport androidx.compose.ui.unit.dp\nimport com.topjohnwu.magisk.ui.component.ListPopupDefaults.MenuPositionProvider\nimport com.topjohnwu.magisk.ui.util.rememberDrawablePainter\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.Checkbox\nimport top.yukonga.miuix.kmp.basic.CircularProgressIndicator\nimport top.yukonga.miuix.kmp.basic.DropdownImpl\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.LinearProgressIndicator\nimport top.yukonga.miuix.kmp.basic.ListPopupColumn\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.PopupPositionProvider\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.extra.SuperListPopup\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Back\nimport top.yukonga.miuix.kmp.icon.extended.Sort\nimport top.yukonga.miuix.kmp.icon.extended.Tune\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun DenyListScreen(viewModel: DenyListViewModel, onBack: () -> Unit) {\n    val loading by viewModel.loading.collectAsState()\n    val apps by viewModel.filteredApps.collectAsState()\n    val query by viewModel.query.collectAsState()\n    val showSystem by viewModel.showSystem.collectAsState()\n    val showOS by viewModel.showOS.collectAsState()\n    val sortBy by viewModel.sortBy.collectAsState()\n    val sortReverse by viewModel.sortReverse.collectAsState()\n\n    val showSortMenu = remember { mutableStateOf(false) }\n    val showFilterMenu = remember { mutableStateOf(false) }\n\n    val scrollBehavior = MiuixScrollBehavior()\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.denylist),\n                navigationIcon = {\n                    IconButton(\n                        modifier = Modifier.padding(start = 16.dp),\n                        onClick = onBack\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Back,\n                            contentDescription = null,\n                            tint = MiuixTheme.colorScheme.onBackground\n                        )\n                    }\n                },\n                actions = {\n                    Box {\n                        IconButton(\n                            onClick = { showSortMenu.value = true },\n                            holdDownState = showSortMenu.value,\n                        ) {\n                            Icon(\n                                imageVector = MiuixIcons.Sort,\n                                contentDescription = stringResource(CoreR.string.menu_sort),\n                            )\n                        }\n                        SuperListPopup(\n                            show = showSortMenu,\n                            popupPositionProvider = MenuPositionProvider,\n                            alignment = PopupPositionProvider.Align.End,\n                            onDismissRequest = { showSortMenu.value = false }\n                        ) {\n                            ListPopupColumn {\n                                val sortOptions = listOf(\n                                    CoreR.string.sort_by_name to SortBy.NAME,\n                                    CoreR.string.sort_by_package_name to SortBy.PACKAGE_NAME,\n                                    CoreR.string.sort_by_install_time to SortBy.INSTALL_TIME,\n                                    CoreR.string.sort_by_update_time to SortBy.UPDATE_TIME,\n                                )\n                                val totalSize = sortOptions.size + 1\n                                sortOptions.forEachIndexed { index, (resId, sort) ->\n                                    DropdownImpl(\n                                        text = stringResource(resId),\n                                        optionSize = totalSize,\n                                        isSelected = sortBy == sort,\n                                        index = index,\n                                        onSelectedIndexChange = {\n                                            viewModel.setSortBy(sort)\n                                            showSortMenu.value = false\n                                        }\n                                    )\n                                }\n                                DropdownImpl(\n                                    text = stringResource(CoreR.string.sort_reverse),\n                                    optionSize = totalSize,\n                                    isSelected = sortReverse,\n                                    index = sortOptions.size,\n                                    onSelectedIndexChange = {\n                                        viewModel.toggleSortReverse()\n                                        showSortMenu.value = false\n                                    }\n                                )\n                            }\n                        }\n                    }\n\n                    Box {\n                        IconButton(\n                            modifier = Modifier.padding(end = 16.dp),\n                            onClick = { showFilterMenu.value = true },\n                            holdDownState = showFilterMenu.value,\n                        ) {\n                            Icon(\n                                imageVector = MiuixIcons.Tune,\n                                contentDescription = stringResource(CoreR.string.hide_filter_hint),\n                            )\n                        }\n                        SuperListPopup(\n                            show = showFilterMenu,\n                            popupPositionProvider = MenuPositionProvider,\n                            alignment = PopupPositionProvider.Align.End,\n                            onDismissRequest = { showFilterMenu.value = false }\n                        ) {\n                            ListPopupColumn {\n                                DropdownImpl(\n                                    text = stringResource(CoreR.string.show_system_app),\n                                    optionSize = 2,\n                                    isSelected = showSystem,\n                                    index = 0,\n                                    onSelectedIndexChange = {\n                                        viewModel.setShowSystem(!showSystem)\n                                        showFilterMenu.value = false\n                                    }\n                                )\n                                DropdownImpl(\n                                    text = stringResource(CoreR.string.show_os_app),\n                                    optionSize = 2,\n                                    isSelected = showOS,\n                                    index = 1,\n                                    onSelectedIndexChange = {\n                                        if (!showOS && !showSystem) {\n                                            viewModel.setShowSystem(true)\n                                        }\n                                        viewModel.setShowOS(!showOS)\n                                        showFilterMenu.value = false\n                                    }\n                                )\n                            }\n                        }\n                    }\n                },\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        Column(modifier = Modifier.fillMaxSize().padding(padding)) {\n            SearchInput(\n                query = query,\n                onQueryChange = viewModel::setQuery,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 12.dp, vertical = 4.dp)\n            )\n\n            if (loading) {\n                Box(\n                    modifier = Modifier.fillMaxSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Column(horizontalAlignment = Alignment.CenterHorizontally) {\n                        Text(\n                            text = stringResource(CoreR.string.loading),\n                            style = MiuixTheme.textStyles.headline2\n                        )\n                        Spacer(Modifier.height(16.dp))\n                        CircularProgressIndicator()\n                    }\n                }\n            } else {\n                LazyColumn(\n                    modifier = Modifier\n                        .fillMaxSize()\n                        .nestedScroll(scrollBehavior.nestedScrollConnection)\n                        .padding(horizontal = 12.dp),\n                    contentPadding = PaddingValues(top = 8.dp, bottom = 88.dp),\n                    verticalArrangement = Arrangement.spacedBy(8.dp)\n                ) {\n                    items(\n                        items = apps,\n                        key = { it.info.packageName }\n                    ) { app ->\n                        DenyAppCard(app)\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SearchInput(query: String, onQueryChange: (String) -> Unit, modifier: Modifier = Modifier) {\n    top.yukonga.miuix.kmp.basic.TextField(\n        value = query,\n        onValueChange = onQueryChange,\n        modifier = modifier,\n        label = stringResource(CoreR.string.hide_filter_hint)\n    )\n}\n\n@Composable\nprivate fun DenyAppCard(app: DenyAppState) {\n    Card(modifier = Modifier.fillMaxWidth()) {\n        Column {\n            if (app.checkedPercent > 0f) {\n                LinearProgressIndicator(\n                    progress = app.checkedPercent,\n                    modifier = Modifier.fillMaxWidth()\n                )\n            }\n\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable { app.isExpanded = !app.isExpanded }\n                    .padding(12.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Image(\n                    painter = rememberDrawablePainter(app.info.iconImage),\n                    contentDescription = app.info.label,\n                    modifier = Modifier.size(40.dp)\n                )\n                Spacer(Modifier.width(12.dp))\n                Column(modifier = Modifier.weight(1f)) {\n                    Text(\n                        text = app.info.label,\n                        style = MiuixTheme.textStyles.body1,\n                    )\n                    Text(\n                        text = app.info.packageName,\n                        style = MiuixTheme.textStyles.body2,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary\n                    )\n                }\n                Spacer(Modifier.width(8.dp))\n                Checkbox(\n                    state = when {\n                        app.itemsChecked == 0 -> ToggleableState.Off\n                        app.checkedPercent < 1f -> ToggleableState.Indeterminate\n                        else -> ToggleableState.On\n                    },\n                    onClick = { app.toggleAll() }\n                )\n            }\n\n            AnimatedVisibility(visible = app.isExpanded) {\n                Column(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(start = 52.dp)\n                ) {\n                    app.processes.forEach { proc ->\n                        ProcessRow(proc)\n                    }\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun ProcessRow(proc: DenyProcessState) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable { proc.toggle() }\n            .padding(horizontal = 12.dp, vertical = 6.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Text(\n            text = proc.displayName,\n            style = MiuixTheme.textStyles.body2,\n            color = if (proc.isEnabled) MiuixTheme.colorScheme.onSurface\n                else MiuixTheme.colorScheme.onSurfaceVariantSummary,\n            modifier = Modifier.weight(1f)\n        )\n        Spacer(Modifier.width(8.dp))\n        Checkbox(\n            state = ToggleableState(proc.isEnabled),\n            onClick = { proc.toggle() }\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/deny/DenyListViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.deny\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.ktx.concurrentMap\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.SharingStarted\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.filter\nimport kotlinx.coroutines.flow.stateIn\nimport kotlinx.coroutines.flow.toCollection\nimport kotlinx.coroutines.withContext\n\nenum class SortBy { NAME, PACKAGE_NAME, INSTALL_TIME, UPDATE_TIME }\n\nclass DenyListViewModel : AsyncLoadViewModel() {\n\n    private val _loading = MutableStateFlow(true)\n    val loading: StateFlow<Boolean> = _loading.asStateFlow()\n\n    private val _allApps = MutableStateFlow<List<DenyAppState>>(emptyList())\n\n    private val _query = MutableStateFlow(\"\")\n    val query: StateFlow<String> = _query.asStateFlow()\n\n    private val _showSystem = MutableStateFlow(false)\n    val showSystem: StateFlow<Boolean> = _showSystem.asStateFlow()\n\n    private val _showOS = MutableStateFlow(false)\n    val showOS: StateFlow<Boolean> = _showOS.asStateFlow()\n\n    private val _sortBy = MutableStateFlow(SortBy.NAME)\n    val sortBy: StateFlow<SortBy> = _sortBy.asStateFlow()\n\n    private val _sortReverse = MutableStateFlow(false)\n    val sortReverse: StateFlow<Boolean> = _sortReverse.asStateFlow()\n\n    val filteredApps: StateFlow<List<DenyAppState>> = combine(\n        _allApps, _query, _showSystem, _showOS, _sortBy, _sortReverse\n    ) { args ->\n        @Suppress(\"UNCHECKED_CAST\")\n        val apps = args[0] as List<DenyAppState>\n        val q = args[1] as String\n        val showSys = args[2] as Boolean\n        val showOS = args[3] as Boolean\n        val sort = args[4] as SortBy\n        val reverse = args[5] as Boolean\n\n        val filtered = apps.filter { app ->\n            val passFilter = app.isChecked ||\n                ((showSys || !app.info.isSystemApp()) &&\n                ((showSys && showOS) || app.info.isApp()))\n            val passQuery = q.isBlank() ||\n                app.info.label.contains(q, true) ||\n                app.info.packageName.contains(q, true) ||\n                app.processes.any { it.process.name.contains(q, true) }\n            passFilter && passQuery\n        }\n\n        val secondary: Comparator<DenyAppState> = when (sort) {\n            SortBy.NAME -> compareBy(String.CASE_INSENSITIVE_ORDER) { it.info.label }\n            SortBy.PACKAGE_NAME -> compareBy(String.CASE_INSENSITIVE_ORDER) { it.info.packageName }\n            SortBy.INSTALL_TIME -> compareByDescending { it.info.firstInstallTime }\n            SortBy.UPDATE_TIME -> compareByDescending { it.info.lastUpdateTime }\n        }\n        val comparator = compareBy<DenyAppState> { it.itemsChecked == 0 }\n            .then(if (reverse) secondary.reversed() else secondary)\n        filtered.sortedWith(comparator)\n    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())\n\n    fun setQuery(q: String) { _query.value = q }\n    fun setShowSystem(v: Boolean) {\n        _showSystem.value = v\n        if (!v) _showOS.value = false\n    }\n    fun setShowOS(v: Boolean) { _showOS.value = v }\n    fun setSortBy(s: SortBy) { _sortBy.value = s }\n    fun toggleSortReverse() { _sortReverse.value = !_sortReverse.value }\n\n    @SuppressLint(\"InlinedApi\")\n    override suspend fun doLoadWork() {\n        _loading.value = true\n        val apps = withContext(Dispatchers.Default) {\n            val pm = AppContext.packageManager\n            val denyList = Shell.cmd(\"magisk --denylist ls\").exec().out\n                .map { CmdlineListItem(it) }\n            val apps = pm.getInstalledApplications(MATCH_UNINSTALLED_PACKAGES).run {\n                asFlow()\n                    .filter { AppContext.packageName != it.packageName }\n                    .concurrentMap { AppProcessInfo(it, pm, denyList) }\n                    .filter { it.processes.isNotEmpty() }\n                    .concurrentMap { DenyAppState(it) }\n                    .toCollection(ArrayList(size))\n            }\n            apps.sortWith(compareBy(\n                { it.processes.count { p -> p.isEnabled } == 0 },\n                { it.info }\n            ))\n            apps\n        }\n        _allApps.value = apps\n        _loading.value = false\n    }\n}\n\nclass DenyAppState(val info: AppProcessInfo) : Comparable<DenyAppState> {\n    val processes = info.processes.map { DenyProcessState(it) }\n    var isExpanded by mutableStateOf(false)\n\n    val itemsChecked: Int get() = processes.count { it.isEnabled }\n    val isChecked: Boolean get() = itemsChecked > 0\n    val checkedPercent: Float get() = if (processes.isEmpty()) 0f else itemsChecked.toFloat() / processes.size\n\n    fun toggleAll() {\n        if (isChecked) {\n            Shell.cmd(\"magisk --denylist rm ${info.packageName}\").submit()\n            processes.filter { it.isEnabled }.forEach { proc ->\n                if (proc.process.isIsolated) {\n                    proc.toggle()\n                } else {\n                    proc.isEnabled = false\n                }\n            }\n        } else {\n            processes.filterNot { it.isEnabled }.forEach { it.toggle() }\n        }\n    }\n\n    override fun compareTo(other: DenyAppState) = comparator.compare(this, other)\n\n    companion object {\n        private val comparator = compareBy<DenyAppState>(\n            { it.itemsChecked == 0 },\n            { it.info }\n        )\n    }\n}\n\nclass DenyProcessState(val process: ProcessInfo) {\n    var isEnabled by mutableStateOf(process.isEnabled)\n\n    val displayName: String =\n        if (process.isIsolated) \"(isolated) ${process.name}*\" else process.name\n\n    fun toggle() {\n        isEnabled = !isEnabled\n        val arg = if (isEnabled) \"add\" else \"rm\"\n        val (name, pkg) = process\n        Shell.cmd(\"magisk --denylist $arg $pkg \\'$name\\'\").submit()\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/flash/FlashScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport androidx.compose.foundation.horizontalScroll\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.ui.terminal.TerminalScreen\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.SmallTopAppBar\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Back\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun FlashScreen(viewModel: FlashViewModel, action: String, onBack: () -> Unit) {\n    val flashState by viewModel.flashState.collectAsState()\n    val showReboot by viewModel.showReboot.collectAsState()\n    val finished = flashState != FlashViewModel.State.FLASHING\n    val useTerminal = action == Const.Value.FLASH_ZIP\n\n    val statusText = when (flashState) {\n        FlashViewModel.State.FLASHING -> stringResource(CoreR.string.flashing)\n        FlashViewModel.State.SUCCESS -> stringResource(CoreR.string.done)\n        FlashViewModel.State.FAILED -> stringResource(CoreR.string.failure)\n    }\n\n    val scrollBehavior = MiuixScrollBehavior()\n    Scaffold(\n        topBar = {\n            SmallTopAppBar(\n                title = \"${stringResource(CoreR.string.flash_screen_title)} - $statusText\",\n                navigationIcon = {\n                    IconButton(\n                        modifier = Modifier.padding(start = 16.dp),\n                        onClick = onBack\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Back,\n                            contentDescription = null,\n                            tint = MiuixTheme.colorScheme.onBackground\n                        )\n                    }\n                },\n                actions = {\n                    if (finished) {\n                        IconButton(\n                            modifier = Modifier.padding(end = 4.dp),\n                            onClick = { viewModel.saveLog() }\n                        ) {\n                            Icon(\n                                painter = painterResource(R.drawable.ic_save_md2),\n                                contentDescription = stringResource(CoreR.string.menuSaveLog),\n                                tint = MiuixTheme.colorScheme.onBackground\n                            )\n                        }\n                    }\n                    if (flashState == FlashViewModel.State.SUCCESS && showReboot) {\n                        IconButton(\n                            modifier = Modifier.padding(end = 16.dp),\n                            onClick = { viewModel.restartPressed() }\n                        ) {\n                            Icon(\n                                painter = painterResource(R.drawable.ic_restart),\n                                contentDescription = stringResource(CoreR.string.reboot),\n                                tint = MiuixTheme.colorScheme.onBackground\n                            )\n                        }\n                    }\n                },\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        if (useTerminal) {\n            TerminalScreen(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding),\n                onEmulatorCreated = { viewModel.onEmulatorCreated(it) },\n            )\n        } else {\n            val items = viewModel.consoleItems\n            val listState = rememberLazyListState()\n\n            LaunchedEffect(items.size) {\n                if (items.isNotEmpty()) {\n                    listState.animateScrollToItem(items.size - 1)\n                }\n            }\n\n            LazyColumn(\n                state = listState,\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding)\n                    .horizontalScroll(rememberScrollState())\n                    .padding(horizontal = 8.dp, vertical = 4.dp)\n            ) {\n                itemsIndexed(items) { _, line ->\n                    Text(\n                        text = line,\n                        fontFamily = FontFamily.Monospace,\n                        fontSize = 12.sp,\n                        lineHeight = 16.sp,\n                        color = MiuixTheme.colorScheme.onSurface,\n                        modifier = Modifier.fillMaxWidth()\n                    )\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/flash/FlashUtils.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.cmp\nimport com.topjohnwu.magisk.ui.MainActivity\n\nobject FlashUtils {\n\n    const val INTENT_FLASH = \"com.topjohnwu.magisk.intent.FLASH\"\n    const val EXTRA_FLASH_ACTION = \"flash_action\"\n    const val EXTRA_FLASH_URI = \"flash_uri\"\n\n    fun installIntent(context: Context, file: Uri): PendingIntent {\n        val intent = Intent(context, MainActivity::class.java).apply {\n            component = MainActivity::class.java.cmp(context.packageName)\n            action = INTENT_FLASH\n            putExtra(EXTRA_FLASH_ACTION, Const.Value.FLASH_ZIP)\n            putExtra(EXTRA_FLASH_URI, file.toString())\n            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP\n        }\n        return PendingIntent.getActivity(\n            context, file.hashCode(), intent,\n            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/flash/FlashViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.flash\n\nimport android.net.Uri\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.core.net.toFile\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.ktx.reboot\nimport com.topjohnwu.magisk.core.ktx.synchronized\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.terminal.TerminalEmulator\nimport com.topjohnwu.magisk.terminal.appendLineOnMain\nimport com.topjohnwu.magisk.terminal.runSuCommand\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.CompletableDeferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.IOException\n\nclass FlashViewModel : BaseViewModel() {\n\n    enum class State {\n        FLASHING, SUCCESS, FAILED\n    }\n\n    private val _flashState = MutableStateFlow(State.FLASHING)\n    val flashState: StateFlow<State> = _flashState.asStateFlow()\n\n    private val _showReboot = MutableStateFlow(Info.isRooted)\n    val showReboot: StateFlow<Boolean> = _showReboot.asStateFlow()\n\n    var flashAction: String = \"\"\n    var flashUri: Uri? = null\n\n    // --- TerminalScreen mode (FLASH_ZIP) ---\n\n    private var emulator: TerminalEmulator? = null\n    private val emulatorReady = CompletableDeferred<TerminalEmulator>()\n\n    fun onEmulatorCreated(emu: TerminalEmulator) {\n        emulator = emu\n        emulatorReady.complete(emu)\n    }\n\n    // --- LazyColumn mode (MagiskInstaller) ---\n\n    val consoleItems = mutableStateListOf<String>()\n    private val logItems = mutableListOf<String>().synchronized()\n    private val outItems = object : CallbackList<String>() {\n        override fun onAddElement(e: String?) {\n            e ?: return\n            consoleItems.add(e)\n            logItems.add(e)\n        }\n    }\n\n    // --- Shared ---\n\n    fun startFlashing() {\n        val action = flashAction\n        val uri = flashUri\n\n        viewModelScope.launch {\n            when (action) {\n                Const.Value.FLASH_ZIP -> {\n                    uri ?: return@launch\n                    flashZip(uri)\n                }\n                Const.Value.UNINSTALL -> {\n                    _showReboot.value = false\n                    onResult(withContext(Dispatchers.IO) {\n                        MagiskInstaller.Uninstall(outItems, logItems).exec()\n                    })\n                }\n                Const.Value.FLASH_MAGISK -> {\n                    onResult(withContext(Dispatchers.IO) {\n                        if (Info.isEmulator)\n                            MagiskInstaller.Emulator(outItems, logItems).exec()\n                        else\n                            MagiskInstaller.Direct(outItems, logItems).exec()\n                    })\n                }\n                Const.Value.FLASH_INACTIVE_SLOT -> {\n                    _showReboot.value = false\n                    onResult(withContext(Dispatchers.IO) {\n                        MagiskInstaller.SecondSlot(outItems, logItems).exec()\n                    })\n                }\n                Const.Value.PATCH_FILE -> {\n                    uri ?: return@launch\n                    _showReboot.value = false\n                    onResult(withContext(Dispatchers.IO) {\n                        MagiskInstaller.Patch(uri, outItems, logItems).exec()\n                    })\n                }\n            }\n        }\n    }\n\n    private fun onResult(success: Boolean) {\n        _flashState.value = if (success) State.SUCCESS else State.FAILED\n    }\n\n    private suspend fun flashZip(uri: Uri) {\n        val emu = emulatorReady.await()\n\n        val installDir = File(AppContext.cacheDir, \"flash\")\n        val result = withContext(Dispatchers.IO) {\n            try {\n                installDir.deleteRecursively()\n                installDir.mkdirs()\n\n                val zipFile = if (uri.scheme == \"file\") {\n                    uri.toFile()\n                } else {\n                    File(installDir, \"install.zip\").also {\n                        try {\n                            uri.inputStream().writeTo(it)\n                        } catch (e: IOException) {\n                            val msg = if (e is FileNotFoundException) \"Invalid Uri\" else \"Cannot copy to cache\"\n                            return@withContext msg to null\n                        }\n                    }\n                }\n\n                val binary = File(installDir, \"update-binary\")\n                AppContext.assets.open(\"module_installer.sh\").use { it.writeTo(binary) }\n\n                val name = uri.displayName\n                null to Triple(installDir, zipFile, name)\n            } catch (e: IOException) {\n                Timber.e(e)\n                \"Unable to extract files\" to null\n            }\n        }\n\n        val (error, prepResult) = result\n        if (prepResult == null) {\n            emu.appendLineOnMain(\"! ${error ?: \"Installation failed\"}\")\n            _flashState.value = State.FAILED\n            return\n        }\n\n        val (dir, zipFile, displayName) = prepResult\n\n        val success = withContext(Dispatchers.IO) {\n            runSuCommand(\n                emu,\n                \"echo '- Installing $displayName'; \" +\n                \"sh $dir/update-binary dummy 1 '${zipFile.absolutePath}'; \" +\n                \"EXIT=\\$?; \" +\n                \"if [ \\$EXIT -ne 0 ]; then echo '! Installation failed'; fi; \" +\n                \"exit \\$EXIT\"\n            )\n        }\n\n        Shell.cmd(\"cd /\", \"rm -rf $dir ${Const.TMPDIR}\").submit()\n        _flashState.value = if (success) State.SUCCESS else State.FAILED\n    }\n\n    fun saveLog() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val name = \"magisk_install_log_%s.log\".format(\n                System.currentTimeMillis().toTime(timeFormatStandard)\n            )\n            val file = MediaStoreUtils.getFile(name)\n            file.uri.outputStream().bufferedWriter().use { writer ->\n                val transcript = emulator?.screen?.transcriptText\n                if (transcript != null) {\n                    writer.write(transcript)\n                } else {\n                    synchronized(logItems) {\n                        logItems.forEach {\n                            writer.write(it)\n                            writer.newLine()\n                        }\n                    }\n                }\n            }\n            showSnackbar(file.toString())\n        }\n    }\n\n    fun restartPressed() = reboot()\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Build\nimport android.os.PowerManager\nimport android.widget.Toast\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.DpSize\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.content.getSystemService\nimport androidx.core.net.toUri\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.ktx.reboot\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.ui.MainActivity\nimport com.topjohnwu.magisk.ui.component.ConfirmResult\nimport com.topjohnwu.magisk.ui.component.ListPopupDefaults.MenuPositionProvider\nimport com.topjohnwu.magisk.ui.component.LoadingDialogHandle\nimport com.topjohnwu.magisk.ui.component.MarkdownText\nimport com.topjohnwu.magisk.ui.component.MarkdownTextAsync\nimport com.topjohnwu.magisk.ui.component.rememberConfirmDialog\nimport com.topjohnwu.magisk.ui.component.rememberLoadingDialog\nimport com.topjohnwu.magisk.ui.flash.FlashUtils\nimport com.topjohnwu.magisk.ui.install.InstallViewModel\nimport kotlinx.coroutines.launch\nimport top.yukonga.miuix.kmp.basic.ButtonDefaults\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.Checkbox\nimport top.yukonga.miuix.kmp.basic.DropdownImpl\nimport top.yukonga.miuix.kmp.basic.HorizontalDivider\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.LinearProgressIndicator\nimport top.yukonga.miuix.kmp.basic.ListPopupColumn\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.PopupPositionProvider\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.SmallTitle\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TextButton\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.basic.VerticalDivider\nimport top.yukonga.miuix.kmp.extra.SuperArrow\nimport top.yukonga.miuix.kmp.extra.SuperBottomSheet\nimport top.yukonga.miuix.kmp.extra.SuperListPopup\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Close\nimport top.yukonga.miuix.kmp.icon.extended.Delete\nimport top.yukonga.miuix.kmp.icon.extended.Hide\nimport top.yukonga.miuix.kmp.icon.extended.Show\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun HomeScreen(viewModel: HomeViewModel, installVm: InstallViewModel) {\n    val uiState by viewModel.uiState.collectAsState()\n    val installUiState by installVm.uiState.collectAsState()\n    val context = LocalContext.current\n    val activity = context as MainActivity\n    val scrollBehavior = MiuixScrollBehavior()\n    val scope = rememberCoroutineScope()\n    val loadingDialog = rememberLoadingDialog()\n    val navigator = com.topjohnwu.magisk.ui.navigation.LocalNavigator.current\n\n    val showUninstallDialog = rememberSaveable { mutableStateOf(false) }\n    val showManagerDialog = rememberSaveable { mutableStateOf(false) }\n    val showEnvFixDialog = rememberSaveable { mutableStateOf(false) }\n    var showHideDialog by rememberSaveable { mutableStateOf(false) }\n    var showRestoreDialog by rememberSaveable { mutableStateOf(false) }\n    val showInstallSheet = rememberSaveable { mutableStateOf(false) }\n    var envFixCode by remember { mutableIntStateOf(0) }\n\n    val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->\n        uri?.let { installVm.onPatchFileSelected(it) }\n    }\n\n    val secondSlotDialog = rememberConfirmDialog()\n    val secondSlotTitle = stringResource(android.R.string.dialog_alert_title)\n    val secondSlotMsg = stringResource(CoreR.string.install_inactive_slot_msg)\n\n    LaunchedEffect(installUiState.requestFilePicker) {\n        if (installUiState.requestFilePicker) {\n            filePicker.launch(\"*/*\")\n            installVm.onFilePickerConsumed()\n        }\n    }\n\n    LaunchedEffect(installUiState.showSecondSlotWarning) {\n        if (installUiState.showSecondSlotWarning) {\n            val result = secondSlotDialog.awaitConfirm(title = secondSlotTitle, content = secondSlotMsg)\n            installVm.onSecondSlotWarningConsumed()\n            if (result == ConfirmResult.Confirmed) {\n                installVm.install()\n            }\n        }\n    }\n\n    LaunchedEffect(uiState.showUninstall) {\n        if (uiState.showUninstall) {\n            showUninstallDialog.value = true\n            viewModel.onUninstallConsumed()\n        }\n    }\n    LaunchedEffect(uiState.showManagerInstall) {\n        if (uiState.showManagerInstall) {\n            showManagerDialog.value = true\n            viewModel.onManagerInstallConsumed()\n        }\n    }\n    LaunchedEffect(uiState.envFixCode) {\n        if (uiState.envFixCode != 0) {\n            envFixCode = uiState.envFixCode\n            showEnvFixDialog.value = true\n            viewModel.onEnvFixConsumed()\n        }\n    }\n    LaunchedEffect(uiState.showHideRestore) {\n        if (uiState.showHideRestore) {\n            val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME\n            if (hidden) showRestoreDialog = true else showHideDialog = true\n            viewModel.onHideRestoreConsumed()\n        }\n    }\n\n    if (showUninstallDialog.value) {\n        UninstallComposableDialog(\n            showDialog = showUninstallDialog,\n            activity = activity,\n            loadingDialog = loadingDialog,\n        )\n    }\n\n    if (showManagerDialog.value) {\n        ManagerInstallComposableDialog(\n            showDialog = showManagerDialog,\n            activity = activity,\n        )\n    }\n\n    if (showEnvFixDialog.value) {\n        EnvFixComposableDialog(\n            showDialog = showEnvFixDialog,\n            code = envFixCode,\n            activity = activity,\n            loadingDialog = loadingDialog,\n            onNavigateInstall = { showInstallSheet.value = true },\n        )\n    }\n\n    if (showHideDialog) {\n        HideAppDialog(\n            onDismiss = { showHideDialog = false },\n            onConfirm = { name ->\n                showHideDialog = false\n                scope.launch {\n                    loadingDialog.withLoading {\n                        AppMigration.patchAndHide(context, name)\n                    }\n                }\n            }\n        )\n    }\n\n    if (showRestoreDialog) {\n        RestoreAppDialog(\n            onDismiss = { showRestoreDialog = false },\n            onConfirm = {\n                showRestoreDialog = false\n                scope.launch {\n                    loadingDialog.withLoading {\n                        AppMigration.restoreApp(context)\n                    }\n                }\n            }\n        )\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.section_home),\n                scrollBehavior = scrollBehavior,\n                actions = {\n                    if (Info.isRooted) {\n                        RebootButton()\n                    }\n                }\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        Column(\n            modifier = Modifier\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .padding(padding)\n                .verticalScroll(rememberScrollState())\n                .padding(horizontal = 16.dp)\n                .padding(top = 12.dp, bottom = 88.dp),\n            verticalArrangement = Arrangement.spacedBy(12.dp)\n        ) {\n            if (uiState.isNoticeVisible) {\n                NoticeCard(onHide = viewModel::hideNotice)\n            }\n\n            Row(\n                modifier = Modifier.height(IntrinsicSize.Max),\n                horizontalArrangement = Arrangement.spacedBy(12.dp)\n            ) {\n                CoreCard(\n                    modifier = Modifier.weight(1f).fillMaxHeight(),\n                    state = viewModel.magiskState,\n                    version = viewModel.magiskInstalledVersion,\n                    remoteVersion = if (viewModel.magiskState == HomeViewModel.State.OUTDATED)\n                        \"${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\" else null,\n                    onInstallClicked = { showInstallSheet.value = true },\n                    onUninstallClicked = { viewModel.onDeletePressed() },\n                )\n                AppCard(\n                    modifier = Modifier.weight(1f).fillMaxHeight(),\n                    state = uiState.appState,\n                    version = viewModel.managerInstalledVersion,\n                    remoteVersion = if (uiState.appState == HomeViewModel.State.OUTDATED)\n                        uiState.managerRemoteVersion else null,\n                    progress = uiState.managerProgress,\n                    isHidden = context.packageName != BuildConfig.APP_PACKAGE_NAME,\n                    onManagerPressed = { viewModel.onManagerPressed() },\n                    onHideRestorePressed = viewModel::onHideRestorePressed,\n                )\n            }\n\n            SmallTitle(text = stringResource(CoreR.string.home_status_title))\n            StatusCard()\n\n            val showDonateSheet = rememberSaveable { mutableStateOf(false) }\n\n            SmallTitle(text = stringResource(CoreR.string.home_support_title))\n            Card(modifier = Modifier.fillMaxWidth()) {\n                SuperArrow(\n                    title = stringResource(CoreR.string.documents),\n                    onClick = { openLink(context, \"https://topjohnwu.github.io/Magisk/\") }\n                )\n                SuperArrow(\n                    title = stringResource(CoreR.string.report_bugs),\n                    onClick = { openLink(context, \"${Const.Url.SOURCE_CODE_URL}/issues\") }\n                )\n                SuperArrow(\n                    title = stringResource(CoreR.string.donate),\n                    onClick = { showDonateSheet.value = true }\n                )\n            }\n\n            SupportBottomSheet(\n                show = showDonateSheet,\n                onLinkClicked = { viewModel.onLinkPressed(it) }\n            )\n\n            SmallTitle(text = stringResource(CoreR.string.home_follow_title))\n            DevelopersCard(onLinkClicked = { openLink(context, it) })\n        }\n    }\n\n    InstallBottomSheet(\n        show = showInstallSheet,\n        installVm = installVm,\n        installUiState = installUiState,\n    )\n}\n\n@Composable\nprivate fun RebootButton() {\n    val showMenu = remember { mutableStateOf(false) }\n    val context = LocalContext.current\n    var safeModeEnabled by remember { mutableIntStateOf(Config.bootloop) }\n\n    val showUserspace = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&\n        context.getSystemService<PowerManager>()?.isRebootingUserspaceSupported == true\n    val showSafeMode = Const.Version.atLeast_28_0()\n\n    val items = buildList {\n        add(RebootOption(CoreR.string.reboot) { reboot() })\n        if (showUserspace) {\n            add(RebootOption(CoreR.string.reboot_userspace) { reboot(\"userspace\") })\n        }\n        add(RebootOption(CoreR.string.reboot_recovery) { reboot(\"recovery\") })\n        add(RebootOption(CoreR.string.reboot_bootloader) { reboot(\"bootloader\") })\n        add(RebootOption(CoreR.string.reboot_download) { reboot(\"download\") })\n        add(RebootOption(CoreR.string.reboot_edl) { reboot(\"edl\") })\n        if (showSafeMode) {\n            add(RebootOption(CoreR.string.reboot_safe_mode) {\n                val newVal = if (safeModeEnabled >= 2) 0 else 2\n                Config.bootloop = newVal\n                safeModeEnabled = newVal\n            })\n        }\n    }\n\n    Box {\n        IconButton(\n            modifier = Modifier.padding(end = 16.dp),\n            onClick = { showMenu.value = true },\n            holdDownState = showMenu.value,\n        ) {\n            Icon(\n                painter = painterResource(R.drawable.ic_restart),\n                contentDescription = stringResource(CoreR.string.reboot),\n            )\n        }\n        SuperListPopup(\n            show = showMenu,\n            popupPositionProvider = MenuPositionProvider,\n            alignment = PopupPositionProvider.Align.End,\n            onDismissRequest = { showMenu.value = false }\n        ) {\n            ListPopupColumn {\n                items.forEachIndexed { index, item ->\n                    val isSafeMode = item.labelRes == CoreR.string.reboot_safe_mode\n                    DropdownImpl(\n                        text = stringResource(item.labelRes),\n                        optionSize = items.size,\n                        isSelected = isSafeMode && safeModeEnabled >= 2,\n                        index = index,\n                        onSelectedIndexChange = {\n                            item.action()\n                            if (!isSafeMode) showMenu.value = false\n                        }\n                    )\n                }\n            }\n        }\n    }\n}\n\nprivate class RebootOption(val labelRes: Int, val action: () -> Unit)\n\n@Composable\nprivate fun NoticeCard(onHide: () -> Unit) {\n    Box(\n        modifier = Modifier\n            .fillMaxWidth()\n            .background(\n                MiuixTheme.colorScheme.tertiaryContainer,\n                RoundedCornerShape(16.dp)\n            )\n            .padding(start = 16.dp, top = 4.dp, bottom = 4.dp, end = 4.dp)\n    ) {\n        Row(\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Text(\n                text = stringResource(CoreR.string.home_notice_content),\n                style = MiuixTheme.textStyles.body2,\n                color = MiuixTheme.colorScheme.onTertiaryContainer,\n                modifier = Modifier.weight(1f).padding(vertical = 8.dp)\n            )\n            IconButton(onClick = onHide) {\n                Icon(\n                    imageVector = MiuixIcons.Close,\n                    contentDescription = stringResource(CoreR.string.hide),\n                    modifier = Modifier.size(15.dp),\n                    tint = MiuixTheme.colorScheme.onTertiaryContainer,\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun CoreCard(\n    modifier: Modifier = Modifier,\n    state: HomeViewModel.State,\n    version: String,\n    remoteVersion: String? = null,\n    onInstallClicked: () -> Unit,\n    onUninstallClicked: () -> Unit,\n) {\n    val actionLabel = when (state) {\n        HomeViewModel.State.OUTDATED -> stringResource(CoreR.string.update)\n        HomeViewModel.State.INVALID -> stringResource(CoreR.string.install)\n        HomeViewModel.State.UP_TO_DATE -> stringResource(CoreR.string.reinstall)\n        HomeViewModel.State.LOADING -> null\n    }\n    val actionColor = when (state) {\n        HomeViewModel.State.OUTDATED, HomeViewModel.State.INVALID -> MiuixTheme.colorScheme.primary\n        else -> MiuixTheme.colorScheme.onSurfaceVariantActions\n    }\n    val uninstallEnabled = Info.env.isActive\n\n    Card(modifier = modifier) {\n        Column(modifier = Modifier.fillMaxSize()) {\n            Box(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxWidth()\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Icon(\n                        painter = painterResource(CoreR.drawable.ic_magisk_outline),\n                        contentDescription = null,\n                        modifier = Modifier.size(24.dp),\n                        tint = MiuixTheme.colorScheme.primary\n                    )\n                    Spacer(Modifier.height(8.dp))\n                    Text(\n                        text = stringResource(CoreR.string.home_core_title),\n                        style = MiuixTheme.textStyles.headline2,\n                    )\n                    Text(\n                        text = version.ifEmpty { stringResource(CoreR.string.not_available) },\n                        style = MiuixTheme.textStyles.body2,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                    )\n                }\n                Column(\n                    modifier = Modifier.align(Alignment.TopEnd).padding(4.dp),\n                    verticalArrangement = Arrangement.spacedBy(0.dp),\n                ) {\n                    IconButton(\n                        onClick = onUninstallClicked,\n                        enabled = uninstallEnabled,\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Delete,\n                            contentDescription = null,\n                            modifier = Modifier.size(18.dp),\n                            tint = if (uninstallEnabled) MiuixTheme.colorScheme.error\n                                else MiuixTheme.colorScheme.onSurfaceVariantActions,\n                        )\n                    }\n                    if (remoteVersion != null) {\n                        UpdateBadge(\n                            version = remoteVersion,\n                            modifier = Modifier.align(Alignment.End).padding(end = 4.dp)\n                        )\n                    }\n                }\n            }\n\n            if (actionLabel != null) {\n                HorizontalDivider(thickness = 0.75.dp)\n                Text(\n                    text = actionLabel,\n                    style = MiuixTheme.textStyles.body2,\n                    color = actionColor,\n                    textAlign = TextAlign.Center,\n                    maxLines = 1,\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .height(44.dp)\n                        .clickable(onClick = onInstallClicked)\n                        .padding(horizontal = 12.dp, vertical = 12.dp)\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun AppCard(\n    modifier: Modifier = Modifier,\n    state: HomeViewModel.State,\n    version: String,\n    remoteVersion: String? = null,\n    progress: Int,\n    isHidden: Boolean,\n    onManagerPressed: () -> Unit,\n    onHideRestorePressed: () -> Unit,\n) {\n    val actionLabel = when (state) {\n        HomeViewModel.State.OUTDATED -> stringResource(CoreR.string.update)\n        HomeViewModel.State.UP_TO_DATE -> stringResource(CoreR.string.reinstall)\n        else -> null\n    }\n    val actionColor = when (state) {\n        HomeViewModel.State.OUTDATED -> MiuixTheme.colorScheme.primary\n        else -> MiuixTheme.colorScheme.onSurfaceVariantActions\n    }\n    val hideRestoreIcon = if (isHidden) MiuixIcons.Show else MiuixIcons.Hide\n\n    Card(modifier = modifier) {\n        Column(modifier = Modifier.fillMaxSize()) {\n            Box(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxWidth()\n            ) {\n                Column(modifier = Modifier.padding(16.dp)) {\n                    Icon(\n                        painter = painterResource(R.drawable.ic_manager),\n                        contentDescription = null,\n                        modifier = Modifier.size(24.dp),\n                        tint = MiuixTheme.colorScheme.primary\n                    )\n                    Spacer(Modifier.height(8.dp))\n                    Text(\n                        text = stringResource(CoreR.string.home_app_title),\n                        style = MiuixTheme.textStyles.headline2,\n                    )\n                    Text(\n                        text = version,\n                        style = MiuixTheme.textStyles.body2,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                    )\n                    if (progress in 1..99) {\n                        Spacer(Modifier.height(8.dp))\n                        LinearProgressIndicator(\n                            progress = progress / 100f,\n                            modifier = Modifier.fillMaxWidth()\n                        )\n                    }\n                }\n                Column(\n                    modifier = Modifier.align(Alignment.TopEnd).padding(4.dp),\n                    verticalArrangement = Arrangement.spacedBy(0.dp),\n                ) {\n                    if (Info.env.isActive) {\n                        IconButton(onClick = onHideRestorePressed) {\n                            Icon(\n                                imageVector = hideRestoreIcon,\n                                contentDescription = null,\n                                modifier = Modifier.size(18.dp),\n                                tint = MiuixTheme.colorScheme.primary,\n                            )\n                        }\n                    }\n                    if (remoteVersion != null) {\n                        UpdateBadge(\n                            version = remoteVersion,\n                            modifier = Modifier.align(Alignment.End).padding(end = 4.dp)\n                        )\n                    }\n                }\n            }\n\n            if (actionLabel != null) {\n                HorizontalDivider(thickness = 0.75.dp)\n                Text(\n                    text = actionLabel,\n                    style = MiuixTheme.textStyles.body2,\n                    color = actionColor,\n                    textAlign = TextAlign.Center,\n                    maxLines = 1,\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .height(44.dp)\n                        .clickable(onClick = onManagerPressed)\n                        .padding(horizontal = 12.dp, vertical = 12.dp)\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun UpdateBadge(version: String, modifier: Modifier = Modifier) {\n    Text(\n        text = version,\n        color = MiuixTheme.colorScheme.onPrimary,\n        fontSize = 10.sp,\n        maxLines = 1,\n        modifier = modifier\n            .background(MiuixTheme.colorScheme.primary, RoundedCornerShape(6.dp))\n            .padding(horizontal = 6.dp, vertical = 2.dp)\n    )\n}\n\n@Composable\nprivate fun StatusCard() {\n    Card(modifier = Modifier.fillMaxWidth()) {\n        Row(\n            modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),\n        ) {\n            Column(\n                modifier = Modifier.weight(1f).padding(16.dp),\n                horizontalAlignment = Alignment.CenterHorizontally,\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.ramdisk),\n                    style = MiuixTheme.textStyles.headline2,\n                )\n                Text(\n                    text = stringResource(if (Info.ramdisk) CoreR.string.yes else CoreR.string.no),\n                    style = MiuixTheme.textStyles.body2,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                )\n            }\n            VerticalDivider(thickness = 0.75.dp)\n            Column(\n                modifier = Modifier.weight(1f).padding(16.dp),\n                horizontalAlignment = Alignment.CenterHorizontally,\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.zygisk),\n                    style = MiuixTheme.textStyles.headline2,\n                )\n                Text(\n                    text = stringResource(if (Info.isZygiskEnabled) CoreR.string.yes else CoreR.string.no),\n                    style = MiuixTheme.textStyles.body2,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                )\n            }\n            VerticalDivider(thickness = 0.75.dp)\n            Column(\n                modifier = Modifier.weight(1f).padding(16.dp),\n                horizontalAlignment = Alignment.CenterHorizontally,\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.denylist),\n                    style = MiuixTheme.textStyles.headline2,\n                )\n                Text(\n                    text = stringResource(if (Config.denyList) CoreR.string.enabled else CoreR.string.disabled),\n                    style = MiuixTheme.textStyles.body2,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SupportBottomSheet(\n    show: MutableState<Boolean>,\n    onLinkClicked: (String) -> Unit,\n) {\n    SuperBottomSheet(\n        show = show,\n        onDismissRequest = { show.value = false },\n        title = stringResource(CoreR.string.home_support_title),\n    ) {\n        Column(modifier = Modifier.padding(bottom = 16.dp)) {\n            Text(\n                text = stringResource(CoreR.string.home_support_content),\n                style = MiuixTheme.textStyles.body2,\n                color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)\n            )\n            SuperArrow(\n                title = stringResource(CoreR.string.patreon),\n                onClick = {\n                    show.value = false\n                    onLinkClicked(Const.Url.PATREON_URL)\n                },\n                startAction = {\n                    Icon(\n                        painter = painterResource(CoreR.drawable.ic_patreon),\n                        contentDescription = null,\n                        modifier = Modifier.size(24.dp),\n                        tint = MiuixTheme.colorScheme.onSurfaceVariantActions\n                    )\n                }\n            )\n            SuperArrow(\n                title = stringResource(CoreR.string.paypal),\n                onClick = {\n                    show.value = false\n                    onLinkClicked(\"https://paypal.me/magiskdonate\")\n                },\n                startAction = {\n                    Icon(\n                        painter = painterResource(CoreR.drawable.ic_paypal),\n                        contentDescription = null,\n                        modifier = Modifier.size(24.dp),\n                        tint = MiuixTheme.colorScheme.onSurfaceVariantActions\n                    )\n                }\n            )\n        }\n    }\n}\n\nprivate data class LinkInfo(val label: String, val icon: Int, val url: String)\nprivate data class DeveloperInfo(val name: String, val links: List<LinkInfo>)\n\nprivate val developers = listOf(\n    DeveloperInfo(\"topjohnwu\", listOf(\n        LinkInfo(\"Twitter\", CoreR.drawable.ic_twitter, \"https://twitter.com/topjohnwu\"),\n        LinkInfo(\"GitHub\", CoreR.drawable.ic_github, Const.Url.SOURCE_CODE_URL),\n    )),\n    DeveloperInfo(\"vvb2060\", listOf(\n        LinkInfo(\"Twitter\", CoreR.drawable.ic_twitter, \"https://twitter.com/vvb2060\"),\n        LinkInfo(\"GitHub\", CoreR.drawable.ic_github, \"https://github.com/vvb2060\"),\n    )),\n    DeveloperInfo(\"yujincheng08\", listOf(\n        LinkInfo(\"Twitter\", CoreR.drawable.ic_twitter, \"https://twitter.com/shanasaimoe\"),\n        LinkInfo(\"GitHub\", CoreR.drawable.ic_github, \"https://github.com/yujincheng08\"),\n        LinkInfo(\"Sponsor\", CoreR.drawable.ic_favorite, \"https://github.com/sponsors/yujincheng08\"),\n    )),\n    DeveloperInfo(\"rikkawww\", listOf(\n        LinkInfo(\"Twitter\", CoreR.drawable.ic_twitter, \"https://twitter.com/rikkawww\"),\n        LinkInfo(\"GitHub\", CoreR.drawable.ic_github, \"https://github.com/rikkawww\"),\n    )),\n    DeveloperInfo(\"canyie\", listOf(\n        LinkInfo(\"Twitter\", CoreR.drawable.ic_twitter, \"https://twitter.com/canyie2977\"),\n        LinkInfo(\"GitHub\", CoreR.drawable.ic_github, \"https://github.com/canyie\"),\n    )),\n)\n\n@Composable\nprivate fun DevelopersCard(onLinkClicked: (String) -> Unit) {\n    var selectedDev by remember { mutableStateOf<DeveloperInfo?>(null) }\n    val showSheet = rememberSaveable { mutableStateOf(false) }\n\n    Card(modifier = Modifier.fillMaxWidth()) {\n        developers.forEach { dev ->\n            SuperArrow(\n                title = \"@${dev.name}\",\n                onClick = {\n                    selectedDev = dev\n                    showSheet.value = true\n                }\n            )\n        }\n    }\n\n    val currentDev = selectedDev\n    if (currentDev != null) {\n        SuperBottomSheet(\n            show = showSheet,\n            onDismissRequest = {\n                showSheet.value = false\n                selectedDev = null\n            },\n            title = \"@${currentDev.name}\",\n        ) {\n            Column(modifier = Modifier.padding(bottom = 16.dp)) {\n                currentDev.links.forEach { link ->\n                    SuperArrow(\n                        title = link.label,\n                        onClick = {\n                            showSheet.value = false\n                            onLinkClicked(link.url)\n                            selectedDev = null\n                        },\n                        startAction = {\n                            Icon(\n                                painter = painterResource(link.icon),\n                                contentDescription = null,\n                                modifier = Modifier.size(24.dp),\n                                tint = MiuixTheme.colorScheme.onSurfaceVariantActions\n                            )\n                        }\n                    )\n                }\n            }\n        }\n    }\n}\n\nprivate fun openLink(context: Context, url: String) {\n    try {\n        context.startActivity(Intent(Intent.ACTION_VIEW, url.toUri()).apply {\n            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        })\n    } catch (_: ActivityNotFoundException) { }\n}\n\n@Composable\nprivate fun InstallBottomSheet(\n    show: MutableState<Boolean>,\n    installVm: InstallViewModel,\n    installUiState: InstallViewModel.UiState,\n) {\n    SuperBottomSheet(\n        show = show,\n        onDismissRequest = { show.value = false },\n        title = stringResource(CoreR.string.install),\n    ) {\n        Column(modifier = Modifier.padding(bottom = 16.dp)) {\n            if (installUiState.notes.isNotEmpty()) {\n                Box(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {\n                    MarkdownText(installUiState.notes)\n                }\n                HorizontalDivider(thickness = 0.75.dp)\n            }\n\n            if (!installVm.skipOptions) {\n                InstallOptionsSection(installUiState, installVm)\n            }\n\n            SuperArrow(\n                title = stringResource(CoreR.string.select_patch_file),\n                summary = stringResource(CoreR.string.select_patch_file_summary),\n                onClick = {\n                    show.value = false\n                    installVm.selectMethod(InstallViewModel.Method.PATCH)\n                },\n                enabled = installUiState.step >= 1 || installVm.skipOptions\n            )\n\n            if (installVm.isRooted) {\n                SuperArrow(\n                    title = stringResource(CoreR.string.direct_install),\n                    summary = stringResource(CoreR.string.direct_install_summary),\n                    onClick = {\n                        show.value = false\n                        installVm.selectMethod(InstallViewModel.Method.DIRECT)\n                        installVm.install()\n                    },\n                    enabled = installUiState.step >= 1 || installVm.skipOptions\n                )\n            }\n\n            if (!installVm.noSecondSlot) {\n                SuperArrow(\n                    title = stringResource(CoreR.string.install_inactive_slot),\n                    summary = stringResource(CoreR.string.install_inactive_slot_summary),\n                    onClick = {\n                        show.value = false\n                        installVm.selectMethod(InstallViewModel.Method.INACTIVE_SLOT)\n                    },\n                    enabled = installUiState.step >= 1 || installVm.skipOptions\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun InstallOptionsSection(\n    uiState: InstallViewModel.UiState,\n    viewModel: InstallViewModel\n) {\n    Column(modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.SpaceBetween,\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Text(\n                text = stringResource(CoreR.string.install_options_title),\n                style = MiuixTheme.textStyles.headline2,\n            )\n            if (uiState.step == 0) {\n                TextButton(\n                    text = stringResource(CoreR.string.install_next),\n                    onClick = { viewModel.nextStep() }\n                )\n            }\n        }\n\n        if (uiState.step == 0) {\n            Spacer(Modifier.height(8.dp))\n            if (!Info.isSAR) {\n                CheckboxRow(\n                    label = stringResource(CoreR.string.keep_dm_verity),\n                    checked = Config.keepVerity,\n                    onCheckedChange = { Config.keepVerity = it }\n                )\n            }\n            if (Info.isFDE) {\n                CheckboxRow(\n                    label = stringResource(CoreR.string.keep_force_encryption),\n                    checked = Config.keepEnc,\n                    onCheckedChange = { Config.keepEnc = it }\n                )\n            }\n            if (!Info.ramdisk) {\n                CheckboxRow(\n                    label = stringResource(CoreR.string.recovery_mode),\n                    checked = Config.recovery,\n                    onCheckedChange = { Config.recovery = it }\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun CheckboxRow(label: String, checked: Boolean, onCheckedChange: (Boolean) -> Unit) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(vertical = 4.dp),\n        verticalAlignment = Alignment.CenterVertically,\n        horizontalArrangement = Arrangement.spacedBy(8.dp)\n    ) {\n        Checkbox(\n            checked = checked,\n            onCheckedChange = { onCheckedChange(it) }\n        )\n        Text(\n            text = label,\n            style = MiuixTheme.textStyles.body1,\n        )\n    }\n}\n\n@Composable\nprivate fun UninstallComposableDialog(\n    showDialog: MutableState<Boolean>,\n    activity: MainActivity,\n    loadingDialog: LoadingDialogHandle,\n) {\n    val scope = rememberCoroutineScope()\n    top.yukonga.miuix.kmp.extra.SuperDialog(\n        show = showDialog,\n        title = stringResource(CoreR.string.uninstall_magisk_title),\n        onDismissRequest = { showDialog.value = false },\n    ) {\n        Text(\n            text = stringResource(CoreR.string.uninstall_magisk_msg),\n            style = MiuixTheme.textStyles.body1,\n            color = MiuixTheme.colorScheme.onSurface,\n        )\n        Spacer(Modifier.height(16.dp))\n        Row(modifier = Modifier.fillMaxWidth()) {\n            TextButton(\n                text = stringResource(CoreR.string.restore_img),\n                onClick = {\n                    showDialog.value = false\n                    scope.launch {\n                        val success = loadingDialog.withLoading {\n                            MagiskInstaller.Restore().exec()\n                        }\n                        activity.toast(\n                            if (success) CoreR.string.restore_done else CoreR.string.restore_fail,\n                            Toast.LENGTH_SHORT\n                        )\n                    }\n                },\n                modifier = Modifier.weight(1f)\n            )\n            Spacer(Modifier.width(20.dp))\n            TextButton(\n                text = stringResource(CoreR.string.complete_uninstall),\n                onClick = {\n                    showDialog.value = false\n                    val intent = Intent(activity, activity.javaClass).apply {\n                        action = FlashUtils.INTENT_FLASH\n                        putExtra(FlashUtils.EXTRA_FLASH_ACTION, Const.Value.UNINSTALL)\n                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP\n                    }\n                    activity.startActivity(intent)\n                },\n                modifier = Modifier.weight(1f),\n                colors = ButtonDefaults.textButtonColorsPrimary()\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun ManagerInstallComposableDialog(\n    showDialog: MutableState<Boolean>,\n    activity: MainActivity,\n) {\n    top.yukonga.miuix.kmp.extra.SuperDialog(\n        show = showDialog,\n        title = stringResource(CoreR.string.install),\n        onDismissRequest = { showDialog.value = false },\n    ) {\n        MarkdownTextAsync {\n            val text = Info.update.note\n            java.io.File(activity.cacheDir, \"${Info.update.versionCode}.md\").writeText(text)\n            text\n        }\n        Spacer(Modifier.height(16.dp))\n        Row(modifier = Modifier.fillMaxWidth()) {\n            TextButton(\n                text = stringResource(android.R.string.cancel),\n                onClick = { showDialog.value = false },\n                modifier = Modifier.weight(1f)\n            )\n            Spacer(Modifier.width(20.dp))\n            TextButton(\n                text = stringResource(CoreR.string.install),\n                onClick = {\n                    showDialog.value = false\n                    DownloadEngine.startWithActivity(activity, Subject.App())\n                },\n                modifier = Modifier.weight(1f),\n                colors = ButtonDefaults.textButtonColorsPrimary()\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun EnvFixComposableDialog(\n    showDialog: MutableState<Boolean>,\n    code: Int,\n    activity: MainActivity,\n    loadingDialog: LoadingDialogHandle,\n    onNavigateInstall: () -> Unit,\n) {\n    val scope = rememberCoroutineScope()\n    val needsFullFix = code == 2 ||\n        Info.env.versionCode != com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_CODE ||\n        Info.env.versionString != com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_NAME\n\n    top.yukonga.miuix.kmp.extra.SuperDialog(\n        show = showDialog,\n        title = stringResource(CoreR.string.env_fix_title),\n        onDismissRequest = { showDialog.value = false },\n    ) {\n        Text(\n            text = stringResource(\n                if (needsFullFix) CoreR.string.env_full_fix_msg else CoreR.string.env_fix_msg\n            ),\n            style = MiuixTheme.textStyles.body1,\n            color = MiuixTheme.colorScheme.onSurface,\n        )\n        Spacer(Modifier.height(16.dp))\n        Row(modifier = Modifier.fillMaxWidth()) {\n            TextButton(\n                text = stringResource(android.R.string.cancel),\n                onClick = { showDialog.value = false },\n                modifier = Modifier.weight(1f)\n            )\n            Spacer(Modifier.width(20.dp))\n            TextButton(\n                text = stringResource(android.R.string.ok),\n                onClick = {\n                    showDialog.value = false\n                    if (needsFullFix) {\n                        onNavigateInstall()\n                    } else {\n                        scope.launch {\n                            val success = loadingDialog.withLoading {\n                                MagiskInstaller.FixEnv().exec()\n                            }\n                            activity.toast(\n                                if (success) CoreR.string.reboot_delay_toast else CoreR.string.setup_fail,\n                                Toast.LENGTH_LONG\n                            )\n                            if (success) {\n                                @Suppress(\"DEPRECATION\")\n                                android.os.Handler(android.os.Looper.getMainLooper())\n                                    .postDelayed({ reboot() }, 5000)\n                            }\n                        }\n                    }\n                },\n                modifier = Modifier.weight(1f),\n                colors = ButtonDefaults.textButtonColorsPrimary()\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun HideAppDialog(onDismiss: () -> Unit, onConfirm: (String) -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(true) }\n    var appName by rememberSaveable { mutableStateOf(\"Settings\") }\n    val isError = appName.length > AppMigration.MAX_LABEL_LENGTH || appName.isBlank()\n\n    top.yukonga.miuix.kmp.extra.SuperDialog(\n        show = showState,\n        title = stringResource(CoreR.string.settings_hide_app_title),\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            top.yukonga.miuix.kmp.basic.TextField(\n                value = appName,\n                onValueChange = { appName = it },\n                modifier = Modifier.fillMaxWidth(),\n                label = stringResource(CoreR.string.settings_app_name_hint),\n            )\n            Spacer(Modifier.height(16.dp))\n            Row(horizontalArrangement = Arrangement.SpaceBetween) {\n                TextButton(\n                    text = stringResource(android.R.string.cancel),\n                    onClick = onDismiss,\n                    modifier = Modifier.weight(1f)\n                )\n                Spacer(Modifier.width(20.dp))\n                TextButton(\n                    text = stringResource(android.R.string.ok),\n                    onClick = { if (!isError) onConfirm(appName) },\n                    modifier = Modifier.weight(1f),\n                    colors = ButtonDefaults.textButtonColorsPrimary()\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun RestoreAppDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(true) }\n\n    top.yukonga.miuix.kmp.extra.SuperDialog(\n        show = showState,\n        title = stringResource(CoreR.string.settings_restore_app_title),\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            Text(\n                text = stringResource(CoreR.string.restore_app_confirmation),\n                style = MiuixTheme.textStyles.body1,\n                color = MiuixTheme.colorScheme.onSurface,\n            )\n            Spacer(Modifier.height(16.dp))\n            Row(horizontalArrangement = Arrangement.SpaceBetween) {\n                TextButton(\n                    text = stringResource(android.R.string.cancel),\n                    onClick = onDismiss,\n                    modifier = Modifier.weight(1f)\n                )\n                Spacer(Modifier.width(20.dp))\n                TextButton(\n                    text = stringResource(android.R.string.ok),\n                    onClick = onConfirm,\n                    modifier = Modifier.weight(1f),\n                    colors = ButtonDefaults.textButtonColorsPrimary()\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/home/HomeViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.home\n\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.widget.Toast\nimport androidx.core.net.toUri\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.download.Subject.App\nimport com.topjohnwu.magisk.core.ktx.await\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlin.math.roundToInt\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass HomeViewModel(\n    private val svc: NetworkService\n) : AsyncLoadViewModel() {\n\n    enum class State {\n        LOADING, INVALID, OUTDATED, UP_TO_DATE\n    }\n\n    data class UiState(\n        val isNoticeVisible: Boolean = Config.safetyNotice,\n        val appState: State = State.LOADING,\n        val managerRemoteVersion: String = \"\",\n        val managerProgress: Int = 0,\n        val showUninstall: Boolean = false,\n        val showManagerInstall: Boolean = false,\n        val showHideRestore: Boolean = false,\n        val envFixCode: Int = 0,\n    )\n\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n\n    val magiskState\n        get() = when {\n            Info.isRooted && Info.env.isUnsupported -> State.OUTDATED\n            !Info.env.isActive -> State.INVALID\n            Info.env.versionCode < BuildConfig.APP_VERSION_CODE -> State.OUTDATED\n            else -> State.UP_TO_DATE\n        }\n\n    val magiskInstalledVersion: String\n        get() = Info.env.run {\n            if (isActive)\n                \"$versionString ($versionCode)\" + if (isDebug) \" (D)\" else \"\"\n            else\n                \"\"\n        }\n\n    val managerInstalledVersion: String\n        get() = \"${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\" +\n            if (BuildConfig.DEBUG) \" (D)\" else \"\"\n\n    companion object {\n        private var checkedEnv = false\n    }\n\n    override suspend fun doLoadWork() {\n        _uiState.update { it.copy(appState = State.LOADING) }\n        Info.fetchUpdate(svc)?.apply {\n            val isDebug = Config.updateChannel == Config.Value.DEBUG_CHANNEL\n            _uiState.update {\n                it.copy(\n                    appState = if (BuildConfig.APP_VERSION_CODE < versionCode) State.OUTDATED else State.UP_TO_DATE,\n                    managerRemoteVersion = \"$version ($versionCode)\" + if (isDebug) \" (D)\" else \"\"\n                )\n            }\n        } ?: run {\n            _uiState.update { it.copy(appState = State.INVALID, managerRemoteVersion = \"\") }\n        }\n        ensureEnv()\n    }\n\n    private val networkObserver: (Boolean) -> Unit = { startLoading() }\n\n    init {\n        Info.isConnected.observeForever(networkObserver)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        Info.isConnected.removeObserver(networkObserver)\n    }\n\n    fun onProgressUpdate(progress: Float, subject: Subject) {\n        if (subject is App)\n            _uiState.update { it.copy(managerProgress = progress.times(100f).roundToInt()) }\n    }\n\n    fun resetProgress() {\n        _uiState.update { it.copy(managerProgress = 0) }\n    }\n\n    fun onLinkPressed(link: String) {\n        val intent = Intent(Intent.ACTION_VIEW, link.toUri())\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        try {\n            AppContext.startActivity(intent)\n        } catch (e: ActivityNotFoundException) {\n            AppContext.toast(CoreR.string.open_link_failed_toast, Toast.LENGTH_SHORT)\n        }\n    }\n\n    fun onDeletePressed() {\n        _uiState.update { it.copy(showUninstall = true) }\n    }\n\n    fun onUninstallConsumed() {\n        _uiState.update { it.copy(showUninstall = false) }\n    }\n\n    fun onManagerPressed() {\n        when (_uiState.value.appState) {\n            State.LOADING -> showSnackbar(CoreR.string.loading)\n            State.INVALID -> showSnackbar(CoreR.string.no_connection)\n            else -> _uiState.update { it.copy(showManagerInstall = true) }\n        }\n    }\n\n    fun onManagerInstallConsumed() {\n        _uiState.update { it.copy(showManagerInstall = false) }\n    }\n\n    fun onHideRestorePressed() {\n        _uiState.update { it.copy(showHideRestore = true) }\n    }\n\n    fun onHideRestoreConsumed() {\n        _uiState.update { it.copy(showHideRestore = false) }\n    }\n\n    fun onEnvFixConsumed() {\n        _uiState.update { it.copy(envFixCode = 0) }\n    }\n\n    fun hideNotice() {\n        Config.safetyNotice = false\n        _uiState.update { it.copy(isNoticeVisible = false) }\n    }\n\n    private suspend fun ensureEnv() {\n        if (magiskState == State.INVALID || checkedEnv) return\n        val cmd = \"env_check ${Info.env.versionString} ${Info.env.versionCode}\"\n        val code = Shell.cmd(cmd).await().code\n        if (code != 0) {\n            _uiState.update { it.copy(envFixCode = code) }\n        }\n        checkedEnv = true\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/install/InstallViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.install\n\nimport android.net.Uri\nimport android.widget.Toast\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_CODE\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.File\nimport java.io.IOException\nimport com.topjohnwu.magisk.core.R as CoreR\n\nclass InstallViewModel(svc: NetworkService) : BaseViewModel() {\n\n    enum class Method { NONE, PATCH, DIRECT, INACTIVE_SLOT }\n\n    data class UiState(\n        val step: Int = 0,\n        val method: Method = Method.NONE,\n        val notes: String = \"\",\n        val patchUri: Uri? = null,\n        val requestFilePicker: Boolean = false,\n        val showSecondSlotWarning: Boolean = false,\n    )\n\n    val isRooted get() = Info.isRooted\n    val skipOptions = Info.isEmulator || (Info.isSAR && !Info.isFDE && Info.ramdisk)\n    val noSecondSlot = !isRooted || !Info.isAB || Info.isEmulator\n\n    private val _uiState = MutableStateFlow(UiState(step = if (skipOptions) 1 else 0))\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n\n    init {\n        viewModelScope.launch(Dispatchers.IO) {\n            try {\n                val noteFile = File(AppContext.cacheDir, \"${APP_VERSION_CODE}.md\")\n                val noteText = when {\n                    noteFile.exists() -> noteFile.readText()\n                    else -> {\n                        val note = svc.fetchUpdate(APP_VERSION_CODE)?.note.orEmpty()\n                        if (note.isEmpty()) return@launch\n                        noteFile.writeText(note)\n                        note\n                    }\n                }\n                withContext(Dispatchers.Main) {\n                    _uiState.update { it.copy(notes = noteText) }\n                }\n            } catch (e: IOException) {\n                Timber.e(e)\n            }\n        }\n    }\n\n    fun nextStep() {\n        _uiState.update { it.copy(step = 1) }\n    }\n\n    fun selectMethod(method: Method) {\n        _uiState.update { it.copy(method = method) }\n        when (method) {\n            Method.PATCH -> {\n                AppContext.toast(CoreR.string.patch_file_msg, Toast.LENGTH_LONG)\n                _uiState.update { it.copy(requestFilePicker = true) }\n            }\n            Method.INACTIVE_SLOT -> {\n                _uiState.update { it.copy(showSecondSlotWarning = true) }\n            }\n            else -> {}\n        }\n    }\n\n    fun onFilePickerConsumed() {\n        _uiState.update { it.copy(requestFilePicker = false) }\n    }\n\n    fun onSecondSlotWarningConsumed() {\n        _uiState.update { it.copy(showSecondSlotWarning = false) }\n    }\n\n    fun onPatchFileSelected(uri: Uri) {\n        _uiState.update { it.copy(patchUri = uri) }\n        if (_uiState.value.method == Method.PATCH) {\n            install()\n        }\n    }\n\n    fun install() {\n        when (_uiState.value.method) {\n            Method.PATCH -> navigateTo(Route.Flash(\n                action = Const.Value.PATCH_FILE,\n                additionalData = _uiState.value.patchUri!!.toString()\n            ))\n            Method.DIRECT -> navigateTo(Route.Flash(\n                action = Const.Value.FLASH_MAGISK\n            ))\n            Method.INACTIVE_SLOT -> navigateTo(Route.Flash(\n                action = Const.Value.FLASH_INACTIVE_SLOT\n            ))\n            else -> error(\"Unknown method\")\n        }\n    }\n\n    val canInstall: Boolean\n        get() {\n            val state = _uiState.value\n            return when (state.method) {\n                Method.PATCH -> state.patchUri != null\n                Method.DIRECT, Method.INACTIVE_SLOT -> true\n                Method.NONE -> false\n            }\n        }\n\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/log/LogScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.NestedScrollConnection\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.topjohnwu.magisk.core.ktx.timeDateFormat\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport com.topjohnwu.magisk.ui.util.rememberDrawablePainter\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.CircularProgressIndicator\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.TabRow\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Delete\nimport top.yukonga.miuix.kmp.icon.extended.Download\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun LogScreen(viewModel: LogViewModel) {\n    val uiState by viewModel.uiState.collectAsState()\n    var selectedTab by rememberSaveable { mutableIntStateOf(0) }\n    val tabTitles = listOf(\n        stringResource(CoreR.string.superuser),\n        stringResource(CoreR.string.magisk)\n    )\n    val scrollBehavior = MiuixScrollBehavior()\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.logs),\n                actions = {\n                    if (selectedTab == 1) {\n                        IconButton(onClick = { viewModel.saveMagiskLog() }) {\n                            Icon(\n                                imageVector = MiuixIcons.Download,\n                                contentDescription = stringResource(CoreR.string.save_log),\n                            )\n                        }\n                    }\n                    IconButton(\n                        modifier = Modifier.padding(end = 16.dp),\n                        onClick = {\n                            if (selectedTab == 0) viewModel.clearLog()\n                            else viewModel.clearMagiskLog()\n                        }\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Delete,\n                            contentDescription = stringResource(CoreR.string.clear_log),\n                        )\n                    }\n                },\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        Column(modifier = Modifier\n            .fillMaxSize()\n            .padding(padding)\n        ) {\n            TabRow(\n                tabs = tabTitles,\n                selectedTabIndex = selectedTab,\n                onTabSelected = { selectedTab = it },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 12.dp, vertical = 8.dp)\n            )\n\n            if (uiState.loading) {\n                Box(\n                    modifier = Modifier.fillMaxSize(),\n                    contentAlignment = Alignment.Center\n                ) {\n                    CircularProgressIndicator()\n                }\n            } else {\n                when (selectedTab) {\n                    0 -> SuLogTab(\n                        logs = uiState.suLogs,\n                        nestedScrollConnection = scrollBehavior.nestedScrollConnection\n                    )\n                    1 -> MagiskLogTab(\n                        entries = uiState.magiskLogEntries,\n                        nestedScrollConnection = scrollBehavior.nestedScrollConnection\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SuLogTab(logs: List<SuLog>, nestedScrollConnection: NestedScrollConnection) {\n    Column(modifier = Modifier.fillMaxSize()) {\n        if (logs.isEmpty()) {\n            Box(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxWidth()\n                    .padding(horizontal = 12.dp),\n                contentAlignment = Alignment.Center\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.log_data_none),\n                    style = MiuixTheme.textStyles.body1,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                    textAlign = TextAlign.Center,\n                )\n            }\n        } else {\n            LazyColumn(\n                modifier = Modifier\n                    .weight(1f)\n                    .nestedScroll(nestedScrollConnection)\n                    .padding(horizontal = 12.dp),\n                contentPadding = PaddingValues(top = 8.dp, bottom = 88.dp),\n                verticalArrangement = Arrangement.spacedBy(8.dp)\n            ) {\n                items(logs, key = { it.id }) { log ->\n                    SuLogCard(log)\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SuLogCard(log: SuLog) {\n    val res = LocalContext.current.resources\n    val pm = LocalContext.current.packageManager\n    val icon = remember(log.packageName) {\n        runCatching {\n            pm.getApplicationInfo(log.packageName, 0).loadIcon(pm)\n        }.getOrDefault(pm.defaultActivityIcon)\n    }\n    val allowed = log.action >= 2\n\n    val uidPidText = buildString {\n        append(\"UID: ${log.toUid}  PID: ${log.fromPid}\")\n        if (log.target != -1) {\n            val target = if (log.target == 0) \"magiskd\" else log.target.toString()\n            append(\"  → $target\")\n        }\n    }\n\n    val details = buildString {\n        if (log.context.isNotEmpty()) {\n            append(res.getString(CoreR.string.selinux_context, log.context))\n        }\n        if (log.gids.isNotEmpty()) {\n            if (isNotEmpty()) append(\"\\n\")\n            append(res.getString(CoreR.string.supp_group, log.gids))\n        }\n        if (log.command.isNotEmpty()) {\n            if (isNotEmpty()) append(\"\\n\")\n            append(log.command)\n        }\n    }\n\n    Card(modifier = Modifier.fillMaxWidth()) {\n        Column(modifier = Modifier.padding(12.dp)) {\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                verticalAlignment = Alignment.Top\n            ) {\n                Image(\n                    painter = rememberDrawablePainter(icon),\n                    contentDescription = log.appName,\n                    modifier = Modifier.size(36.dp)\n                )\n                Spacer(Modifier.width(10.dp))\n                Column(modifier = Modifier.weight(1f)) {\n                    Text(\n                        text = log.appName,\n                        style = MiuixTheme.textStyles.body1,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis,\n                    )\n                    Text(\n                        text = uidPidText,\n                        style = MiuixTheme.textStyles.body2,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis,\n                    )\n                }\n                Spacer(Modifier.width(8.dp))\n                Column(horizontalAlignment = Alignment.End) {\n                    Text(\n                        text = log.time.toTime(timeDateFormat),\n                        fontSize = 11.sp,\n                        fontFamily = FontFamily.Monospace,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                        maxLines = 1,\n                    )\n                    Spacer(Modifier.height(4.dp))\n                    SuActionBadge(allowed)\n                }\n            }\n\n            if (details.isNotEmpty()) {\n                Spacer(Modifier.height(6.dp))\n                Text(\n                    text = details,\n                    fontFamily = FontFamily.Monospace,\n                    fontSize = 12.sp,\n                    lineHeight = 16.sp,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SuActionBadge(allowed: Boolean) {\n    val bg = if (allowed) MiuixTheme.colorScheme.primary else MiuixTheme.colorScheme.error\n    val fg = if (allowed) MiuixTheme.colorScheme.onPrimary else MiuixTheme.colorScheme.onError\n    val text = if (allowed) \"Approved\" else \"Rejected\"\n    Text(\n        text = text,\n        color = fg,\n        fontSize = 10.sp,\n        maxLines = 1,\n        modifier = Modifier\n            .background(bg, RoundedCornerShape(6.dp))\n            .padding(horizontal = 6.dp, vertical = 2.dp)\n    )\n}\n\n@Composable\nprivate fun MagiskLogTab(\n    entries: List<MagiskLogEntry>,\n    nestedScrollConnection: NestedScrollConnection\n) {\n    Column(modifier = Modifier.fillMaxSize()) {\n        if (entries.isEmpty()) {\n            Box(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxWidth()\n                    .padding(horizontal = 12.dp),\n                contentAlignment = Alignment.Center\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.log_data_magisk_none),\n                    style = MiuixTheme.textStyles.body1,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                    textAlign = TextAlign.Center,\n                )\n            }\n        } else {\n            val listState = rememberLazyListState(initialFirstVisibleItemIndex = entries.size - 1)\n            LazyColumn(\n                state = listState,\n                modifier = Modifier\n                    .weight(1f)\n                    .nestedScroll(nestedScrollConnection)\n                    .padding(horizontal = 12.dp),\n                contentPadding = PaddingValues(top = 8.dp, bottom = 88.dp),\n                verticalArrangement = Arrangement.spacedBy(4.dp)\n            ) {\n                items(entries.size, key = { it }) { index ->\n                    MagiskLogCard(entries[index])\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun MagiskLogCard(entry: MagiskLogEntry) {\n    var expanded by remember { mutableStateOf(false) }\n\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .clickable { expanded = !expanded }\n    ) {\n        Column(modifier = Modifier.padding(12.dp)) {\n            if (entry.isParsed) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.SpaceBetween,\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Row(\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.spacedBy(6.dp),\n                        modifier = Modifier.weight(1f)\n                    ) {\n                        LogLevelBadge(entry.level)\n                        Text(\n                            text = entry.tag,\n                            style = MiuixTheme.textStyles.body1,\n                            fontWeight = FontWeight.Normal,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis,\n                        )\n                    }\n                    Spacer(Modifier.width(8.dp))\n                    Text(\n                        text = entry.timestamp,\n                        fontSize = 11.sp,\n                        fontFamily = FontFamily.Monospace,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                        maxLines = 1,\n                    )\n                }\n                Spacer(Modifier.height(4.dp))\n            }\n\n            Text(\n                text = entry.message,\n                fontFamily = FontFamily.Monospace,\n                fontSize = 12.sp,\n                lineHeight = 16.sp,\n                color = MiuixTheme.colorScheme.onSurface,\n                maxLines = if (expanded) Int.MAX_VALUE else 3,\n                overflow = TextOverflow.Ellipsis,\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun LogLevelBadge(level: Char) {\n    val (bg, fg) = when (level) {\n        'V' -> Color(0xFF9E9E9E) to Color.White\n        'D' -> Color(0xFF2196F3) to Color.White\n        'I' -> Color(0xFF4CAF50) to Color.White\n        'W' -> Color(0xFFFFC107) to Color.Black\n        'E' -> Color(0xFFF44336) to Color.White\n        'F' -> Color(0xFF9C27B0) to Color.White\n        else -> Color(0xFF757575) to Color.White\n    }\n    Box(\n        modifier = Modifier\n            .clip(RoundedCornerShape(4.dp))\n            .background(bg)\n            .padding(horizontal = 5.dp, vertical = 1.dp),\n        contentAlignment = Alignment.Center\n    ) {\n        Text(\n            text = level.toString(),\n            fontSize = 10.sp,\n            fontWeight = FontWeight.Bold,\n            fontFamily = FontFamily.Monospace,\n            color = fg,\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\nimport android.system.Os\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport com.topjohnwu.magisk.core.repository.LogRepository\nimport com.topjohnwu.magisk.core.su.SuEvents\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.FileInputStream\n\nclass LogViewModel(\n    private val repo: LogRepository\n) : AsyncLoadViewModel() {\n\n    init {\n        @OptIn(kotlinx.coroutines.FlowPreview::class)\n        viewModelScope.launch {\n            SuEvents.logUpdated.debounce(500).collect { reload() }\n        }\n    }\n\n    data class UiState(\n        val loading: Boolean = true,\n        val magiskLog: String = \"\",\n        val magiskLogEntries: List<MagiskLogEntry> = emptyList(),\n        val suLogs: List<SuLog> = emptyList(),\n    )\n\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n\n    private var magiskLogRaw = \"\"\n\n    override suspend fun doLoadWork() {\n        _uiState.update { it.copy(loading = true) }\n        withContext(Dispatchers.Default) {\n            magiskLogRaw = repo.fetchMagiskLogs()\n            val suLogs = repo.fetchSuLogs()\n            val entries = MagiskLogParser.parse(magiskLogRaw)\n            _uiState.update { it.copy(\n                loading = false,\n                magiskLog = magiskLogRaw,\n                magiskLogEntries = entries,\n                suLogs = suLogs,\n            ) }\n        }\n    }\n\n    fun saveMagiskLog() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val filename = \"magisk_log_%s.log\".format(\n                System.currentTimeMillis().toTime(timeFormatStandard))\n            val logFile = MediaStoreUtils.getFile(filename)\n            logFile.uri.outputStream().bufferedWriter().use { file ->\n                file.write(\"---Detected Device Info---\\n\\n\")\n                file.write(\"isAB=${Info.isAB}\\n\")\n                file.write(\"isSAR=${Info.isSAR}\\n\")\n                file.write(\"ramdisk=${Info.ramdisk}\\n\")\n                val uname = Os.uname()\n                file.write(\"kernel=${uname.sysname} ${uname.machine} ${uname.release} ${uname.version}\\n\")\n\n                file.write(\"\\n\\n---System Properties---\\n\\n\")\n                ProcessBuilder(\"getprop\").start()\n                    .inputStream.reader().use { it.copyTo(file) }\n\n                file.write(\"\\n\\n---Environment Variables---\\n\\n\")\n                System.getenv().forEach { (key, value) -> file.write(\"${key}=${value}\\n\") }\n\n                file.write(\"\\n\\n---System MountInfo---\\n\\n\")\n                FileInputStream(\"/proc/self/mountinfo\").reader().use { it.copyTo(file) }\n\n                file.write(\"\\n---Magisk Logs---\\n\")\n                file.write(\"${Info.env.versionString} (${Info.env.versionCode})\\n\\n\")\n                if (Info.env.isActive) file.write(magiskLogRaw)\n\n                file.write(\"\\n---Manager Logs---\\n\")\n                file.write(\"${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\\n\\n\")\n                ProcessBuilder(\"logcat\", \"-d\").start()\n                    .inputStream.reader().use { it.copyTo(file) }\n            }\n            showSnackbar(logFile.toString())\n        }\n    }\n\n    fun clearMagiskLog() = repo.clearMagiskLogs {\n        showSnackbar(R.string.logs_cleared)\n        startLoading()\n    }\n\n    fun clearLog() = viewModelScope.launch {\n        repo.clearLogs()\n        showSnackbar(R.string.logs_cleared)\n        startLoading()\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/log/MagiskLogParser.kt",
    "content": "package com.topjohnwu.magisk.ui.log\n\ndata class MagiskLogEntry(\n    val timestamp: String = \"\",\n    val pid: Int = 0,\n    val tid: Int = 0,\n    val level: Char = 'I',\n    val tag: String = \"\",\n    val message: String = \"\",\n    val isParsed: Boolean = false,\n)\n\nobject MagiskLogParser {\n\n    // Logcat format: \"MM-DD HH:MM:SS.mmm  PID  TID LEVEL TAG     : message\"\n    private val logcatRegex = Regex(\n        \"\"\"(\\d{2}-\\d{2}\\s+\\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s+(\\d+)\\s+(\\d+)\\s+([VDIWEF])\\s+(.+?)\\s*:\\s+(.*)\"\"\"\n    )\n\n    fun parse(raw: String): List<MagiskLogEntry> {\n        if (raw.isBlank()) return emptyList()\n\n        val lines = raw.lines()\n        val result = mutableListOf<MagiskLogEntry>()\n\n        for (line in lines) {\n            if (line.isBlank()) continue\n\n            val match = logcatRegex.find(line)\n            if (match != null) {\n                result.add(\n                    MagiskLogEntry(\n                        timestamp = match.groupValues[1],\n                        pid = match.groupValues[2].toIntOrNull() ?: 0,\n                        tid = match.groupValues[3].toIntOrNull() ?: 0,\n                        level = match.groupValues[4].firstOrNull() ?: 'I',\n                        tag = match.groupValues[5].trim(),\n                        message = match.groupValues[6],\n                        isParsed = true,\n                    )\n                )\n            } else if (result.isNotEmpty() && result.last().isParsed) {\n                // Continuation line — append to previous entry\n                val prev = result.last()\n                result[result.lastIndex] = prev.copy(\n                    message = prev.message + \"\\n\" + line.trimEnd()\n                )\n            } else {\n                result.add(\n                    MagiskLogEntry(message = line.trimEnd())\n                )\n            }\n        }\n        return result\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/module/ActionScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.painterResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.ui.terminal.TerminalScreen\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.SmallTopAppBar\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Back\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun ActionScreen(viewModel: ActionViewModel, actionName: String, onBack: () -> Unit) {\n    val actionState by viewModel.actionState.collectAsState()\n    val finished = actionState != ActionViewModel.State.RUNNING\n\n    val scrollBehavior = MiuixScrollBehavior()\n    Scaffold(\n        topBar = {\n            SmallTopAppBar(\n                title = actionName,\n                navigationIcon = {\n                    IconButton(\n                        modifier = Modifier.padding(start = 16.dp),\n                        onClick = onBack\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Back,\n                            contentDescription = null,\n                            tint = MiuixTheme.colorScheme.onBackground\n                        )\n                    }\n                },\n                actions = {\n                    if (finished) {\n                        IconButton(\n                            modifier = Modifier.padding(end = 16.dp),\n                            onClick = { viewModel.saveLog() }\n                        ) {\n                            Icon(\n                                painter = painterResource(R.drawable.ic_save_md2),\n                                contentDescription = stringResource(CoreR.string.menuSaveLog),\n                                tint = MiuixTheme.colorScheme.onBackground\n                            )\n                        }\n                    }\n                },\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        TerminalScreen(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(padding),\n            onEmulatorCreated = { viewModel.onEmulatorCreated(it) },\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/module/ActionViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.ktx.timeFormatStandard\nimport com.topjohnwu.magisk.core.ktx.toTime\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.terminal.TerminalEmulator\nimport com.topjohnwu.magisk.terminal.runSuCommand\nimport kotlinx.coroutines.CompletableDeferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass ActionViewModel : BaseViewModel() {\n\n    enum class State {\n        RUNNING, SUCCESS, FAILED\n    }\n\n    private val _actionState = MutableStateFlow(State.RUNNING)\n    val actionState: StateFlow<State> = _actionState.asStateFlow()\n\n    var actionId: String = \"\"\n    var actionName: String = \"\"\n\n    private var emulator: TerminalEmulator? = null\n    private val emulatorReady = CompletableDeferred<TerminalEmulator>()\n\n    fun onEmulatorCreated(emu: TerminalEmulator) {\n        emulator = emu\n        emulatorReady.complete(emu)\n    }\n\n    fun startRunAction() {\n        viewModelScope.launch {\n            val emu = emulatorReady.await()\n\n            val success = withContext(Dispatchers.IO) {\n                runSuCommand(\n                    emu,\n                    \"cd /data/adb/modules/$actionId && sh ./action.sh\"\n                )\n            }\n\n            _actionState.value = if (success) State.SUCCESS else State.FAILED\n        }\n    }\n\n    fun saveLog() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val name = \"%s_action_log_%s.log\".format(\n                actionName,\n                System.currentTimeMillis().toTime(timeFormatStandard)\n            )\n            val file = MediaStoreUtils.getFile(name)\n            file.uri.outputStream().bufferedWriter().use { writer ->\n                val transcript = emulator?.screen?.transcriptText\n                if (transcript != null) {\n                    writer.write(transcript)\n                }\n            }\n            showSnackbar(file.toString())\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/module/ModuleScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.net.Uri\nimport android.provider.OpenableColumns\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.MutableState\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextDecoration\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.model.module.OnlineModule\nimport com.topjohnwu.magisk.ui.MainActivity\nimport com.topjohnwu.magisk.ui.component.ConfirmResult\nimport com.topjohnwu.magisk.ui.component.MarkdownTextAsync\nimport com.topjohnwu.magisk.ui.component.rememberConfirmDialog\nimport kotlinx.coroutines.launch\nimport top.yukonga.miuix.kmp.basic.ButtonDefaults\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.CircularProgressIndicator\nimport top.yukonga.miuix.kmp.basic.FloatingActionButton\nimport top.yukonga.miuix.kmp.basic.HorizontalDivider\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.Switch\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TextButton\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.extra.SuperDialog\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Add\nimport top.yukonga.miuix.kmp.icon.extended.Delete\nimport top.yukonga.miuix.kmp.icon.extended.Play\nimport top.yukonga.miuix.kmp.icon.extended.Undo\nimport top.yukonga.miuix.kmp.icon.extended.UploadCloud\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun ModuleScreen(viewModel: ModuleViewModel) {\n    val uiState by viewModel.uiState.collectAsState()\n    val scrollBehavior = MiuixScrollBehavior()\n    val colorScheme = MiuixTheme.colorScheme\n    val context = LocalContext.current\n    val scope = rememberCoroutineScope()\n    val activity = context as MainActivity\n\n    var pendingZipUri by remember { mutableStateOf<Uri?>(null) }\n    var pendingZipName by remember { mutableStateOf(\"\") }\n    val localInstallDialog = rememberConfirmDialog()\n    val confirmInstallTitle = stringResource(CoreR.string.confirm_install_title)\n\n    var pendingOnlineModule by remember { mutableStateOf<OnlineModule?>(null) }\n    val showOnlineDialog = rememberSaveable { mutableStateOf(false) }\n\n    val filePicker = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->\n        if (uri != null) {\n            val displayName = context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->\n                val idx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)\n                if (cursor.moveToFirst() && idx >= 0) cursor.getString(idx) else null\n            } ?: uri.lastPathSegment ?: \"module.zip\"\n            pendingZipUri = uri\n            pendingZipName = displayName\n            scope.launch {\n                val result = localInstallDialog.awaitConfirm(\n                    title = confirmInstallTitle,\n                    content = context.getString(CoreR.string.confirm_install, displayName),\n                )\n                if (result == ConfirmResult.Confirmed) {\n                    viewModel.confirmLocalInstall(uri)\n                }\n                pendingZipUri = null\n            }\n        }\n    }\n\n    if (showOnlineDialog.value && pendingOnlineModule != null) {\n        OnlineModuleDialog(\n            item = pendingOnlineModule!!,\n            showDialog = showOnlineDialog,\n            onDownload = { install ->\n                showOnlineDialog.value = false\n                DownloadEngine.startWithActivity(\n                    activity,\n                    OnlineModuleSubject(pendingOnlineModule!!, install)\n                )\n                pendingOnlineModule = null\n            },\n            onDismiss = {\n                showOnlineDialog.value = false\n                pendingOnlineModule = null\n            }\n        )\n    }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.modules),\n                scrollBehavior = scrollBehavior\n            )\n        },\n        floatingActionButton = {\n            FloatingActionButton(\n                onClick = { filePicker.launch(\"application/zip\") },\n                shadowElevation = 0.dp,\n                modifier = Modifier\n                    .padding(bottom = 88.dp, end = 20.dp)\n                    .border(0.05.dp, colorScheme.outline.copy(alpha = 0.5f), CircleShape),\n                content = {\n                    Icon(\n                        imageVector = MiuixIcons.Add,\n                        contentDescription = stringResource(CoreR.string.module_action_install_external),\n                        modifier = Modifier.size(28.dp),\n                        tint = colorScheme.onPrimary\n                    )\n                },\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        if (uiState.loading) {\n            Box(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding),\n                contentAlignment = Alignment.Center\n            ) {\n                CircularProgressIndicator()\n            }\n            return@Scaffold\n        }\n\n        if (uiState.modules.isEmpty()) {\n            Box(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding),\n                contentAlignment = Alignment.Center\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.module_empty),\n                    style = MiuixTheme.textStyles.body1,\n                    color = colorScheme.onSurfaceVariantSummary\n                )\n            }\n            return@Scaffold\n        }\n\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .padding(padding)\n                .padding(horizontal = 12.dp),\n            contentPadding = PaddingValues(bottom = 160.dp),\n            verticalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            item { Spacer(Modifier.height(4.dp)) }\n            items(uiState.modules, key = { it.module.id }) { item ->\n                ModuleCard(\n                    item = item,\n                    viewModel = viewModel,\n                    onUpdateClick = { onlineModule ->\n                        if (onlineModule != null && Info.isConnected.value == true) {\n                            pendingOnlineModule = onlineModule\n                            showOnlineDialog.value = true\n                        }\n                    }\n                )\n            }\n            item { Spacer(Modifier.height(4.dp)) }\n        }\n    }\n}\n\n@Composable\nprivate fun ModuleCard(item: ModuleItem, viewModel: ModuleViewModel, onUpdateClick: (OnlineModule?) -> Unit) {\n    val infoAlpha = if (!item.isRemoved && item.isEnabled && !item.showNotice) 1f else 0.5f\n    val strikeThrough = if (item.isRemoved) TextDecoration.LineThrough else TextDecoration.None\n    val colorScheme = MiuixTheme.colorScheme\n    val actionIconTint = colorScheme.onSurface.copy(alpha = 0.8f)\n    val actionBg = colorScheme.secondaryContainer.copy(alpha = 0.8f)\n    val updateBg = colorScheme.tertiaryContainer.copy(alpha = 0.6f)\n    val updateTint = colorScheme.onTertiaryContainer.copy(alpha = 0.8f)\n    val removeBg = colorScheme.errorContainer.copy(alpha = 0.6f)\n    val removeTint = colorScheme.onErrorContainer.copy(alpha = 0.8f)\n    var expanded by rememberSaveable(item.module.id) { mutableStateOf(false) }\n    val hasDescription = item.module.description.isNotBlank()\n\n    Card(\n        modifier = Modifier.fillMaxWidth(),\n        insideMargin = PaddingValues(16.dp),\n        onClick = { if (hasDescription) expanded = !expanded }\n    ) {\n        Column(modifier = Modifier.alpha(infoAlpha)) {\n            Row(\n                modifier = Modifier.fillMaxWidth(),\n                horizontalArrangement = Arrangement.spacedBy(8.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Column(\n                    modifier = Modifier\n                        .weight(1f)\n                        .padding(end = 4.dp)\n                ) {\n                    Text(\n                        text = item.module.name,\n                        style = MiuixTheme.textStyles.body1,\n                        textDecoration = strikeThrough,\n                    )\n                    Text(\n                        text = stringResource(\n                            CoreR.string.module_version_author,\n                            item.module.version,\n                            item.module.author\n                        ),\n                        style = MiuixTheme.textStyles.body2,\n                        color = colorScheme.onSurfaceVariantSummary,\n                        textDecoration = strikeThrough,\n                    )\n                }\n                Switch(\n                    checked = item.isEnabled,\n                    onCheckedChange = { viewModel.toggleEnabled(item) }\n                )\n            }\n\n            if (hasDescription) {\n                Box(\n                    modifier = Modifier\n                        .padding(top = 2.dp)\n                        .animateContentSize()\n                ) {\n                    Text(\n                        text = item.module.description,\n                        style = MiuixTheme.textStyles.body2,\n                        color = colorScheme.onSurfaceVariantSummary,\n                        textDecoration = strikeThrough,\n                        overflow = if (expanded) TextOverflow.Clip else TextOverflow.Ellipsis,\n                        maxLines = if (expanded) Int.MAX_VALUE else 4,\n                    )\n                }\n            }\n\n            if (item.showNotice) {\n                Spacer(Modifier.height(4.dp))\n                Text(\n                    text = item.noticeText,\n                    style = MiuixTheme.textStyles.body2,\n                    color = colorScheme.primary,\n                )\n            }\n        }\n\n        HorizontalDivider(\n            modifier = Modifier.padding(vertical = 8.dp),\n            thickness = 0.5.dp,\n            color = colorScheme.outline.copy(alpha = 0.5f)\n        )\n\n        Row(verticalAlignment = Alignment.CenterVertically) {\n            AnimatedVisibility(\n                visible = item.isEnabled && !item.isRemoved,\n                enter = fadeIn(),\n                exit = fadeOut()\n            ) {\n                Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {\n                    if (item.showAction) {\n                        IconButton(\n                            backgroundColor = actionBg,\n                            minHeight = 35.dp,\n                            minWidth = 35.dp,\n                            onClick = { viewModel.runAction(item.module.id, item.module.name) },\n                        ) {\n                            Row(\n                                modifier = Modifier.padding(horizontal = 10.dp),\n                                verticalAlignment = Alignment.CenterVertically,\n                                horizontalArrangement = Arrangement.spacedBy(4.dp),\n                            ) {\n                                Icon(\n                                    modifier = Modifier.size(20.dp),\n                                    imageVector = MiuixIcons.Play,\n                                    tint = actionIconTint,\n                                    contentDescription = stringResource(CoreR.string.module_action)\n                                )\n                                Text(\n                                    text = stringResource(CoreR.string.module_action),\n                                    color = actionIconTint,\n                                    style = MiuixTheme.textStyles.body2,\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n\n            Spacer(Modifier.weight(1f))\n\n            AnimatedVisibility(\n                visible = item.showUpdate && item.updateReady,\n                enter = fadeIn(),\n                exit = fadeOut()\n            ) {\n                IconButton(\n                    modifier = Modifier.padding(end = 8.dp),\n                    backgroundColor = updateBg,\n                    minHeight = 35.dp,\n                    minWidth = 35.dp,\n                    onClick = { onUpdateClick(item.module.updateInfo) },\n                ) {\n                    Row(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        verticalAlignment = Alignment.CenterVertically,\n                        horizontalArrangement = Arrangement.spacedBy(4.dp),\n                    ) {\n                        Icon(\n                            modifier = Modifier.size(20.dp),\n                            imageVector = MiuixIcons.UploadCloud,\n                            tint = updateTint,\n                            contentDescription = stringResource(CoreR.string.update),\n                        )\n                        Text(\n                            text = stringResource(CoreR.string.update),\n                            color = updateTint,\n                            style = MiuixTheme.textStyles.body2,\n                        )\n                    }\n                }\n            }\n\n            IconButton(\n                backgroundColor = if (item.isRemoved) actionBg else removeBg,\n                minHeight = 35.dp,\n                minWidth = 35.dp,\n                onClick = { viewModel.toggleRemove(item) },\n                enabled = !item.isUpdated\n            ) {\n                val tint = if (item.isRemoved) actionIconTint else removeTint\n                Row(\n                    modifier = Modifier.padding(horizontal = 10.dp),\n                    verticalAlignment = Alignment.CenterVertically,\n                    horizontalArrangement = Arrangement.spacedBy(4.dp),\n                ) {\n                    Icon(\n                        modifier = Modifier.size(20.dp),\n                        imageVector = if (item.isRemoved) MiuixIcons.Undo else MiuixIcons.Delete,\n                        tint = tint,\n                        contentDescription = null\n                    )\n                    Text(\n                        text = stringResource(\n                            if (item.isRemoved) CoreR.string.module_state_restore\n                            else CoreR.string.module_state_remove\n                        ),\n                        color = tint,\n                        style = MiuixTheme.textStyles.body2,\n                    )\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun OnlineModuleDialog(\n    item: OnlineModule,\n    showDialog: MutableState<Boolean>,\n    onDownload: (install: Boolean) -> Unit,\n    onDismiss: () -> Unit,\n) {\n    val svc = ServiceLocator.networkService\n    val title = stringResource(\n        CoreR.string.repo_install_title,\n        item.name, item.version, item.versionCode\n    )\n\n    SuperDialog(\n        show = showDialog,\n        title = title,\n        onDismissRequest = onDismiss,\n    ) {\n        MarkdownTextAsync {\n            val str = svc.fetchString(item.changelog)\n            if (str.length > 1000) str.substring(0, 1000) else str\n        }\n        Spacer(Modifier.height(16.dp))\n        Row(\n            modifier = Modifier.fillMaxWidth(),\n            horizontalArrangement = Arrangement.End,\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            TextButton(\n                text = stringResource(android.R.string.cancel),\n                onClick = onDismiss,\n            )\n            Spacer(Modifier.weight(1f))\n            TextButton(\n                text = stringResource(CoreR.string.download),\n                onClick = { onDownload(false) },\n            )\n            Spacer(Modifier.width(8.dp))\n            TextButton(\n                text = stringResource(CoreR.string.install),\n                onClick = { onDownload(true) },\n                colors = ButtonDefaults.textButtonColorsPrimary()\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/module/ModuleViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.module\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.core.model.module.OnlineModule\nimport com.topjohnwu.magisk.ui.flash.FlashUtils\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.withContext\nimport kotlinx.parcelize.Parcelize\n\nclass ModuleItem(val module: LocalModule) {\n    val showNotice: Boolean\n    val showAction: Boolean\n    val noticeText: String\n\n    init {\n        val isZygisk = module.isZygisk\n        val isRiru = module.isRiru\n        val zygiskUnloaded = isZygisk && module.zygiskUnloaded\n\n        showNotice = zygiskUnloaded ||\n            (Info.isZygiskEnabled && isRiru) ||\n            (!Info.isZygiskEnabled && isZygisk)\n        showAction = module.hasAction && !showNotice\n        noticeText = when {\n            zygiskUnloaded -> \"Zygisk module not loaded due to incompatibility\"\n            isRiru -> \"Module suspended because Zygisk is enabled\"\n            else -> \"Module suspended because Zygisk isn't enabled\"\n        }\n    }\n\n    var isEnabled by mutableStateOf(module.enable)\n    var isRemoved by mutableStateOf(module.remove)\n    var showUpdate by mutableStateOf(module.updateInfo != null)\n    val isUpdated = module.updated\n    val updateReady get() = module.outdated && !isRemoved && isEnabled\n}\n\n@Parcelize\nclass OnlineModuleSubject(\n    override val module: OnlineModule,\n    override val autoLaunch: Boolean,\n    override val notifyId: Int = Notifications.nextId()\n) : Subject.Module() {\n    override fun pendingIntent(context: Context) = FlashUtils.installIntent(context, file)\n}\n\nclass ModuleViewModel : AsyncLoadViewModel() {\n\n    data class UiState(\n        val loading: Boolean = true,\n        val modules: List<ModuleItem> = emptyList(),\n    )\n\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n\n    override suspend fun doLoadWork() {\n        _uiState.update { it.copy(loading = true) }\n        val moduleLoaded = Info.env.isActive &&\n            withContext(Dispatchers.IO) { LocalModule.loaded() }\n        if (moduleLoaded) {\n            val modules = withContext(Dispatchers.Default) {\n                LocalModule.installed().map { ModuleItem(it) }\n            }\n            _uiState.update { it.copy(loading = false, modules = modules) }\n            loadUpdateInfo()\n        } else {\n            _uiState.update { it.copy(loading = false) }\n        }\n    }\n\n    private val networkObserver: (Boolean) -> Unit = { startLoading() }\n\n    init {\n        Info.isConnected.observeForever(networkObserver)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        Info.isConnected.removeObserver(networkObserver)\n    }\n\n    private suspend fun loadUpdateInfo() {\n        withContext(Dispatchers.IO) {\n            _uiState.value.modules.forEach { item ->\n                if (item.module.fetch()) {\n                    item.showUpdate = item.module.updateInfo != null\n                }\n            }\n        }\n    }\n\n    fun confirmLocalInstall(uri: Uri) {\n        navigateTo(Route.Flash(Const.Value.FLASH_ZIP, uri.toString()))\n    }\n\n    fun runAction(id: String, name: String) {\n        navigateTo(Route.Action(id, name))\n    }\n\n    fun toggleEnabled(item: ModuleItem) {\n        item.isEnabled = !item.isEnabled\n        item.module.enable = item.isEnabled\n    }\n\n    fun toggleRemove(item: ModuleItem) {\n        item.isRemoved = !item.isRemoved\n        item.module.remove = item.isRemoved\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/navigation/CollectNavEvents.kt",
    "content": "package com.topjohnwu.magisk.ui.navigation\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport com.topjohnwu.magisk.arch.BaseViewModel\n\n@Composable\nfun CollectNavEvents(viewModel: BaseViewModel, navigator: Navigator) {\n    LaunchedEffect(viewModel) {\n        viewModel.navEvents.collect { route ->\n            navigator.push(route)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/navigation/Navigator.kt",
    "content": "package com.topjohnwu.magisk.ui.navigation\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.mutableStateListOf\nimport androidx.compose.runtime.saveable.Saver\nimport androidx.compose.runtime.saveable.listSaver\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.snapshots.SnapshotStateList\nimport androidx.compose.runtime.staticCompositionLocalOf\nimport androidx.navigation3.runtime.NavKey\n\nclass Navigator(initialKey: NavKey) {\n    val backStack: SnapshotStateList<NavKey> = mutableStateListOf(initialKey)\n\n    fun push(key: NavKey) {\n        backStack.add(key)\n    }\n\n    fun replace(key: NavKey) {\n        if (backStack.isNotEmpty()) {\n            backStack[backStack.lastIndex] = key\n        } else {\n            backStack.add(key)\n        }\n    }\n\n    fun replaceAll(keys: List<NavKey>) {\n        if (keys.isEmpty()) return\n        if (backStack.isNotEmpty()) {\n            backStack.clear()\n            backStack.addAll(keys)\n        }\n    }\n\n    fun pop() {\n        backStack.removeLastOrNull()\n    }\n\n    fun popUntil(predicate: (NavKey) -> Boolean) {\n        while (backStack.isNotEmpty() && !predicate(backStack.last())) {\n            backStack.removeAt(backStack.lastIndex)\n        }\n    }\n\n    fun current(): NavKey? = backStack.lastOrNull()\n\n    fun backStackSize(): Int = backStack.size\n\n    companion object {\n        val Saver: Saver<Navigator, Any> = listSaver(\n            save = { navigator -> navigator.backStack.toList() },\n            restore = { savedList ->\n                val initialKey = savedList.firstOrNull() ?: Route.Main\n                Navigator(initialKey).also {\n                    it.backStack.clear()\n                    it.backStack.addAll(savedList)\n                }\n            }\n        )\n    }\n}\n\n@Composable\nfun rememberNavigator(startRoute: NavKey): Navigator {\n    return rememberSaveable(startRoute, saver = Navigator.Saver) {\n        Navigator(startRoute)\n    }\n}\n\nval LocalNavigator = staticCompositionLocalOf<Navigator> {\n    error(\"LocalNavigator not provided\")\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/navigation/Routes.kt",
    "content": "package com.topjohnwu.magisk.ui.navigation\n\nimport android.os.Parcelable\nimport androidx.navigation3.runtime.NavKey\nimport kotlinx.parcelize.Parcelize\nimport kotlinx.serialization.Serializable\n\nsealed interface Route : NavKey, Parcelable {\n    @Parcelize\n    @Serializable\n    data object Main : Route\n\n    @Parcelize\n    @Serializable\n    data object DenyList : Route\n\n    @Parcelize\n    @Serializable\n    data class Flash(\n        val action: String,\n        val additionalData: String? = null,\n    ) : Route\n\n    @Parcelize\n    @Serializable\n    data class SuperuserDetail(val uid: Int) : Route\n\n    @Parcelize\n    @Serializable\n    data class Action(\n        val id: String,\n        val name: String,\n    ) : Route\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.os.Build\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.DpSize\nimport androidx.compose.ui.unit.dp\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.ui.theme.ThemeState\nimport top.yukonga.miuix.kmp.basic.ButtonDefaults\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.SmallTitle\nimport top.yukonga.miuix.kmp.basic.TextButton\nimport top.yukonga.miuix.kmp.basic.TextField\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.extra.SuperArrow\nimport top.yukonga.miuix.kmp.extra.SuperDialog\nimport top.yukonga.miuix.kmp.extra.SuperDropdown\nimport top.yukonga.miuix.kmp.extra.SuperSwitch\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun SettingsScreen(viewModel: SettingsViewModel) {\n    val scrollBehavior = MiuixScrollBehavior()\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.settings),\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .verticalScroll(rememberScrollState())\n                .padding(padding)\n                .padding(horizontal = 12.dp)\n                .padding(bottom = 88.dp)\n        ) {\n            CustomizationSection(viewModel)\n            Spacer(Modifier.height(12.dp))\n            AppSettingsSection(viewModel)\n            if (Info.env.isActive) {\n                Spacer(Modifier.height(12.dp))\n                MagiskSection(viewModel)\n            }\n            if (Info.showSuperUser) {\n                Spacer(Modifier.height(12.dp))\n                SuperuserSection(viewModel)\n            }\n        }\n    }\n}\n\n// --- Customization ---\n\n@Composable\nprivate fun CustomizationSection(viewModel: SettingsViewModel) {\n    val context = LocalContext.current\n\n    SmallTitle(text = stringResource(CoreR.string.settings_customization))\n    Card(modifier = Modifier.fillMaxWidth()) {\n        if (LocaleSetting.useLocaleManager) {\n            val locale = LocaleSetting.instance.appLocale\n            val summary = locale?.getDisplayName(locale) ?: stringResource(CoreR.string.system_default)\n            SuperArrow(\n                title = stringResource(CoreR.string.language),\n                summary = summary,\n                onClick = {\n                    context.startActivity(LocaleSetting.localeSettingsIntent)\n                }\n            )\n        } else {\n            val names = remember { LocaleSetting.available.names }\n            val tags = remember { LocaleSetting.available.tags }\n            var selectedIndex by remember {\n                mutableIntStateOf(tags.indexOf(Config.locale).coerceAtLeast(0))\n            }\n            SuperDropdown(\n                title = stringResource(CoreR.string.language),\n                items = names.toList(),\n                selectedIndex = selectedIndex,\n                onSelectedIndexChange = { index ->\n                    selectedIndex = index\n                    Config.locale = tags[index]\n                }\n            )\n        }\n\n        // Color Mode\n        val resources = context.resources\n        val colorModeEntries = remember {\n            resources.getStringArray(CoreR.array.color_mode).toList()\n        }\n        var colorMode by remember { mutableIntStateOf(Config.colorMode) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.settings_color_mode),\n            items = colorModeEntries,\n            selectedIndex = colorMode,\n            onSelectedIndexChange = { index ->\n                colorMode = index\n                Config.colorMode = index\n                ThemeState.colorMode = index\n            }\n        )\n\n        if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context)) {\n            SuperArrow(\n                title = stringResource(CoreR.string.add_shortcut_title),\n                summary = stringResource(CoreR.string.setting_add_shortcut_summary),\n                onClick = { viewModel.requestAddShortcut() }\n            )\n        }\n    }\n}\n\n// --- App Settings ---\n\n@Composable\nprivate fun AppSettingsSection(viewModel: SettingsViewModel) {\n    val context = LocalContext.current\n    val resources = context.resources\n    val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME\n\n    SmallTitle(text = stringResource(CoreR.string.home_app_title))\n    Card(modifier = Modifier.fillMaxWidth()) {\n        // Update Channel\n        val updateChannelEntries = remember {\n            resources.getStringArray(CoreR.array.update_channel).toList()\n        }\n        var updateChannel by remember {\n            mutableIntStateOf(Config.updateChannel.coerceIn(0, updateChannelEntries.size - 1))\n        }\n        var showUrlDialog by remember { mutableStateOf(false) }\n\n        SuperDropdown(\n            title = stringResource(CoreR.string.settings_update_channel_title),\n            items = updateChannelEntries,\n            selectedIndex = updateChannel,\n            onSelectedIndexChange = { index ->\n                updateChannel = index\n                Config.updateChannel = index\n                Info.resetUpdate()\n                if (index == Config.Value.CUSTOM_CHANNEL && Config.customChannelUrl.isBlank()) {\n                    showUrlDialog = true\n                }\n            }\n        )\n\n        // Update Channel URL (for custom channel)\n        if (updateChannel == Config.Value.CUSTOM_CHANNEL) {\n            UpdateChannelUrlDialog(\n                show = showUrlDialog,\n                onDismiss = { showUrlDialog = false }\n            )\n            SuperArrow(\n                title = stringResource(CoreR.string.settings_update_custom),\n                summary = Config.customChannelUrl.ifBlank { null },\n                onClick = { showUrlDialog = true }\n            )\n        }\n\n        // DoH Toggle\n        var doh by remember { mutableStateOf(Config.doh) }\n        SuperSwitch(\n            title = stringResource(CoreR.string.settings_doh_title),\n            summary = stringResource(CoreR.string.settings_doh_description),\n            checked = doh,\n            onCheckedChange = {\n                doh = it\n                Config.doh = it\n            }\n        )\n\n        // Update Checker\n        var checkUpdate by remember { mutableStateOf(Config.checkUpdate) }\n        SuperSwitch(\n            title = stringResource(CoreR.string.settings_check_update_title),\n            summary = stringResource(CoreR.string.settings_check_update_summary),\n            checked = checkUpdate,\n            onCheckedChange = { newValue ->\n                checkUpdate = newValue\n                Config.checkUpdate = newValue\n            }\n        )\n\n        // Download Path\n        var showDownloadDialog by remember { mutableStateOf(false) }\n        DownloadPathDialog(\n            show = showDownloadDialog,\n            onDismiss = { showDownloadDialog = false }\n        )\n        SuperArrow(\n            title = stringResource(CoreR.string.settings_download_path_title),\n            summary = MediaStoreUtils.fullPath(Config.downloadDir),\n            onClick = {\n                showDownloadDialog = true\n            }\n        )\n\n        // Random Package Name\n        var randName by remember { mutableStateOf(Config.randName) }\n        SuperSwitch(\n            title = stringResource(CoreR.string.settings_random_name_title),\n            summary = stringResource(CoreR.string.settings_random_name_description),\n            checked = randName,\n            onCheckedChange = {\n                randName = it\n                Config.randName = it\n            }\n        )\n\n    }\n}\n\n// --- Magisk ---\n\n@Composable\nprivate fun MagiskSection(viewModel: SettingsViewModel) {\n    SmallTitle(text = stringResource(CoreR.string.magisk))\n    Card(modifier = Modifier.fillMaxWidth()) {\n        // Systemless Hosts\n        SuperArrow(\n            title = stringResource(CoreR.string.settings_hosts_title),\n            summary = stringResource(CoreR.string.settings_hosts_summary),\n            onClick = { viewModel.createHosts() }\n        )\n\n        if (Const.Version.atLeast_24_0()) {\n            // Zygisk\n            var zygisk by remember { mutableStateOf(Config.zygisk) }\n            SuperSwitch(\n                title = stringResource(CoreR.string.zygisk),\n                summary = stringResource(\n                    if (zygisk != Info.isZygiskEnabled) CoreR.string.reboot_apply_change\n                    else CoreR.string.settings_zygisk_summary\n                ),\n                checked = zygisk,\n                onCheckedChange = {\n                    zygisk = it\n                    Config.zygisk = it\n                    viewModel.notifyZygiskChange()\n                }\n            )\n\n            // DenyList\n            val denyListEnabled by viewModel.denyListEnabled.collectAsState()\n            SuperSwitch(\n                title = stringResource(CoreR.string.settings_denylist_title),\n                summary = stringResource(CoreR.string.settings_denylist_summary),\n                checked = denyListEnabled,\n                onCheckedChange = { viewModel.toggleDenyList(it) }\n            )\n\n            // DenyList Config\n            SuperArrow(\n                title = stringResource(CoreR.string.settings_denylist_config_title),\n                summary = stringResource(CoreR.string.settings_denylist_config_summary),\n                onClick = { viewModel.navigateToDenyList() }\n            )\n        }\n    }\n}\n\n// --- Superuser ---\n\n@Composable\nprivate fun SuperuserSection(viewModel: SettingsViewModel) {\n    val context = LocalContext.current\n    val resources = context.resources\n\n    SmallTitle(text = stringResource(CoreR.string.superuser))\n    Card(modifier = Modifier.fillMaxWidth()) {\n        // Tapjack (SDK < S)\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {\n            var tapjack by remember { mutableStateOf(Config.suTapjack) }\n            SuperSwitch(\n                title = stringResource(CoreR.string.settings_su_tapjack_title),\n                summary = stringResource(CoreR.string.settings_su_tapjack_summary),\n                checked = tapjack,\n                onCheckedChange = {\n                    tapjack = it\n                    Config.suTapjack = it\n                }\n            )\n        }\n\n        // Authentication\n        var suAuth by remember { mutableStateOf(Config.suAuth) }\n        SuperSwitch(\n            title = stringResource(CoreR.string.settings_su_auth_title),\n            summary = stringResource(\n                if (Info.isDeviceSecure) CoreR.string.settings_su_auth_summary\n                else CoreR.string.settings_su_auth_insecure\n            ),\n            checked = suAuth,\n            enabled = Info.isDeviceSecure,\n            onCheckedChange = { newValue ->\n                viewModel.withAuth {\n                    suAuth = newValue\n                    Config.suAuth = newValue\n                }\n            }\n        )\n\n        // Access Mode\n        val accessEntries = remember {\n            resources.getStringArray(CoreR.array.su_access).toList()\n        }\n        var accessMode by remember { mutableIntStateOf(Config.rootMode) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.superuser_access),\n            items = accessEntries,\n            selectedIndex = accessMode,\n            onSelectedIndexChange = {\n                accessMode = it\n                Config.rootMode = it\n            }\n        )\n\n        // Multiuser Mode\n        val multiuserEntries = remember {\n            resources.getStringArray(CoreR.array.multiuser_mode).toList()\n        }\n        val multiuserDescriptions = remember {\n            resources.getStringArray(CoreR.array.multiuser_summary).toList()\n        }\n        var multiuserMode by remember { mutableIntStateOf(Config.suMultiuserMode) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.multiuser_mode),\n            summary = multiuserDescriptions.getOrElse(multiuserMode) { \"\" },\n            items = multiuserEntries,\n            selectedIndex = multiuserMode,\n            enabled = Const.USER_ID == 0,\n            onSelectedIndexChange = {\n                multiuserMode = it\n                Config.suMultiuserMode = it\n            }\n        )\n\n        // Mount Namespace Mode\n        val namespaceEntries = remember {\n            resources.getStringArray(CoreR.array.namespace).toList()\n        }\n        val namespaceDescriptions = remember {\n            resources.getStringArray(CoreR.array.namespace_summary).toList()\n        }\n        var mntNamespaceMode by remember { mutableIntStateOf(Config.suMntNamespaceMode) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.mount_namespace_mode),\n            summary = namespaceDescriptions.getOrElse(mntNamespaceMode) { \"\" },\n            items = namespaceEntries,\n            selectedIndex = mntNamespaceMode,\n            onSelectedIndexChange = {\n                mntNamespaceMode = it\n                Config.suMntNamespaceMode = it\n            }\n        )\n\n        // Automatic Response\n        val autoResponseEntries = remember {\n            resources.getStringArray(CoreR.array.auto_response).toList()\n        }\n        var autoResponse by remember { mutableIntStateOf(Config.suAutoResponse) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.auto_response),\n            items = autoResponseEntries,\n            selectedIndex = autoResponse,\n            onSelectedIndexChange = { newIndex ->\n                val doIt = {\n                    autoResponse = newIndex\n                    Config.suAutoResponse = newIndex\n                }\n                if (Config.suAuth) viewModel.withAuth(doIt) else doIt()\n            }\n        )\n\n        // Request Timeout\n        val timeoutEntries = remember {\n            resources.getStringArray(CoreR.array.request_timeout).toList()\n        }\n        val timeoutValues = remember { listOf(10, 15, 20, 30, 45, 60) }\n        var timeoutIndex by remember {\n            mutableIntStateOf(timeoutValues.indexOf(Config.suDefaultTimeout).coerceAtLeast(0))\n        }\n        SuperDropdown(\n            title = stringResource(CoreR.string.request_timeout),\n            items = timeoutEntries,\n            selectedIndex = timeoutIndex,\n            onSelectedIndexChange = {\n                timeoutIndex = it\n                Config.suDefaultTimeout = timeoutValues[it]\n            }\n        )\n\n        // SU Notification\n        val notifEntries = remember {\n            resources.getStringArray(CoreR.array.su_notification).toList()\n        }\n        var suNotification by remember { mutableIntStateOf(Config.suNotification) }\n        SuperDropdown(\n            title = stringResource(CoreR.string.superuser_notification),\n            items = notifEntries,\n            selectedIndex = suNotification,\n            onSelectedIndexChange = {\n                suNotification = it\n                Config.suNotification = it\n            }\n        )\n\n        // Reauthenticate (SDK < O)\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n            var reAuth by remember { mutableStateOf(Config.suReAuth) }\n            SuperSwitch(\n                title = stringResource(CoreR.string.settings_su_reauth_title),\n                summary = stringResource(CoreR.string.settings_su_reauth_summary),\n                checked = reAuth,\n                onCheckedChange = {\n                    reAuth = it\n                    Config.suReAuth = it\n                }\n            )\n        }\n\n        // Restrict (version >= 30.1)\n        if (Const.Version.atLeast_30_1()) {\n            var restrict by remember { mutableStateOf(Config.suRestrict) }\n            SuperSwitch(\n                title = stringResource(CoreR.string.settings_su_restrict_title),\n                summary = stringResource(CoreR.string.settings_su_restrict_summary),\n                checked = restrict,\n                onCheckedChange = {\n                    restrict = it\n                    Config.suRestrict = it\n                }\n            )\n        }\n    }\n}\n\n// --- Dialogs ---\n\n@Composable\nprivate fun UpdateChannelUrlDialog(show: Boolean, onDismiss: () -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(show) }\n    showState.value = show\n    var url by rememberSaveable { mutableStateOf(Config.customChannelUrl) }\n\n    SuperDialog(\n        show = showState,\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            TextField(\n                value = url,\n                onValueChange = { url = it },\n                modifier = Modifier.fillMaxWidth(),\n                label = stringResource(CoreR.string.settings_update_custom_msg)\n            )\n            Spacer(Modifier.height(16.dp))\n            TextButton(\n                text = stringResource(android.R.string.ok),\n                onClick = {\n                    Config.customChannelUrl = url\n                    Info.resetUpdate()\n                    onDismiss()\n                },\n                modifier = Modifier.fillMaxWidth()\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun DownloadPathDialog(show: Boolean, onDismiss: () -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(show) }\n    showState.value = show\n    var path by rememberSaveable { mutableStateOf(Config.downloadDir) }\n\n    SuperDialog(\n        show = showState,\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            top.yukonga.miuix.kmp.basic.Text(\n                text = stringResource(CoreR.string.settings_download_path_message, MediaStoreUtils.fullPath(path)),\n                modifier = Modifier.padding(bottom = 8.dp)\n            )\n            TextField(\n                value = path,\n                onValueChange = { path = it },\n                modifier = Modifier.fillMaxWidth(),\n                label = stringResource(CoreR.string.settings_download_path_title)\n            )\n            Spacer(Modifier.height(16.dp))\n            TextButton(\n                text = stringResource(android.R.string.ok),\n                onClick = {\n                    Config.downloadDir = path\n                    onDismiss()\n                },\n                modifier = Modifier.fillMaxWidth()\n            )\n        }\n    }\n}\n\n@Composable\nprivate fun HideAppDialog(show: Boolean, onDismiss: () -> Unit, onConfirm: (String) -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(show) }\n    showState.value = show\n    var appName by rememberSaveable { mutableStateOf(\"Settings\") }\n    val isError = appName.length > AppMigration.MAX_LABEL_LENGTH || appName.isBlank()\n\n    SuperDialog(\n        show = showState,\n        title = stringResource(CoreR.string.settings_hide_app_title),\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            TextField(\n                value = appName,\n                onValueChange = { appName = it },\n                modifier = Modifier.fillMaxWidth(),\n                label = stringResource(CoreR.string.settings_app_name_hint),\n            )\n            Spacer(Modifier.height(16.dp))\n            Row(\n                horizontalArrangement = Arrangement.SpaceBetween,\n            ) {\n                TextButton(\n                    text = stringResource(android.R.string.cancel),\n                    onClick = onDismiss,\n                    modifier = Modifier.weight(1f)\n                )\n                Spacer(Modifier.width(20.dp))\n                TextButton(\n                    text = stringResource(android.R.string.ok),\n                    onClick = { if (!isError) onConfirm(appName) },\n                    modifier = Modifier.weight(1f),\n                    colors = ButtonDefaults.textButtonColorsPrimary()\n                )\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun RestoreDialog(show: Boolean, onDismiss: () -> Unit, onConfirm: () -> Unit) {\n    val showState = rememberSaveable { mutableStateOf(show) }\n    showState.value = show\n\n    SuperDialog(\n        show = showState,\n        title = stringResource(CoreR.string.settings_restore_app_title),\n        onDismissRequest = onDismiss,\n        insideMargin = DpSize(24.dp, 24.dp)\n    ) {\n        Column(modifier = Modifier.padding(top = 8.dp)) {\n            top.yukonga.miuix.kmp.basic.Text(\n                text = stringResource(CoreR.string.restore_app_confirmation),\n                color = MiuixTheme.colorScheme.onSurface,\n            )\n            Spacer(Modifier.height(16.dp))\n            Row(\n                horizontalArrangement = Arrangement.SpaceBetween,\n            ) {\n                TextButton(\n                    text = stringResource(android.R.string.cancel),\n                    onClick = onDismiss,\n                    modifier = Modifier.weight(1f)\n                )\n                Spacer(Modifier.width(20.dp))\n                TextButton(\n                    text = stringResource(android.R.string.ok),\n                    onClick = onConfirm,\n                    modifier = Modifier.weight(1f),\n                    colors = ButtonDefaults.textButtonColorsPrimary()\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/settings/SettingsViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.settings\n\nimport android.content.Context\nimport android.widget.Toast\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport com.topjohnwu.magisk.view.Shortcuts\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass SettingsViewModel : BaseViewModel() {\n\n    private val _denyListEnabled = MutableStateFlow(Config.denyList)\n    val denyListEnabled: StateFlow<Boolean> = _denyListEnabled.asStateFlow()\n\n    val zygiskMismatch get() = Config.zygisk != Info.isZygiskEnabled\n\n    var authenticate: (onSuccess: () -> Unit) -> Unit = { it() }\n\n    fun navigateToDenyList() {\n        navigateTo(Route.DenyList)\n    }\n\n    fun requestAddShortcut() {\n        Shortcuts.addHomeIcon(AppContext)\n    }\n\n    suspend fun hideApp(context: Context, name: String): Boolean {\n        val success = withContext(Dispatchers.IO) {\n            AppMigration.patchAndHide(context, name)\n        }\n        if (!success) {\n            context.toast(R.string.failure, Toast.LENGTH_LONG)\n        }\n        return success\n    }\n\n    suspend fun restoreApp(context: Context): Boolean {\n        val success = AppMigration.restoreApp(context)\n        if (!success) {\n            context.toast(R.string.failure, Toast.LENGTH_LONG)\n        }\n        return success\n    }\n\n    fun createHosts() {\n        viewModelScope.launch {\n            RootUtils.addSystemlessHosts()\n            AppContext.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)\n        }\n    }\n\n    fun toggleDenyList(enabled: Boolean) {\n        _denyListEnabled.value = enabled\n        val cmd = if (enabled) \"enable\" else \"disable\"\n        Shell.cmd(\"magisk --denylist $cmd\").submit { result ->\n            if (result.isSuccess) {\n                Config.denyList = enabled\n            } else {\n                _denyListEnabled.value = !enabled\n            }\n        }\n    }\n\n    fun withAuth(action: () -> Unit) = authenticate(action)\n\n    fun notifyZygiskChange() {\n        if (zygiskMismatch) showSnackbar(R.string.reboot_apply_change)\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserDetailScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport com.topjohnwu.magisk.ui.component.ConfirmResult\nimport com.topjohnwu.magisk.ui.component.rememberConfirmDialog\nimport com.topjohnwu.magisk.ui.util.rememberDrawablePainter\nimport kotlinx.coroutines.launch\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.Icon\nimport top.yukonga.miuix.kmp.basic.IconButton\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.Switch\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.icon.MiuixIcons\nimport top.yukonga.miuix.kmp.icon.extended.Back\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun SuperuserDetailScreen(\n    uid: Int,\n    viewModel: SuperuserViewModel,\n    onBack: () -> Unit,\n) {\n    val uiState by viewModel.uiState.collectAsState()\n    val items = uiState.policies.filter { it.policy.uid == uid }\n    val item = items.firstOrNull()\n    val scrollBehavior = MiuixScrollBehavior()\n    val scope = rememberCoroutineScope()\n    val revokeDialog = rememberConfirmDialog()\n    val revokeTitle = stringResource(CoreR.string.su_revoke_title)\n    val revokeMsg = item?.let { stringResource(CoreR.string.su_revoke_msg, it.appName) } ?: \"\"\n\n    LaunchedEffect(Unit) { viewModel.refreshSuRestrict() }\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.superuser_setting),\n                navigationIcon = {\n                    IconButton(\n                        modifier = Modifier.padding(start = 16.dp),\n                        onClick = onBack\n                    ) {\n                        Icon(\n                            imageVector = MiuixIcons.Back,\n                            contentDescription = stringResource(CoreR.string.back),\n                        )\n                    }\n                },\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        if (item == null) return@Scaffold\n\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .padding(padding)\n                .padding(horizontal = 12.dp),\n            contentPadding = PaddingValues(bottom = 88.dp),\n            verticalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            item {\n                Spacer(Modifier.height(4.dp))\n            }\n\n            item {\n                Card(modifier = Modifier.fillMaxWidth()) {\n                    Row(\n                        modifier = Modifier.padding(16.dp),\n                        verticalAlignment = Alignment.CenterVertically\n                    ) {\n                        Image(\n                            painter = rememberDrawablePainter(item.icon),\n                            contentDescription = item.appName,\n                            modifier = Modifier.size(48.dp)\n                        )\n                        Spacer(Modifier.width(16.dp))\n                        Column {\n                            Row(verticalAlignment = Alignment.CenterVertically) {\n                                Text(\n                                    text = item.title,\n                                    style = MiuixTheme.textStyles.headline2,\n                                    modifier = Modifier.weight(1f, fill = false),\n                                )\n                                if (item.isSharedUid) {\n                                    Spacer(Modifier.width(6.dp))\n                                    SharedUidBadge()\n                                }\n                            }\n                            Text(\n                                text = item.packageName,\n                                style = MiuixTheme.textStyles.body2,\n                                color = MiuixTheme.colorScheme.onSurfaceVariantSummary\n                            )\n                            Text(\n                                text = \"UID: ${item.policy.uid}\",\n                                style = MiuixTheme.textStyles.body2,\n                                color = MiuixTheme.colorScheme.onSurfaceVariantSummary\n                            )\n                        }\n                    }\n                }\n            }\n\n            item {\n                Card(modifier = Modifier.fillMaxWidth()) {\n                    Column {\n                        if (uiState.suRestrict || item.isRestricted) {\n                            SwitchRow(\n                                title = stringResource(CoreR.string.settings_su_restrict_title),\n                                checked = item.isRestricted,\n                                onCheckedChange = { viewModel.toggleRestrict(item) }\n                            )\n                        }\n                        SwitchRow(\n                            title = stringResource(CoreR.string.superuser_toggle_notification),\n                            checked = item.notification,\n                            onCheckedChange = { viewModel.updateNotify(item) }\n                        )\n                        SwitchRow(\n                            title = stringResource(CoreR.string.logs),\n                            checked = item.logging,\n                            onCheckedChange = { viewModel.updateLogging(item) }\n                        )\n                    }\n                }\n            }\n\n            item {\n                Card(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .clickable {\n                            if (viewModel.requiresAuth) {\n                                viewModel.authenticate { viewModel.performDelete(item, onBack) }\n                            } else {\n                                scope.launch {\n                                    val result = revokeDialog.awaitConfirm(\n                                        title = revokeTitle,\n                                        content = revokeMsg,\n                                    )\n                                    if (result == ConfirmResult.Confirmed) {\n                                        viewModel.performDelete(item, onBack)\n                                    }\n                                }\n                            }\n                        }\n                ) {\n                    RevokeRow()\n                }\n            }\n        }\n    }\n}\n\n@Composable\nprivate fun SwitchRow(\n    title: String,\n    checked: Boolean,\n    onCheckedChange: () -> Unit,\n) {\n    Row(\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 16.dp, vertical = 14.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Text(\n            text = title,\n            style = MiuixTheme.textStyles.body1,\n            modifier = Modifier.weight(1f),\n        )\n        Spacer(Modifier.width(16.dp))\n        Switch(\n            checked = checked,\n            onCheckedChange = { onCheckedChange() }\n        )\n    }\n}\n\n@Composable\nprivate fun RevokeRow() {\n    Text(\n        text = stringResource(CoreR.string.superuser_toggle_revoke),\n        style = MiuixTheme.textStyles.body1,\n        color = MiuixTheme.colorScheme.error,\n        modifier = Modifier\n            .fillMaxWidth()\n            .padding(horizontal = 16.dp, vertical = 16.dp)\n    )\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.topjohnwu.magisk.ui.navigation.LocalNavigator\nimport com.topjohnwu.magisk.ui.navigation.Route\nimport com.topjohnwu.magisk.ui.util.rememberDrawablePainter\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.CircularProgressIndicator\nimport top.yukonga.miuix.kmp.basic.MiuixScrollBehavior\nimport top.yukonga.miuix.kmp.basic.Scaffold\nimport top.yukonga.miuix.kmp.basic.Switch\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TopAppBar\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@Composable\nfun SuperuserScreen(viewModel: SuperuserViewModel) {\n    val uiState by viewModel.uiState.collectAsState()\n    val scrollBehavior = MiuixScrollBehavior()\n    val navigator = LocalNavigator.current\n\n    Scaffold(\n        topBar = {\n            TopAppBar(\n                title = stringResource(CoreR.string.superuser),\n                scrollBehavior = scrollBehavior\n            )\n        },\n        popupHost = { }\n    ) { padding ->\n        if (uiState.loading) {\n            Box(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding),\n                contentAlignment = Alignment.Center\n            ) {\n                CircularProgressIndicator()\n            }\n            return@Scaffold\n        }\n\n        if (uiState.policies.isEmpty()) {\n            Box(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .padding(padding),\n                contentAlignment = Alignment.Center\n            ) {\n                Text(\n                    text = stringResource(CoreR.string.superuser_policy_none),\n                    style = MiuixTheme.textStyles.body1,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary\n                )\n            }\n            return@Scaffold\n        }\n\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .nestedScroll(scrollBehavior.nestedScrollConnection)\n                .padding(padding)\n                .padding(horizontal = 12.dp),\n            contentPadding = PaddingValues(bottom = 88.dp),\n            verticalArrangement = Arrangement.spacedBy(8.dp)\n        ) {\n            item { Spacer(Modifier.height(4.dp)) }\n            items(uiState.policies, key = { \"${it.policy.uid}_${it.packageName}\" }) { item ->\n                PolicyCard(\n                    item = item,\n                    onToggle = { viewModel.togglePolicy(item) },\n                    onDetail = { navigator.push(Route.SuperuserDetail(item.policy.uid)) },\n                )\n            }\n            item { Spacer(Modifier.height(4.dp)) }\n        }\n    }\n}\n\n@Composable\nprivate fun PolicyCard(\n    item: PolicyItem,\n    onToggle: () -> Unit,\n    onDetail: () -> Unit,\n) {\n    Card(\n        modifier = Modifier\n            .fillMaxWidth()\n            .alpha(if (item.isEnabled) 1f else 0.5f)\n    ) {\n        Row(\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(IntrinsicSize.Min),\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n            Row(\n                modifier = Modifier\n                    .weight(1f)\n                    .clickable(onClick = onDetail)\n                    .padding(16.dp),\n                verticalAlignment = Alignment.CenterVertically\n            ) {\n                Image(\n                    painter = rememberDrawablePainter(item.icon),\n                    contentDescription = item.appName,\n                    modifier = Modifier.size(40.dp)\n                )\n                Spacer(Modifier.width(12.dp))\n                Column(modifier = Modifier.weight(1f)) {\n                    Row(verticalAlignment = Alignment.CenterVertically) {\n                        Text(\n                            text = item.title,\n                            style = MiuixTheme.textStyles.body1,\n                            modifier = Modifier.weight(1f, fill = false),\n                        )\n                        if (item.isSharedUid) {\n                            Spacer(Modifier.width(6.dp))\n                            SharedUidBadge()\n                        }\n                    }\n                    Text(\n                        text = item.packageName,\n                        style = MiuixTheme.textStyles.body2,\n                        color = MiuixTheme.colorScheme.onSurfaceVariantSummary\n                    )\n                }\n            }\n\n            Box(\n                modifier = Modifier\n                    .fillMaxHeight()\n                    .padding(vertical = 12.dp)\n                    .width(0.5.dp)\n                    .background(MiuixTheme.colorScheme.dividerLine)\n            )\n\n            Box(\n                modifier = Modifier\n                    .clickable(onClick = onToggle)\n                    .padding(horizontal = 20.dp, vertical = 16.dp),\n                contentAlignment = Alignment.Center\n            ) {\n                Switch(\n                    checked = item.isEnabled,\n                    onCheckedChange = { onToggle() }\n                )\n            }\n        }\n    }\n}\n\n@Composable\ninternal fun SharedUidBadge(modifier: Modifier = Modifier) {\n    Text(\n        text = \"SharedUID\",\n        color = MiuixTheme.colorScheme.onPrimary,\n        fontSize = 10.sp,\n        maxLines = 1,\n        modifier = modifier\n            .background(MiuixTheme.colorScheme.primary, RoundedCornerShape(6.dp))\n            .padding(horizontal = 6.dp, vertical = 2.dp)\n    )\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/superuser/SuperuserViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.superuser\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES\nimport android.graphics.drawable.Drawable\nimport android.os.Process\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.AsyncLoadViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.core.su.SuEvents\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.debounce\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.util.Locale\n\nclass PolicyItem(\n    val policy: SuPolicy,\n    val packageName: String,\n    val isSharedUid: Boolean,\n    val icon: Drawable,\n    val appName: String,\n) {\n    val title get() = appName\n    val showSlider = Config.suRestrict || policy.policy == SuPolicy.RESTRICT\n\n    var isExpanded by mutableStateOf(false)\n    var policyValue by mutableIntStateOf(policy.policy)\n    var notification by mutableStateOf(policy.notification)\n    var logging by mutableStateOf(policy.logging)\n\n    val isEnabled get() = policyValue >= SuPolicy.ALLOW\n    val isRestricted get() = policyValue == SuPolicy.RESTRICT\n}\n\nclass SuperuserViewModel(\n    private val db: PolicyDao\n) : AsyncLoadViewModel() {\n\n    var authenticate: (onSuccess: () -> Unit) -> Unit = { it() }\n\n    init {\n        @OptIn(kotlinx.coroutines.FlowPreview::class)\n        viewModelScope.launch {\n            SuEvents.policyChanged.debounce(500).collect { reload() }\n        }\n    }\n\n    data class UiState(\n        val loading: Boolean = true,\n        val policies: List<PolicyItem> = emptyList(),\n        val suRestrict: Boolean = Config.suRestrict,\n    )\n\n    private val _uiState = MutableStateFlow(UiState())\n    val uiState: StateFlow<UiState> = _uiState.asStateFlow()\n\n    @SuppressLint(\"InlinedApi\")\n    override suspend fun doLoadWork() {\n        if (!Info.showSuperUser) {\n            _uiState.update { it.copy(loading = false) }\n            return\n        }\n        _uiState.update { it.copy(loading = true) }\n        withContext(Dispatchers.IO) {\n            db.deleteOutdated()\n            db.delete(AppContext.applicationInfo.uid)\n            val policies = ArrayList<PolicyItem>()\n            val pm = AppContext.packageManager\n            for (policy in db.fetchAll()) {\n                val pkgs =\n                    if (policy.uid == Process.SYSTEM_UID) arrayOf(\"android\")\n                    else pm.getPackagesForUid(policy.uid)\n                if (pkgs == null) {\n                    db.delete(policy.uid)\n                    continue\n                }\n                val map = pkgs.mapNotNull { pkg ->\n                    try {\n                        val info = pm.getPackageInfo(pkg, MATCH_UNINSTALLED_PACKAGES)\n                        PolicyItem(\n                            policy = policy,\n                            packageName = info.packageName,\n                            isSharedUid = info.sharedUserId != null,\n                            icon = info.applicationInfo?.loadIcon(pm) ?: pm.defaultActivityIcon,\n                            appName = info.applicationInfo?.getLabel(pm) ?: info.packageName\n                        )\n                    } catch (_: PackageManager.NameNotFoundException) {\n                        null\n                    }\n                }\n                if (map.isEmpty()) {\n                    db.delete(policy.uid)\n                    continue\n                }\n                policies.addAll(map)\n            }\n            policies.sortWith(compareBy(\n                { it.appName.lowercase(Locale.ROOT) },\n                { it.packageName }\n            ))\n            _uiState.update { it.copy(loading = false, policies = policies, suRestrict = Config.suRestrict) }\n        }\n    }\n\n    fun refreshSuRestrict() {\n        _uiState.update { it.copy(suRestrict = Config.suRestrict) }\n    }\n\n    val requiresAuth get() = Config.suAuth\n\n    fun performDelete(item: PolicyItem, onDeleted: () -> Unit = {}) {\n        viewModelScope.launch {\n            db.delete(item.policy.uid)\n            _uiState.update { state ->\n                state.copy(policies = state.policies.filter { it.policy.uid != item.policy.uid })\n            }\n            onDeleted()\n        }\n    }\n\n    fun updateNotify(item: PolicyItem) {\n        item.notification = !item.notification\n        item.policy.notification = item.notification\n        viewModelScope.launch {\n            db.update(item.policy)\n            _uiState.value.policies\n                .filter { it.policy.uid == item.policy.uid }\n                .forEach { it.notification = item.notification }\n            val res = if (item.notification) R.string.su_snack_notif_on else R.string.su_snack_notif_off\n            showSnackbar(AppContext.getString(res, item.appName))\n        }\n    }\n\n    fun updateLogging(item: PolicyItem) {\n        item.logging = !item.logging\n        item.policy.logging = item.logging\n        viewModelScope.launch {\n            db.update(item.policy)\n            _uiState.value.policies\n                .filter { it.policy.uid == item.policy.uid }\n                .forEach { it.logging = item.logging }\n            val res = if (item.logging) R.string.su_snack_log_on else R.string.su_snack_log_off\n            showSnackbar(AppContext.getString(res, item.appName))\n        }\n    }\n\n    fun updatePolicy(item: PolicyItem, newPolicy: Int) {\n        fun updateState() {\n            viewModelScope.launch {\n                item.policy.policy = newPolicy\n                item.policyValue = newPolicy\n                db.update(item.policy)\n                _uiState.value.policies\n                    .filter { it.policy.uid == item.policy.uid }\n                    .forEach { it.policyValue = newPolicy }\n                val res = if (newPolicy >= SuPolicy.ALLOW) R.string.su_snack_grant else R.string.su_snack_deny\n                showSnackbar(AppContext.getString(res, item.appName))\n            }\n        }\n\n        if (Config.suAuth) {\n            authenticate { updateState() }\n        } else {\n            updateState()\n        }\n    }\n\n    fun togglePolicy(item: PolicyItem) {\n        val newPolicy = if (item.isEnabled) SuPolicy.DENY else SuPolicy.ALLOW\n        updatePolicy(item, newPolicy)\n    }\n\n    fun toggleRestrict(item: PolicyItem) {\n        val newPolicy = if (item.isRestricted) SuPolicy.ALLOW else SuPolicy.RESTRICT\n        updatePolicy(item, newPolicy)\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestActivity.kt",
    "content": "package com.topjohnwu.magisk.ui.surequest\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ActivityInfo\nimport android.content.res.Resources\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.Window\nimport android.view.WindowManager\nimport android.view.accessibility.AccessibilityEvent\nimport android.view.accessibility.AccessibilityNodeInfo\nimport android.view.accessibility.AccessibilityNodeProvider\nimport androidx.activity.compose.setContent\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.ui.Modifier\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.arch.VMFactory\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.base.ActivityExtension\nimport com.topjohnwu.magisk.core.base.UntrackedActivity\nimport com.topjohnwu.magisk.core.su.SuCallbackHandler\nimport com.topjohnwu.magisk.core.su.SuCallbackHandler.REQUEST\nimport com.topjohnwu.magisk.core.wrap\nimport com.topjohnwu.magisk.ui.theme.MagiskTheme\nimport com.topjohnwu.magisk.ui.theme.Theme\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport top.yukonga.miuix.kmp.utils.MiuixPopupUtils.Companion.MiuixPopupHost\n\nopen class SuRequestActivity : AppCompatActivity(), UntrackedActivity {\n\n    private val extension = ActivityExtension(this)\n    private val viewModel: SuRequestViewModel by lazy {\n        ViewModelProvider(this, VMFactory)[SuRequestViewModel::class.java]\n    }\n\n    init {\n        AppCompatDelegate.setDefaultNightMode(Config.darkTheme)\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base.wrap())\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        extension.onCreate(savedInstanceState)\n        supportRequestWindowFeature(Window.FEATURE_NO_TITLE)\n        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED\n        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        window.addFlags(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            window.setHideOverlayWindows(true)\n        }\n        setTheme(Theme.selected.themeRes)\n        super.onCreate(savedInstanceState)\n\n        viewModel.finishActivity = { finish() }\n        viewModel.authenticate = { onSuccess ->\n            extension.withAuthentication { if (it) onSuccess() }\n        }\n\n        if (intent.action == Intent.ACTION_VIEW) {\n            val action = intent.getStringExtra(\"action\")\n            if (action == REQUEST) {\n                viewModel.handleRequest(intent)\n            } else {\n                lifecycleScope.launch {\n                    withContext(Dispatchers.IO) {\n                        SuCallbackHandler.run(this@SuRequestActivity, action, intent.extras)\n                    }\n                    finish()\n                }\n            }\n        } else {\n            finish()\n        }\n\n        if (viewModel.useTapjackProtection) {\n            window.decorView.rootView.accessibilityDelegate = EmptyAccessibilityDelegate\n        }\n\n        setContent {\n            MagiskTheme {\n                Box(modifier = Modifier.fillMaxSize()) {\n                    SuRequestScreen(viewModel = viewModel)\n                    MiuixPopupHost()\n                }\n            }\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        extension.onSaveInstanceState(outState)\n    }\n\n    override fun getTheme(): Resources.Theme {\n        val theme = super.getTheme()\n        theme.applyStyle(R.style.Foundation_Floating, true)\n        return theme\n    }\n\n    @Deprecated(\"Use OnBackPressedDispatcher\")\n    override fun onBackPressed() {\n        viewModel.denyPressed()\n    }\n\n    override fun finish() {\n        super.finishAndRemoveTask()\n    }\n\n    private object EmptyAccessibilityDelegate : View.AccessibilityDelegate() {\n        override fun sendAccessibilityEvent(host: View, eventType: Int) {}\n        override fun performAccessibilityAction(host: View, action: Int, args: Bundle?) = true\n        override fun sendAccessibilityEventUnchecked(host: View, event: AccessibilityEvent) {}\n        override fun dispatchPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) = true\n        override fun onPopulateAccessibilityEvent(host: View, event: AccessibilityEvent) {}\n        override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) {}\n        override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {}\n        override fun addExtraDataToAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo, extraDataKey: String, arguments: Bundle?) {}\n        override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, event: AccessibilityEvent): Boolean = false\n        override fun getAccessibilityNodeProvider(host: View): AccessibilityNodeProvider? = null\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.surequest\n\nimport android.view.MotionEvent\nimport android.widget.Toast\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.widthIn\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.ExperimentalComposeUiApi\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.input.pointer.pointerInteropFilter\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringArrayResource\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.ui.superuser.SharedUidBadge\nimport com.topjohnwu.magisk.ui.util.rememberDrawablePainter\nimport top.yukonga.miuix.kmp.basic.ButtonDefaults\nimport top.yukonga.miuix.kmp.basic.Card\nimport top.yukonga.miuix.kmp.basic.Slider\nimport top.yukonga.miuix.kmp.basic.SliderDefaults\nimport top.yukonga.miuix.kmp.basic.Text\nimport top.yukonga.miuix.kmp.basic.TextButton\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport com.topjohnwu.magisk.core.R as CoreR\n\n@OptIn(ExperimentalComposeUiApi::class)\n@Composable\nfun SuRequestScreen(viewModel: SuRequestViewModel) {\n    if (!viewModel.showUi) return\n\n    val context = LocalContext.current\n    val icon = viewModel.icon\n    val title = viewModel.title\n    val packageName = viewModel.packageName\n    val grantEnabled = viewModel.grantEnabled\n    val denyCountdown = viewModel.denyCountdown\n    val selectedPosition = viewModel.selectedItemPosition\n    val timeoutEntries = stringArrayResource(CoreR.array.allow_timeout).toList()\n    // Slider order: Once(1), 10min(2), 20min(3), 30min(4), 60min(5), Forever(0)\n    val sliderToIndex = intArrayOf(1, 2, 3, 4, 5, 0)\n    val indexToSlider = remember {\n        IntArray(sliderToIndex.size).also { arr ->\n            sliderToIndex.forEachIndexed { slider, orig -> arr[orig] = slider }\n        }\n    }\n    val sliderValue = indexToSlider[selectedPosition].toFloat()\n    val sliderLabel by remember(sliderValue) {\n        derivedStateOf { timeoutEntries[sliderToIndex[sliderValue.toInt()]] }\n    }\n\n    val denyText = if (denyCountdown > 0) {\n        \"${stringResource(CoreR.string.deny)} ($denyCountdown)\"\n    } else {\n        stringResource(CoreR.string.deny)\n    }\n\n    Box(\n        modifier = Modifier.fillMaxSize(),\n        contentAlignment = Alignment.Center\n    ) {\n        Card(\n            modifier = Modifier\n                .widthIn(min = 320.dp, max = 420.dp)\n                .padding(24.dp)\n        ) {\n            Column(\n                modifier = Modifier.padding(20.dp),\n                horizontalAlignment = Alignment.CenterHorizontally\n            ) {\n                Row(\n                    verticalAlignment = Alignment.CenterVertically,\n                    modifier = Modifier.padding(horizontal = 8.dp)\n                ) {\n                    if (icon != null) {\n                        Image(\n                            painter = rememberDrawablePainter(icon),\n                            contentDescription = null,\n                            modifier = Modifier.size(40.dp)\n                        )\n                        Spacer(Modifier.width(12.dp))\n                    }\n                    Column {\n                        Row(verticalAlignment = Alignment.CenterVertically) {\n                            Text(\n                                text = title,\n                                style = MiuixTheme.textStyles.body1,\n                                fontWeight = FontWeight.Bold,\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis,\n                                modifier = Modifier.weight(1f, fill = false),\n                            )\n                            if (viewModel.isSharedUid) {\n                                Spacer(Modifier.width(6.dp))\n                                SharedUidBadge()\n                            }\n                        }\n                        Text(\n                            text = packageName,\n                            style = MiuixTheme.textStyles.body2,\n                            color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis,\n                        )\n                    }\n                }\n\n                Spacer(Modifier.height(16.dp))\n\n                Text(\n                    text = stringResource(CoreR.string.su_request_title),\n                    style = MiuixTheme.textStyles.body2,\n                    color = MiuixTheme.colorScheme.primary,\n                    fontWeight = FontWeight.Bold,\n                    textAlign = TextAlign.Center,\n                )\n\n                Spacer(Modifier.height(16.dp))\n\n                Text(\n                    text = \"Permission timeout: $sliderLabel\",\n                    style = MiuixTheme.textStyles.body2,\n                    color = MiuixTheme.colorScheme.onSurfaceVariantSummary,\n                    modifier = Modifier.fillMaxWidth().padding(start = 8.dp),\n                )\n                Spacer(Modifier.height(8.dp))\n                Slider(\n                    value = sliderValue,\n                    onValueChange = { value ->\n                        viewModel.spinnerTouched()\n                        val pos = value.toInt().coerceIn(0, sliderToIndex.lastIndex)\n                        viewModel.selectedItemPosition = sliderToIndex[pos]\n                    },\n                    valueRange = 0f..5f,\n                    steps = 4,\n                    showKeyPoints = true,\n                    height = 20.dp,\n                    hapticEffect = SliderDefaults.SliderHapticEffect.Step,\n                    modifier = Modifier.fillMaxWidth().padding(horizontal = 4.dp)\n                )\n                Spacer(Modifier.height(16.dp))\n\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.spacedBy(8.dp)\n                ) {\n                    TextButton(\n                        text = denyText,\n                        onClick = { viewModel.denyPressed() },\n                        modifier = Modifier.weight(1f),\n                        cornerRadius = 12.dp,\n                        minHeight = 40.dp,\n                        insideMargin = PaddingValues(horizontal = 16.dp, vertical = 8.dp),\n                    )\n                    TextButton(\n                        text = stringResource(CoreR.string.grant),\n                        enabled = grantEnabled,\n                        colors = ButtonDefaults.textButtonColorsPrimary(),\n                        onClick = { viewModel.grantPressed() },\n                        cornerRadius = 12.dp,\n                        minHeight = 40.dp,\n                        insideMargin = PaddingValues(horizontal = 16.dp, vertical = 8.dp),\n                        modifier = Modifier\n                            .weight(1f)\n                            .then(\n                                if (viewModel.useTapjackProtection) {\n                                    Modifier.pointerInteropFilter { event ->\n                                        if (event.flags and MotionEvent.FLAG_WINDOW_IS_OBSCURED != 0 ||\n                                            event.flags and MotionEvent.FLAG_WINDOW_IS_PARTIALLY_OBSCURED != 0\n                                        ) {\n                                            if (event.action == MotionEvent.ACTION_UP) {\n                                                context.toast(\n                                                    CoreR.string.touch_filtered_warning,\n                                                    Toast.LENGTH_SHORT\n                                                )\n                                            }\n                                            true\n                                        } else {\n                                            false\n                                        }\n                                    }\n                                } else Modifier\n                            )\n                    )\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/surequest/SuRequestViewModel.kt",
    "content": "package com.topjohnwu.magisk.ui.surequest\n\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.graphics.drawable.Drawable\nimport android.os.CountDownTimer\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.topjohnwu.magisk.arch.BaseViewModel\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.ALLOW\nimport com.topjohnwu.magisk.core.model.su.SuPolicy.Companion.DENY\nimport com.topjohnwu.magisk.core.su.SuRequestHandler\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.TimeUnit.SECONDS\n\nclass SuRequestViewModel(\n    policyDB: PolicyDao,\n    private val timeoutPrefs: SharedPreferences\n) : BaseViewModel() {\n\n    var authenticate: (onSuccess: () -> Unit) -> Unit = { it() }\n    var finishActivity: () -> Unit = {}\n\n    var icon by mutableStateOf<Drawable?>(null)\n    var title by mutableStateOf(\"\")\n    var packageName by mutableStateOf(\"\")\n    var isSharedUid by mutableStateOf(false)\n\n    var selectedItemPosition by mutableIntStateOf(0)\n    var grantEnabled by mutableStateOf(false)\n    var denyCountdown by mutableIntStateOf(0)\n\n    var showUi by mutableStateOf(false)\n    var useTapjackProtection by mutableStateOf(false)\n\n    private val handler = SuRequestHandler(AppContext.packageManager, policyDB)\n    private val millis = SECONDS.toMillis(Config.suDefaultTimeout.toLong())\n    private var timer = SuTimer(millis, 1000)\n    private var initialized = false\n\n    fun grantPressed() {\n        cancelTimer()\n        if (Config.suAuth) {\n            authenticate { respond(ALLOW) }\n        } else {\n            respond(ALLOW)\n        }\n    }\n\n    fun denyPressed() {\n        respond(DENY)\n    }\n\n    fun spinnerTouched() {\n        cancelTimer()\n    }\n\n    fun handleRequest(intent: Intent) {\n        viewModelScope.launch(Dispatchers.Default) {\n            if (handler.start(intent))\n                showDialog()\n            else\n                finishActivity()\n        }\n    }\n\n    private fun showDialog() {\n        val pm = handler.pm\n        val info = handler.pkgInfo\n        val app = info.applicationInfo\n\n        isSharedUid = info.sharedUserId != null\n        if (app == null) {\n            icon = pm.defaultActivityIcon\n            title = info.sharedUserId.toString()\n            packageName = info.sharedUserId.toString()\n        } else {\n            icon = app.loadIcon(pm)\n            title = app.getLabel(pm)\n            packageName = info.packageName\n        }\n\n        selectedItemPosition = timeoutPrefs.getInt(packageName, 0)\n        timer.start()\n        useTapjackProtection = Config.suTapjack\n        showUi = true\n        initialized = true\n    }\n\n    private fun respond(action: Int) {\n        if (!initialized) return\n        timer.cancel()\n\n        val pos = selectedItemPosition\n        timeoutPrefs.edit().putInt(packageName, pos).apply()\n\n        viewModelScope.launch {\n            handler.respond(action, Config.Value.TIMEOUT_LIST[pos])\n            finishActivity()\n        }\n    }\n\n    private fun cancelTimer() {\n        timer.cancel()\n        denyCountdown = 0\n    }\n\n    private inner class SuTimer(\n        private val millis: Long,\n        interval: Long\n    ) : CountDownTimer(millis, interval) {\n\n        override fun onTick(remains: Long) {\n            if (!grantEnabled && remains <= millis - 1000) {\n                grantEnabled = true\n            }\n            denyCountdown = (remains / 1000).toInt() + 1\n        }\n\n        override fun onFinish() {\n            denyCountdown = 0\n            respond(DENY)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/terminal/TerminalRenderer.kt",
    "content": "package com.topjohnwu.magisk.ui.terminal\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.PorterDuff\nimport android.graphics.Typeface\nimport com.topjohnwu.magisk.terminal.TerminalEmulator\nimport com.topjohnwu.magisk.terminal.TextStyle\nimport com.topjohnwu.magisk.terminal.WcWidth\n\n/**\n * Renderer of a [TerminalEmulator] into a [Canvas].\n *\n * Saves font metrics, so needs to be recreated each time the typeface or font size changes.\n */\nclass TerminalRenderer(\n    textSize: Int,\n    typeface: Typeface,\n) {\n    val textSize: Int = textSize\n    val typeface: Typeface = typeface\n    private val textPaint = Paint()\n\n    /** The width of a single mono spaced character obtained by [Paint.measureText] on a single 'X'. */\n    val fontWidth: Float\n\n    /** The [Paint.getFontSpacing]. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */\n    val fontLineSpacing: Int\n\n    /** The [Paint.ascent]. See http://www.fampennings.nl/maarten/android/08numgrid/font.png */\n    private val fontAscent: Int\n\n    /** The [fontLineSpacing] + [fontAscent]. */\n    val fontLineSpacingAndAscent: Int\n\n    private val asciiMeasures = FloatArray(127)\n\n    init {\n        textPaint.typeface = typeface\n        textPaint.isAntiAlias = true\n        textPaint.textSize = textSize.toFloat()\n\n        fontLineSpacing = kotlin.math.ceil(textPaint.fontSpacing).toInt()\n        fontAscent = kotlin.math.ceil(textPaint.ascent()).toInt()\n        fontLineSpacingAndAscent = fontLineSpacing + fontAscent\n        fontWidth = textPaint.measureText(\"X\")\n\n        val sb = StringBuilder(\" \")\n        for (i in asciiMeasures.indices) {\n            sb[0] = i.toChar()\n            asciiMeasures[i] = textPaint.measureText(sb, 0, 1)\n        }\n    }\n\n    /**\n     * Render the terminal to a canvas with at a specified row scroll, and an optional rectangular selection.\n     */\n    fun render(\n        mEmulator: TerminalEmulator,\n        canvas: Canvas,\n        topRow: Int,\n        selectionY1: Int,\n        selectionY2: Int,\n        selectionX1: Int,\n        selectionX2: Int,\n    ) {\n        val reverseVideo = mEmulator.isReverseVideo\n        val endRow = topRow + mEmulator.mRows\n        val columns = mEmulator.mColumns\n        val cursorCol = mEmulator.cursorCol\n        val cursorRow = mEmulator.cursorRow\n        val cursorVisible = mEmulator.shouldCursorBeVisible()\n        val screen = mEmulator.screen\n        val palette = mEmulator.mColors.currentColors\n        val cursorShape = mEmulator.cursorStyle\n\n        if (reverseVideo) {\n            canvas.drawColor(palette[TextStyle.COLOR_INDEX_FOREGROUND], PorterDuff.Mode.SRC)\n        }\n\n        var heightOffset = fontLineSpacingAndAscent.toFloat()\n        for (row in topRow until endRow) {\n            heightOffset += fontLineSpacing\n\n            val cursorX = if (row == cursorRow && cursorVisible) cursorCol else -1\n            var selx1 = -1\n            var selx2 = -1\n            if (row in selectionY1..selectionY2) {\n                if (row == selectionY1) selx1 = selectionX1\n                selx2 = if (row == selectionY2) selectionX2 else mEmulator.mColumns\n            }\n\n            val lineObject = screen.allocateFullLineIfNecessary(screen.externalToInternalRow(row))\n            val line = lineObject.text\n            val charsUsedInLine = lineObject.spaceUsed\n\n            var lastRunStyle = 0L\n            var lastRunInsideCursor = false\n            var lastRunInsideSelection = false\n            var lastRunStartColumn = -1\n            var lastRunStartIndex = 0\n            var lastRunFontWidthMismatch = false\n            var currentCharIndex = 0\n            var measuredWidthForRun = 0f\n\n            var column = 0\n            while (column < columns) {\n                val charAtIndex = line[currentCharIndex]\n                val charIsHighsurrogate = Character.isHighSurrogate(charAtIndex)\n                val charsForCodePoint = if (charIsHighsurrogate) 2 else 1\n                val codePoint = if (charIsHighsurrogate) {\n                    Character.toCodePoint(charAtIndex, line[currentCharIndex + 1])\n                } else {\n                    charAtIndex.code\n                }\n                val codePointWcWidth = WcWidth.width(codePoint)\n                val insideCursor = cursorX == column || (codePointWcWidth == 2 && cursorX == column + 1)\n                val insideSelection = column >= selx1 && column <= selx2\n                val style = lineObject.getStyle(column)\n\n                // Check if the measured text width for this code point is not the same as that expected by wcwidth().\n                // This could happen for some fonts which are not truly monospace, or for more exotic characters such as\n                // smileys which android font renders as wide.\n                // If this is detected, we draw this code point scaled to match what wcwidth() expects.\n                val measuredCodePointWidth = if (codePoint < asciiMeasures.size) {\n                    asciiMeasures[codePoint]\n                } else {\n                    textPaint.measureText(line, currentCharIndex, charsForCodePoint)\n                }\n                val fontWidthMismatch = kotlin.math.abs(measuredCodePointWidth / fontWidth - codePointWcWidth.toFloat()) > 0.01f\n\n                if (style != lastRunStyle || insideCursor != lastRunInsideCursor || insideSelection != lastRunInsideSelection ||\n                    fontWidthMismatch || lastRunFontWidthMismatch\n                ) {\n                    if (column != 0) {\n                        val columnWidthSinceLastRun = column - lastRunStartColumn\n                        val charsSinceLastRun = currentCharIndex - lastRunStartIndex\n                        val cursorColor = if (lastRunInsideCursor) mEmulator.mColors.currentColors[TextStyle.COLOR_INDEX_CURSOR] else 0\n                        var invertCursorTextColor = false\n                        if (lastRunInsideCursor && cursorShape == TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK) {\n                            invertCursorTextColor = true\n                        }\n                        drawTextRun(\n                            canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun,\n                            lastRunStartIndex, charsSinceLastRun, measuredWidthForRun,\n                            cursorColor, cursorShape, lastRunStyle,\n                            reverseVideo || invertCursorTextColor || lastRunInsideSelection,\n                        )\n                    }\n                    measuredWidthForRun = 0f\n                    lastRunStyle = style\n                    lastRunInsideCursor = insideCursor\n                    lastRunInsideSelection = insideSelection\n                    lastRunStartColumn = column\n                    lastRunStartIndex = currentCharIndex\n                    lastRunFontWidthMismatch = fontWidthMismatch\n                }\n                measuredWidthForRun += measuredCodePointWidth\n                column += codePointWcWidth\n                currentCharIndex += charsForCodePoint\n                while (currentCharIndex < charsUsedInLine && WcWidth.width(line, currentCharIndex) <= 0) {\n                    // Eat combining chars so that they are treated as part of the last non-combining code point,\n                    // instead of e.g. being considered inside the cursor in the next run.\n                    currentCharIndex += if (Character.isHighSurrogate(line[currentCharIndex])) 2 else 1\n                }\n            }\n\n            val columnWidthSinceLastRun = columns - lastRunStartColumn\n            val charsSinceLastRun = currentCharIndex - lastRunStartIndex\n            val cursorColor = if (lastRunInsideCursor) mEmulator.mColors.currentColors[TextStyle.COLOR_INDEX_CURSOR] else 0\n            var invertCursorTextColor = false\n            if (lastRunInsideCursor && cursorShape == TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK) {\n                invertCursorTextColor = true\n            }\n            drawTextRun(\n                canvas, line, palette, heightOffset, lastRunStartColumn, columnWidthSinceLastRun,\n                lastRunStartIndex, charsSinceLastRun, measuredWidthForRun,\n                cursorColor, cursorShape, lastRunStyle,\n                reverseVideo || invertCursorTextColor || lastRunInsideSelection,\n            )\n        }\n    }\n\n    private fun drawTextRun(\n        canvas: Canvas,\n        text: CharArray,\n        palette: IntArray,\n        y: Float,\n        startColumn: Int,\n        runWidthColumns: Int,\n        startCharIndex: Int,\n        runWidthChars: Int,\n        mes: Float,\n        cursor: Int,\n        cursorStyle: Int,\n        textStyle: Long,\n        reverseVideo: Boolean,\n    ) {\n        var foreColor = TextStyle.decodeForeColor(textStyle)\n        val effect = TextStyle.decodeEffect(textStyle)\n        var backColor = TextStyle.decodeBackColor(textStyle)\n        val bold = (effect and (TextStyle.CHARACTER_ATTRIBUTE_BOLD or TextStyle.CHARACTER_ATTRIBUTE_BLINK)) != 0\n        val underline = (effect and TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0\n        val italic = (effect and TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0\n        val strikeThrough = (effect and TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0\n        val dim = (effect and TextStyle.CHARACTER_ATTRIBUTE_DIM) != 0\n\n        if ((foreColor and 0xff000000.toInt()) != 0xff000000.toInt()) {\n            // Let bold have bright colors if applicable (one of the first 8):\n            if (bold && foreColor in 0..7) foreColor += 8\n            foreColor = palette[foreColor]\n        }\n\n        if ((backColor and 0xff000000.toInt()) != 0xff000000.toInt()) {\n            backColor = palette[backColor]\n        }\n\n        // Reverse video here if _one and only one_ of the reverse flags are set:\n        val reverseVideoHere = reverseVideo xor ((effect and TextStyle.CHARACTER_ATTRIBUTE_INVERSE) != 0)\n        if (reverseVideoHere) {\n            val tmp = foreColor\n            foreColor = backColor\n            backColor = tmp\n        }\n\n        var left = startColumn * fontWidth\n        var right = left + runWidthColumns * fontWidth\n\n        var adjustedMes = mes / fontWidth\n        var savedMatrix = false\n        if (kotlin.math.abs(adjustedMes - runWidthColumns) > 0.01) {\n            canvas.save()\n            canvas.scale(runWidthColumns / adjustedMes, 1f)\n            left *= adjustedMes / runWidthColumns\n            right *= adjustedMes / runWidthColumns\n            savedMatrix = true\n        }\n\n        if (backColor != palette[TextStyle.COLOR_INDEX_BACKGROUND]) {\n            // Only draw non-default background.\n            textPaint.color = backColor\n            canvas.drawRect(left, y - fontLineSpacingAndAscent + fontAscent, right, y, textPaint)\n        }\n\n        if (cursor != 0) {\n            textPaint.color = cursor\n            var cursorHeight = (fontLineSpacingAndAscent - fontAscent).toFloat()\n            if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE) cursorHeight /= 4f\n            else if (cursorStyle == TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR) right -= ((right - left) * 3) / 4f\n            canvas.drawRect(left, y - cursorHeight, right, y, textPaint)\n        }\n\n        if ((effect and TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) == 0) {\n            if (dim) {\n                var red = 0xFF and (foreColor shr 16)\n                var green = 0xFF and (foreColor shr 8)\n                var blue = 0xFF and foreColor\n                // Dim color handling used by libvte which in turn took it from xterm\n                // (https://bug735245.bugzilla-attachments.gnome.org/attachment.cgi?id=284267):\n                red = red * 2 / 3\n                green = green * 2 / 3\n                blue = blue * 2 / 3\n                foreColor = -0x1000000 or (red shl 16) or (green shl 8) or blue\n            }\n\n            textPaint.isFakeBoldText = bold\n            textPaint.isUnderlineText = underline\n            textPaint.textSkewX = if (italic) -0.35f else 0f\n            textPaint.isStrikeThruText = strikeThrough\n            textPaint.color = foreColor\n\n            // The text alignment is the default Paint.Align.LEFT.\n            canvas.drawTextRun(\n                text, startCharIndex, runWidthChars, startCharIndex, runWidthChars,\n                left, y - fontLineSpacingAndAscent, false, textPaint,\n            )\n        }\n\n        if (savedMatrix) canvas.restore()\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/terminal/TerminalScreen.kt",
    "content": "package com.topjohnwu.magisk.ui.terminal\n\nimport android.graphics.Typeface\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.gestures.Orientation\nimport androidx.compose.foundation.gestures.rememberScrollableState\nimport androidx.compose.foundation.gestures.scrollable\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.drawBehind\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.drawscope.drawIntoCanvas\nimport androidx.compose.ui.graphics.nativeCanvas\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.unit.sp\nimport com.topjohnwu.magisk.terminal.TerminalEmulator\nimport kotlin.math.max\n\n@Composable\nfun TerminalScreen(\n    modifier: Modifier = Modifier,\n    onEmulatorCreated: (TerminalEmulator) -> Unit = {},\n) {\n    val density = LocalDensity.current\n    val renderer = remember {\n        val textSizePx = with(density) { 12.sp.toPx().toInt() }\n        TerminalRenderer(textSizePx, Typeface.MONOSPACE)\n    }\n\n    var emulator by remember { mutableStateOf<TerminalEmulator?>(null) }\n    var updateTick by remember { mutableIntStateOf(0) }\n    var topRow by remember { mutableIntStateOf(0) }\n    var scrolledToBottom by remember { mutableStateOf(true) }\n\n    BoxWithConstraints(modifier = modifier) {\n        val widthPx = constraints.maxWidth\n        val heightPx = constraints.maxHeight\n        val cols = max(4, (widthPx / renderer.fontWidth).toInt())\n        val rows = max(4, (heightPx - renderer.fontLineSpacingAndAscent) / renderer.fontLineSpacing)\n        val lineHeight = renderer.fontLineSpacing.toFloat()\n\n        LaunchedEffect(cols, rows) {\n            val emu = emulator\n            if (emu == null) {\n                val newEmu = TerminalEmulator(cols, rows, renderer.fontWidth.toInt(), renderer.fontLineSpacing, null)\n                newEmu.onScreenUpdate = {\n                    if (scrolledToBottom) topRow = 0\n                    updateTick++\n                }\n                emulator = newEmu\n                onEmulatorCreated(newEmu)\n            } else {\n                emu.resize(cols, rows, renderer.fontWidth.toInt(), renderer.fontLineSpacing)\n            }\n        }\n\n        Spacer(\n            modifier = Modifier\n                .fillMaxSize()\n                .background(Color.Black)\n                .scrollable(\n                    orientation = Orientation.Vertical,\n                    state = rememberScrollableState { delta ->\n                        val emu = emulator ?: return@rememberScrollableState 0f\n                        val minTop = -emu.screen.activeTranscriptRows\n                        val rowDelta = -(delta / lineHeight).toInt()\n                        if (rowDelta != 0) {\n                            val newTopRow = (topRow + rowDelta).coerceIn(minTop, 0)\n                            topRow = newTopRow\n                            scrolledToBottom = newTopRow >= 0\n                        }\n                        delta\n                    }\n                )\n                .drawBehind {\n                    @Suppress(\"UNUSED_EXPRESSION\")\n                    updateTick\n                    val emu = emulator ?: return@drawBehind\n                    drawIntoCanvas { canvas ->\n                        renderer.render(emu, canvas.nativeCanvas, topRow, -1, -1, -1, -1)\n                    }\n                }\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/theme/MagiskTheme.kt",
    "content": "package com.topjohnwu.magisk.ui.theme\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.setValue\nimport com.topjohnwu.magisk.core.Config\nimport top.yukonga.miuix.kmp.theme.ColorSchemeMode\nimport top.yukonga.miuix.kmp.theme.LocalContentColor\nimport top.yukonga.miuix.kmp.theme.MiuixTheme\nimport top.yukonga.miuix.kmp.theme.ThemeController\n\nobject ThemeState {\n    var colorMode by mutableIntStateOf(Config.colorMode)\n}\n\n@Composable\nfun MagiskTheme(\n    content: @Composable () -> Unit\n) {\n    val isDark = isSystemInDarkTheme()\n    val mode = ThemeState.colorMode\n    val controller = when (mode) {\n        1 -> ThemeController(ColorSchemeMode.Light)\n        2 -> ThemeController(ColorSchemeMode.Dark)\n        3 -> ThemeController(ColorSchemeMode.MonetSystem, isDark = isDark)\n        4 -> ThemeController(ColorSchemeMode.MonetLight)\n        5 -> ThemeController(ColorSchemeMode.MonetDark)\n        else -> ThemeController(ColorSchemeMode.System)\n    }\n    MiuixTheme(controller = controller) {\n        CompositionLocalProvider(\n            LocalContentColor provides MiuixTheme.colorScheme.onBackground,\n            content = content\n        )\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/theme/Theme.kt",
    "content": "package com.topjohnwu.magisk.ui.theme\n\nimport com.topjohnwu.magisk.R\nimport com.topjohnwu.magisk.core.Config\n\nenum class Theme(\n    val themeName: String,\n    val themeRes: Int\n) {\n\n    Piplup(\n        themeName = \"Piplup\",\n        themeRes = R.style.ThemeFoundationMD2_Piplup\n    ),\n    PiplupAmoled(\n        themeName = \"AMOLED\",\n        themeRes = R.style.ThemeFoundationMD2_Amoled\n    ),\n    Rayquaza(\n        themeName = \"Rayquaza\",\n        themeRes = R.style.ThemeFoundationMD2_Rayquaza\n    ),\n    Zapdos(\n        themeName = \"Zapdos\",\n        themeRes = R.style.ThemeFoundationMD2_Zapdos\n    ),\n    Charmeleon(\n        themeName = \"Charmeleon\",\n        themeRes = R.style.ThemeFoundationMD2_Charmeleon\n    ),\n    Mew(\n        themeName = \"Mew\",\n        themeRes = R.style.ThemeFoundationMD2_Mew\n    ),\n    Salamence(\n        themeName = \"Salamence\",\n        themeRes = R.style.ThemeFoundationMD2_Salamence\n    ),\n    Fraxure(\n        themeName = \"Fraxure (Legacy)\",\n        themeRes = R.style.ThemeFoundationMD2_Fraxure\n    );\n\n    val isSelected get() = Config.themeOrdinal == ordinal\n\n    companion object {\n        val selected get() = values().getOrNull(Config.themeOrdinal) ?: Piplup\n    }\n\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/ui/util/DrawablePainter.kt",
    "content": "package com.topjohnwu.magisk.ui.util\n\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.graphics.asImageBitmap\nimport androidx.compose.ui.graphics.painter.BitmapPainter\nimport androidx.compose.ui.graphics.painter.Painter\n\n@Composable\nfun rememberDrawablePainter(drawable: Drawable): Painter {\n    return remember(drawable) {\n        val w = drawable.intrinsicWidth.coerceAtLeast(1)\n        val h = drawable.intrinsicHeight.coerceAtLeast(1)\n        val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)\n        val canvas = android.graphics.Canvas(bitmap)\n        drawable.setBounds(0, 0, w, h)\n        drawable.draw(canvas)\n        BitmapPainter(bitmap.asImageBitmap())\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/java/com/topjohnwu/magisk/utils/AccessibilityUtils.kt",
    "content": "package com.topjohnwu.magisk.utils\n\nimport android.content.ContentResolver\nimport android.provider.Settings\n\nclass AccessibilityUtils {\n    companion object {\n        fun isAnimationEnabled(cr: ContentResolver): Boolean {\n            return !(Settings.Global.getFloat(cr, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0.0f\n                && Settings.Global.getFloat(cr, Settings.Global.TRANSITION_ANIMATION_SCALE, 1.0f) == 0.0f\n                && Settings.Global.getFloat(cr, Settings.Global.WINDOW_ANIMATION_SCALE, 1.0f) == 0.0f)\n        }\n    }\n}\n"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_card_background_color_selector.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorSurfaceVariant\" android:state_enabled=\"true\" />\n    <item android:alpha=\"0.68\" android:color=\"?colorSurfaceVariant\" />\n</selector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_error_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorError\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_menu_tint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabledVariant\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorSecondary\" android:state_checked=\"true\" />\n    <item android:color=\"?colorOnSurfaceVariant\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_on_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_primary_error_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorError\" android:state_selected=\"true\" />\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorPrimary\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_state_primary_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorPrimary\" android:state_selected=\"true\" />\n    <item android:color=\"?colorPrimary\" android:state_checked=\"true\" />\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnSurfaceVariant\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/color/color_text_transient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?colorDisabled\" android:state_enabled=\"false\" />\n    <item android:color=\"?colorOnSurface\" />\n</selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_bug_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 Z M 14 16 L 10 16 L 10 14 L 14 14 L 14 16 Z M 14 12 L 10 12 L 10 10 L 14 10 L 14 12 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15\"\n                android:valueTo=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_bug_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 20 8 L 18.595 8 L 17.19 8 C 16.74 7.2 16.12 6.5 15.37 6 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.5 5 12 5 C 11.5 5 11.05 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6 C 7.87 6.5 7.26 7.21 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.03 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.03 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 8.47 20.87 12.14 21.84 15 20.18 C 15.91 19.66 16.67 18.9 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.97 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.97 10.33 17.91 10 L 20 10 L 20 8 M 14.828 17.828 C 15.578 17.079 16 16.06 16 15 L 16 11 C 16 9.94 15.578 8.921 14.828 8.172 C 14.079 7.422 13.06 7 12 7 C 10.94 7 9.921 7.422 9.172 8.172 C 8.422 8.921 8 9.94 8 11 L 8 15 C 8 16.06 8.422 17.079 9.172 17.828 C 9.921 18.578 10.94 19 12 19 C 13.06 19 14.079 18.578 14.828 17.828 M 14 10 L 14 11 L 14 12 L 10 12 L 10 10 L 14 10 M 10 14 L 14 14 L 14 16 L 10 16 L 10 14 L 10 14\"\n                android:valueTo=\"M 20 8 L 20 8 L 17.19 8 C 16.74 7.22 16.12 6.55 15.37 6.04 L 17 4.41 L 15.59 3 L 13.42 5.17 C 12.96 5.06 12.49 5 12 5 C 11.51 5 11.04 5.06 10.59 5.17 L 8.41 3 L 7 4.41 L 8.62 6.04 C 7.88 6.55 7.26 7.22 6.81 8 L 4 8 L 4 10 L 6.09 10 C 6.04 10.33 6 10.66 6 11 L 6 12 L 4 12 L 4 14 L 6 14 L 6 15 C 6 15.34 6.04 15.67 6.09 16 L 4 16 L 4 18 L 6.81 18 C 7.85 19.79 9.78 21 12 21 C 14.22 21 16.15 19.79 17.19 18 L 20 18 L 20 16 L 17.91 16 C 17.96 15.67 18 15.34 18 15 L 18 14 L 20 14 L 20 12 L 18 12 L 18 11 C 18 10.66 17.96 10.33 17.91 10 L 20 10 L 20 8 M 14 16 C 14 15.43 14 14.859 14 14.289 L 14 14 C 13.869 14 13.739 14 13.608 14 C 12.405 14 11.203 14 10 14 C 10 14.509 10 15.017 10 15.526 C 10 15.684 10 15.842 10 16 L 10.33 16 C 10.392 16 10.454 16 10.515 16 C 11.677 16 12.838 16 14 16 C 14 16 14 16 14 16 M 14 10 L 14 12 L 14 12 L 10 12 L 10 10 L 14 10 M 12 15 L 12 15 L 12 15 L 12 15 L 12 15 L 12 15\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_circle_check_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:pathData=\"M 12 2 C 6.5 2 2 6.5 2 12 C 2 17.5 6.5 22 12 22 C 17.5 22 22 17.5 22 12 C 22 6.5 17.5 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13 L 10 17 L 18 9 L 16.59 7.58 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"500\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.744 3.12 17.24 4.927 19.052 C 6.74 20.87 9.244 22 12 22 C 13.911 22 15.701 21.457 17.224 20.517 C 18.628 19.651 19.804 18.448 20.638 17.024 C 21.503 15.545 22 13.828 22 12 C 22 10.2 21.518 8.507 20.677 7.044 C 19.755 5.441 18.402 4.114 16.779 3.224 C 15.357 2.444 13.728 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13\"\n                android:valueTo=\"M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_circle_check_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_1\"\n                android:fillColor=\"#000000\"\n                android:pathData=\"M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 7 13 L 17 13 L 17 11 L 7 11\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_1\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"500\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 13.755 22 15.48 21.538 17 20.66 C 18.52 19.783 19.783 18.52 20.66 17 C 21.538 15.48 22 13.755 22 12 C 22 10.245 21.538 8.52 20.66 7 C 19.783 5.48 18.52 4.217 17 3.34 C 15.48 2.462 13.755 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 7 13 L 7 13 L 17 13 L 17 11 L 17 11 L 7 11 L 7 11 L 7 11\"\n                android:valueTo=\"M 12 2 C 9.217 2 6.689 3.152 4.872 5.004 C 3.098 6.811 2 9.283 2 12 C 2 14.856 3.213 17.442 5.149 19.268 C 6.942 20.96 9.356 22 12 22 C 14.061 22 15.982 21.368 17.578 20.288 C 19.114 19.249 20.349 17.796 21.119 16.092 C 21.685 14.841 22 13.456 22 12 C 22 10.122 21.475 8.361 20.566 6.856 C 19.691 5.408 18.46 4.197 16.997 3.347 C 15.524 2.491 13.817 2 12 2 M 12 20 C 7.59 20 4 16.41 4 12 C 4 7.59 7.59 4 12 4 C 16.41 4 20 7.59 20 12 C 20 16.41 16.41 20 12 20 M 6 13 L 10 17 L 18 9 L 16.59 7.58 L 16.59 7.58 L 10 14.17 L 7.41 11.59 L 6 13\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_home_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_3\"\n                android:fillColor=\"#000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 L 9 21 L 4 21 L 4 9 L 12 3 Z\"\n                android:strokeWidth=\"1\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_3\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\"\n                android:valueTo=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_home_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path_3\"\n                android:fillColor=\"#000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:strokeWidth=\"1\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path_3\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\"\n                android:valueTo=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_module_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"outlined\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"outlined\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\"\n                android:valueTo=\"M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_module_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"outlined\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 22 13.5 C 22 15.26 20.7 16.72 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.28 3.3 8.74 2 10.5 2 C 12.26 2 13.72 3.3 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 20.7 10.28 22 11.74 22 13.5 M 17 15 L 18.5 15 C 18.898 15 19.279 14.842 19.561 14.561 C 19.842 14.279 20 13.898 20 13.5 C 20 13.102 19.842 12.721 19.561 12.439 C 19.279 12.158 18.898 12 18.5 12 L 17 12 L 17 7 L 12 7 L 12 5.5 C 12 5.102 11.842 4.721 11.561 4.439 C 11.279 4.158 10.898 4 10.5 4 C 10.102 4 9.721 4.158 9.439 4.439 C 9.158 4.721 9 5.102 9 5.5 L 9 7 L 4 7 L 4 9.12 C 5.76 9.8 7 11.5 7 13.5 C 7 15.5 5.75 17.2 4 17.88 L 4 20 L 6.12 20 C 6.8 18.25 8.5 17 10.5 17 C 12.5 17 14.2 18.25 14.88 20 L 17 20 L 17 15 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"outlined\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 22 13.5 C 22 14.087 21.856 14.64 21.6 15.126 C 21.344 15.612 20.978 16.03 20.533 16.347 C 20.089 16.664 19.567 16.88 19 16.96 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 13.2 22 L 13.2 21.7 C 13.2 20.984 12.915 20.297 12.409 19.791 C 11.903 19.285 11.216 19 10.5 19 C 9 19 7.8 20.21 7.8 21.7 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 2.3 16.2 C 3.79 16.2 5 15 5 13.5 C 5 12 3.79 10.8 2.3 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 7.04 5 C 7.12 4.433 7.336 3.911 7.653 3.467 C 7.97 3.022 8.388 2.656 8.874 2.4 C 9.36 2.144 9.913 2 10.5 2 C 11.087 2 11.64 2.144 12.126 2.4 C 12.612 2.656 13.03 3.022 13.347 3.467 C 13.664 3.911 13.88 4.433 13.96 5 L 17 5 C 17.53 5 18.039 5.211 18.414 5.586 C 18.789 5.961 19 6.47 19 7 L 19 10.04 C 19.425 10.1 19.825 10.236 20.186 10.434 C 20.547 10.633 20.869 10.893 21.137 11.2 C 21.406 11.508 21.622 11.863 21.77 12.251 C 21.919 12.639 22 13.06 22 13.5 M 17 12 L 18.5 12 C 18.898 12 19.279 12.158 19.561 12.439 C 19.842 12.721 20 13.102 20 13.5 C 20 13.898 19.842 14.279 19.561 14.561 C 19.279 14.842 18.898 15 18.5 15 L 17 15 L 17 15 L 17 20 L 14.88 20 C 14.2 18.25 12.5 17 10.5 17 C 8.5 17 6.8 18.25 6.12 20 L 4 20 L 4 17.88 C 5.75 17.2 7 15.5 7 13.5 C 7 11.5 5.76 9.8 4 9.12 L 4 7 L 9 7 L 9 5.5 C 9 5.102 9.158 4.721 9.439 4.439 C 9.721 4.158 10.102 4 10.5 4 C 10.898 4 11.279 4.158 11.561 4.439 C 11.842 4.721 12 5.102 12 5.5 L 12 7 L 17 7 L 17 12\"\n                android:valueTo=\"M 23 13.5 C 23 14.163 22.736 14.799 22.268 15.268 C 21.799 15.736 21.163 16 20.5 16 C 20 16 19.5 16 19 16 L 19 20 C 19 20.53 18.789 21.039 18.414 21.414 C 18.039 21.789 17.53 22 17 22 L 15.1 22 L 13.2 22 C 13.2 21.5 13.2 21 13.2 20.5 C 13.2 19 12 17.8 10.5 17.8 C 9 17.8 7.8 19 7.8 20.5 L 7.8 22 L 4 22 C 3.47 22 2.961 21.789 2.586 21.414 C 2.211 21.039 2 20.53 2 20 L 2 16.2 L 3.5 16.2 C 5 16.2 6.2 15 6.2 13.5 C 6.2 12 5 10.8 3.5 10.8 L 2 10.8 L 2 7 C 2 6.47 2.211 5.961 2.586 5.586 C 2.961 5.211 3.47 5 4 5 L 8 5 C 8 4.5 8 4 8 3.5 C 8 2.837 8.264 2.201 8.732 1.732 C 9.201 1.264 9.837 1 10.5 1 C 11.163 1 11.799 1.264 12.268 1.732 C 12.736 2.201 13 2.837 13 3.5 C 13 4 13 4.5 13 5 L 17 5 C 17.55 5 18.05 5.223 18.413 5.584 C 18.775 5.945 19 6.445 19 7 L 19 11 C 19.5 11 20 11 20.5 11 C 20.5 11 20.5 11 20.5 11 C 21.163 11 21.799 11.264 22.268 11.732 C 22.736 12.201 23 12.837 23 13.5 M 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_settings_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 M 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12\"\n                android:valueTo=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_settings_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.547 11.333 4.523 11.667 4.5 12 C 4.523 12.323 4.547 12.647 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.68 16.04 18.34 16.56 17.95 L 19.05 18.95 C 19.27 19.04 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.79 15.05 21.73 14.78 21.54 14.63 L 19.43 13 L 19.465 12.499 C 19.477 12.333 19.488 12.166 19.5 12 C 19.477 11.667 19.453 11.333 19.43 11 L 21.54 9.37 C 21.73 9.22 21.79 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8 C 12.53 8 13.05 8.105 13.531 8.305 C 14.011 8.504 14.454 8.797 14.828 9.172 C 15.578 9.921 16 10.94 16 12 C 16 12.53 15.895 13.05 15.695 13.531 C 15.496 14.011 15.203 14.454 14.828 14.828 C 14.079 15.578 13.06 16 12 16 C 10.94 16 9.921 15.578 9.172 14.828 C 8.422 14.079 8 13.06 8 12 C 8 10.94 8.422 9.921 9.172 9.172 C 9.921 8.422 10.94 8 12 8 M 12 10 C 11.912 10 11.824 10.006 11.737 10.017 C 11.651 10.029 11.565 10.046 11.481 10.069 C 11.397 10.091 11.315 10.119 11.235 10.152 C 11.155 10.186 11.077 10.224 11.001 10.267 C 10.926 10.311 10.854 10.359 10.784 10.412 C 10.715 10.466 10.649 10.524 10.586 10.586 C 10.524 10.649 10.466 10.715 10.412 10.784 C 10.359 10.854 10.311 10.926 10.267 11.001 C 10.224 11.077 10.186 11.155 10.152 11.235 C 10.119 11.315 10.091 11.397 10.069 11.481 C 10.046 11.565 10.029 11.651 10.017 11.737 C 10.006 11.824 10 11.912 10 12 C 10 12.088 10.006 12.176 10.017 12.263 C 10.029 12.349 10.046 12.435 10.069 12.519 C 10.091 12.603 10.119 12.685 10.152 12.765 C 10.186 12.845 10.224 12.923 10.267 12.999 C 10.311 13.074 10.359 13.146 10.412 13.216 C 10.466 13.285 10.524 13.351 10.586 13.414 C 10.649 13.476 10.715 13.534 10.784 13.588 C 10.854 13.641 10.926 13.689 11.001 13.733 C 11.077 13.776 11.155 13.814 11.235 13.848 C 11.315 13.881 11.397 13.909 11.481 13.931 C 11.565 13.954 11.651 13.971 11.737 13.983 C 11.824 13.994 11.912 14 12 14 C 12.53 14 13.039 13.789 13.414 13.414 C 13.468 13.36 13.518 13.304 13.565 13.245 C 13.611 13.187 13.655 13.126 13.694 13.062 C 13.734 12.999 13.77 12.934 13.802 12.867 C 13.834 12.8 13.863 12.731 13.887 12.661 C 13.912 12.591 13.933 12.519 13.949 12.447 C 13.966 12.374 13.979 12.3 13.987 12.226 C 13.996 12.151 14 12.076 14 12 C 14 11.912 13.994 11.824 13.983 11.737 C 13.971 11.651 13.954 11.565 13.931 11.481 C 13.909 11.397 13.881 11.315 13.848 11.235 C 13.814 11.155 13.776 11.077 13.733 11.001 C 13.689 10.926 13.641 10.854 13.588 10.784 C 13.534 10.715 13.476 10.649 13.414 10.586 C 13.039 10.211 12.53 10 12 10 M 11.25 4 L 11.25 4 L 12.75 4 L 13.12 6.62 C 14.32 6.86 15.38 7.5 16.15 8.39 L 18.56 7.35 L 19.31 8.65 L 17.2 10.2 C 17.6 11.37 17.6 12.64 17.2 13.81 L 19.32 15.36 L 18.57 16.66 L 16.14 15.62 C 15.37 16.5 14.32 17.14 13.13 17.39 L 12.76 20 L 11.24 20 L 10.87 17.38 C 9.68 17.14 8.63 16.5 7.86 15.62 L 5.43 16.66 L 4.68 15.36 L 6.8 13.8 C 6.4 12.64 6.4 11.37 6.8 10.2 L 4.69 8.65 L 5.44 7.35 L 7.85 8.39 C 8.62 7.5 9.68 6.86 10.88 6.61 L 11.25 4\"\n                android:valueTo=\"M 14.87 5.07 L 14.5 2.42 C 14.46 2.18 14.25 2 14 2 L 10 2 C 9.75 2 9.54 2.18 9.5 2.42 L 9.13 5.07 C 8.5 5.32 7.96 5.66 7.44 6.05 L 4.95 5.05 C 4.73 4.96 4.46 5.05 4.34 5.27 L 2.34 8.73 C 2.21 8.95 2.27 9.22 2.46 9.37 L 4.57 11 C 4.53 11.34 4.5 11.67 4.5 12 C 4.5 12.33 4.53 12.65 4.57 12.97 L 2.46 14.63 C 2.27 14.78 2.21 15.05 2.34 15.27 L 4.34 18.73 C 4.46 18.95 4.73 19.03 4.95 18.95 L 7.44 17.94 C 7.96 18.34 8.5 18.68 9.13 18.93 L 9.5 21.58 C 9.54 21.82 9.75 22 10 22 L 14 22 C 14.25 22 14.46 21.82 14.5 21.58 L 14.87 18.93 C 15.5 18.67 16.04 18.34 16.56 17.94 L 19.05 18.95 C 19.27 19.03 19.54 18.95 19.66 18.73 L 21.66 15.27 C 21.78 15.05 21.73 14.78 21.54 14.63 L 19.43 12.97 L 19.43 12.97 C 19.47 12.65 19.5 12.33 19.5 12 C 19.5 11.67 19.47 11.34 19.43 11 L 21.54 9.37 C 21.73 9.22 21.78 8.95 21.66 8.73 L 19.66 5.27 C 19.54 5.05 19.27 4.96 19.05 5.05 L 16.56 6.05 C 16.04 5.66 15.5 5.32 14.87 5.07 M 12 8.5 C 12.614 8.5 13.218 8.662 13.75 8.969 C 14.282 9.276 14.724 9.718 15.031 10.25 C 15.338 10.782 15.5 11.386 15.5 12 C 15.5 12.614 15.338 13.218 15.031 13.75 C 14.724 14.282 14.282 14.724 13.75 15.031 C 13.218 15.338 12.614 15.5 12 15.5 C 11.072 15.5 10.181 15.131 9.525 14.475 C 8.869 13.819 8.5 12.928 8.5 12 C 8.5 11.072 8.869 10.181 9.525 9.525 C 10.181 8.869 11.072 8.5 12 8.5 M 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 C 11.982 12 11.982 12 11.982 12 M 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12 L 12 12 L 12 12 C 12 12 12 12 12 12 L 12 12\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_superuser_from_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 12 1 L 3 5 L 3 11 C 3 16.55 6.84 21.74 12 23 C 17.16 21.74 21 16.55 21 11 L 21 5 L 12 1 Z\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18\"\n                android:valueTo=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/avd_superuser_to_filled.xml",
    "content": "<animated-vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\">\n    <aapt:attr name=\"android:drawable\">\n        <vector\n            android:name=\"vector\"\n            android:width=\"24dp\"\n            android:height=\"24dp\"\n            android:viewportWidth=\"24\"\n            android:viewportHeight=\"24\">\n            <path\n                android:name=\"path\"\n                android:fillColor=\"#000000\"\n                android:fillType=\"evenOdd\"\n                android:pathData=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\" />\n        </vector>\n    </aapt:attr>\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:duration=\"300\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"\n                android:propertyName=\"pathData\"\n                android:valueFrom=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 7.5 3 L 12 1 L 21 5 L 21 11 M 12 21 L 12 21 C 8.25 20 5 15.54 5 11.22 L 5 6.3 L 12 3.18 L 19 6.3 L 19 11.22 C 19 15.54 15.75 20 12 21\"\n                android:valueTo=\"M 21 11 C 21 16.55 17.16 21.74 12 23 C 6.84 21.74 3 16.55 3 11 L 3 5 L 12 1 L 12 1 L 21 5 L 21 11 M 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 L 12 10.18 C 12 10.18 12 10.18 12 10.18\"\n                android:valueType=\"pathType\" />\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_bug_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_bug_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_bug_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_bug_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_bug_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_bug_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_bug_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20,8H17.19C16.74,7.2 16.12,6.5 15.37,6L17,4.41L15.59,3L13.42,5.17C12.96,5.06 12.5,5 12,5C11.5,5 11.05,5.06 10.59,5.17L8.41,3L7,4.41L8.62,6C7.87,6.5 7.26,7.21 6.81,8H4V10H6.09C6.03,10.33 6,10.66 6,11V12H4V14H6V15C6,15.34 6.03,15.67 6.09,16H4V18H6.81C8.47,20.87 12.14,21.84 15,20.18C15.91,19.66 16.67,18.9 17.19,18H20V16H17.91C17.97,15.67 18,15.34 18,15V14H20V12H18V11C18,10.66 17.97,10.33 17.91,10H20V8M16,15A4,4 0 0,1 12,19A4,4 0 0,1 8,15V11A4,4 0 0,1 12,7A4,4 0 0,1 16,11V15M14,10V12H10V10H14M10,14H14V16H10V14Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_check_circle_checked_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M12 20C7.59 20 4 16.41 4 12S7.59 4 12 4 20 7.59 20 12 16.41 20 12 20M16.59 7.58L10 14.17L7.41 11.59L6 13L10 17L18 9L16.59 7.58Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_check_circle_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"NewApi\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_check_circle_checked_md2\"\n        android:state_selected=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_check_circle_unchecked_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_circle_check_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_circle_check_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_check_circle_unchecked_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_check_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z\"\n        tools:fillColor=\"#000\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_delete_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"?colorOnSurface\"\n      android:pathData=\"M18,4h-2.5l-0.7,-0.7C14.6,3.2 14.4,3 14.1,3H9.9C9.6,3 9.4,3.2 9.2,3.3L8.5,4H6C5.4,4 5,4.5 5,5s0.4,1 1,1h12c0.5,0 1,-0.4 1,-1S18.5,4 18,4z\" />\n  <path\n      android:fillColor=\"?colorOnSurface\"\n      android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V9c0,-1.1 -0.9,-2 -2,-2H8C6.9,7 6,7.9 6,9V19z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_download_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M13,5V11H14.17L12,13.17L9.83,11H11V5H13M15,3H9V9H5L12,16L19,9H15V3M19,18H5V20H19V18Z\"\n        tools:fillColor=\"#000\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_home_filled_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M 9 14 L 9 21 L 4 21 L 4 9 L 12 3 L 12 3 L 20 9 L 20 21 L 15 21 L 15 14 L 9 14 M 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4 L 12 13.4\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_home_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_home_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_home_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_home_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_home_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_home_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:fillType=\"evenOdd\"\n        android:pathData=\"M 9 13 L 9 19 L 6 19 L 6 10 L 12 5.5 L 15 7.75 L 18 10 L 18 19 L 15 19 L 15 13 L 9 13 M 4 21 L 4 9 L 12 3 L 20 9 L 20 21 L 4 21 L 4 21\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_install.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,18L7,13H10V9H14V13H17L12,18M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2M14,6V4H10V6H14M4,8V19H20V8H4Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_manager.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M7,3v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1L9,4h10v16L9,20v-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L21,3c0,-1.1 -0.9,-2 -2,-2L9,1c-1.1,0 -2,0.9 -2,2zM9.5,15.5c0.29,-0.12 0.55,-0.29 0.8,-0.48l-0.02,0.03 1.01,0.39c0.23,0.09 0.49,0 0.61,-0.22l0.84,-1.46c0.12,-0.21 0.07,-0.49 -0.12,-0.64l-0.85,-0.68 -0.02,0.03c0.02,-0.16 0.05,-0.32 0.05,-0.48s-0.03,-0.32 -0.05,-0.48l0.02,0.03 0.85,-0.68c0.19,-0.15 0.24,-0.43 0.12,-0.64l-0.84,-1.46c-0.12,-0.21 -0.38,-0.31 -0.61,-0.22l-1.01,0.39 0.02,0.03c-0.25,-0.17 -0.51,-0.34 -0.8,-0.46l-0.17,-1.08C9.3,7.18 9.09,7 8.84,7L7.16,7c-0.25,0 -0.46,0.18 -0.49,0.42L6.5,8.5c-0.29,0.12 -0.55,0.29 -0.8,0.48l0.02,-0.03 -1.02,-0.39c-0.23,-0.09 -0.49,0 -0.61,0.22l-0.84,1.46c-0.12,0.21 -0.07,0.49 0.12,0.64l0.85,0.68 0.02,-0.03c-0.02,0.15 -0.05,0.31 -0.05,0.47s0.03,0.32 0.05,0.48l-0.02,-0.03 -0.85,0.68c-0.19,0.15 -0.24,0.43 -0.12,0.64l0.84,1.46c0.12,0.21 0.38,0.31 0.61,0.22l1.01,-0.39 -0.01,-0.04c0.25,0.19 0.51,0.36 0.8,0.48l0.17,1.07c0.03,0.25 0.24,0.43 0.49,0.43h1.68c0.25,0 0.46,-0.18 0.49,-0.42l0.17,-1.08zM6,12c0,-1.1 0.9,-2 2,-2s2,0.9 2,2 -0.9,2 -2,2 -2,-0.9 -2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_module_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M20.5,11H19V7C19,5.89 18.1,5 17,5H13V3.5A2.5,2.5 0 0,0 10.5,1A2.5,2.5 0 0,0 8,3.5V5H4A2,2 0 0,0 2,7V10.8H3.5C5,10.8 6.2,12 6.2,13.5C6.2,15 5,16.2 3.5,16.2H2V20A2,2 0 0,0 4,22H7.8V20.5C7.8,19 9,17.8 10.5,17.8C12,17.8 13.2,19 13.2,20.5V22H17A2,2 0 0,0 19,20V16H20.5A2.5,2.5 0 0,0 23,13.5A2.5,2.5 0 0,0 20.5,11Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_module_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_module_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_module_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_module_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_module_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_module_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M22,13.5C22,15.26 20.7,16.72 19,16.96V20A2,2 0 0,1 17,22H13.2V21.7A2.7,2.7 0 0,0 10.5,19C9,19 7.8,20.21 7.8,21.7V22H4A2,2 0 0,1 2,20V16.2H2.3C3.79,16.2 5,15 5,13.5C5,12 3.79,10.8 2.3,10.8H2V7A2,2 0 0,1 4,5H7.04C7.28,3.3 8.74,2 10.5,2C12.26,2 13.72,3.3 13.96,5H17A2,2 0 0,1 19,7V10.04C20.7,10.28 22,11.74 22,13.5M17,15H18.5A1.5,1.5 0 0,0 20,13.5A1.5,1.5 0 0,0 18.5,12H17V7H12V5.5A1.5,1.5 0 0,0 10.5,4A1.5,1.5 0 0,0 9,5.5V7H4V9.12C5.76,9.8 7,11.5 7,13.5C7,15.5 5.75,17.2 4,17.88V20H6.12C6.8,18.25 8.5,17 10.5,17C12.5,17 14.2,18.25 14.88,20H17V15Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_module_storage_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M2,10.96C1.5,10.68 1.35,10.07 1.63,9.59L3.13,7C3.24,6.8 3.41,6.66 3.6,6.58L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.66,6.72 20.82,6.88 20.91,7.08L22.36,9.6C22.64,10.08 22.47,10.69 22,10.96L21,11.54V16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V10.96C2.7,11.13 2.32,11.14 2,10.96M12,4.15V4.15L12,10.85V10.85L17.96,7.5L12,4.15M5,15.91L11,19.29V12.58L5,9.21V15.91M19,15.91V12.69L14,15.59C13.67,15.77 13.3,15.76 13,15.6V19.29L19,15.91M13.85,13.36L20.13,9.73L19.55,8.72L13.27,12.35L13.85,13.36Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_notifications_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M16,17H7V10.5C7,8 9,6 11.5,6C14,6 16,8 16,10.5M18,16V10.5C18,7.43 15.86,4.86 13,4.18V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V4.18C7.13,4.86 5,7.43 5,10.5V16L3,18V19H20V18M11.5,22A2,2 0 0,0 13.5,20H9.5A2,2 0 0,0 11.5,22Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_restart.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,4C14.1,4 16.1,4.8 17.6,6.3C20.7,9.4 20.7,14.5 17.6,17.6C15.8,19.5 13.3,20.2 10.9,19.9L11.4,17.9C13.1,18.1 14.9,17.5 16.2,16.2C18.5,13.9 18.5,10.1 16.2,7.7C15.1,6.6 13.5,6 12,6V10.6L7,5.6L12,0.6V4M6.3,17.6C3.7,15 3.3,11 5.1,7.9L6.6,9.4C5.5,11.6 5.9,14.4 7.8,16.2C8.3,16.7 8.9,17.1 9.6,17.4L9,19.4C8,19 7.1,18.4 6.3,17.6Z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_save_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_search_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.25,4.25c0.41,0.41 1.08,0.41 1.49,0 0.41,-0.41 0.41,-1.08 0,-1.49L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_settings_filled_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_settings_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_settings_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_settings_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_settings_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_settings_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_settings_outlined_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_superuser_filled_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:width=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z\" />\n</vector>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_superuser_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/checked\"\n        android:drawable=\"@drawable/ic_superuser_filled_md2\"\n        android:state_checked=\"true\" />\n\n    <item\n        android:id=\"@+id/unchecked\"\n        android:drawable=\"@drawable/ic_superuser_outlined_md2\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_superuser_from_filled\"\n        android:fromId=\"@+id/checked\"\n        android:toId=\"@+id/unchecked\" />\n\n    <transition\n        android:drawable=\"@drawable/avd_superuser_to_filled\"\n        android:fromId=\"@+id/unchecked\"\n        android:toId=\"@id/checked\" />\n\n</animated-selector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_superuser_outlined_md2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:width=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M21,11C21,16.55 17.16,21.74 12,23C6.84,21.74 3,16.55 3,11V5L12,1L21,5V11M12,21C15.75,20 19,15.54 19,11.22V6.3L12,3.18L5,6.3V11.22C5,15.54 8.25,20 12,21Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/drawable/ic_update_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"?colorOnSurface\"\n        android:pathData=\"M17,1H7A2,2 0 0,0 5,3V21A2,2 0 0,0 7,23H17A2,2 0 0,0 19,21V3A2,2 0 0,0 17,1M17,19H7V5H17V19M16,13H13V8H11V13H8L12,17L16,13Z\" />\n</vector>"
  },
  {
    "path": "app/apk-ng/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--region Deprecated-->\n    <attr name=\"cardStyle\" format=\"reference\" />\n    <attr name=\"colorAccentFallback\" format=\"reference\" />\n    <attr name=\"imageColorTint\" format=\"reference\" />\n    <attr name=\"colorControl\" format=\"reference\" />\n    <!--endregion-->\n\n    <!--region Colors-->\n\n    <!--Static-->\n    <attr name=\"colorDisabled\" format=\"color\" />\n    <attr name=\"colorDisabledVariant\" format=\"color\" />\n    <attr name=\"colorSurfaceVariant\" format=\"color\" />\n    <attr name=\"colorOnPrimaryVariant\" format=\"color\" />\n    <attr name=\"colorOnSurfaceVariant\" format=\"color\" />\n    <attr name=\"colorSurfaceSurfaceVariant\" format=\"color\" />\n\n    <!--endregion-->\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <dimen name=\"margin_generic\">16dp</dimen>\n\n    <dimen name=\"l_125\">2dp</dimen>\n    <dimen name=\"l_25\">4dp</dimen>\n    <dimen name=\"l_50\">8dp</dimen>\n    <dimen name=\"l_75\">12dp</dimen>\n    <dimen name=\"l1\">16dp</dimen>\n    <dimen name=\"l2\">32dp</dimen>\n    <dimen name=\"l3\">48dp</dimen>\n\n    <dimen name=\"r1\">8dp</dimen>\n\n    <dimen name=\"internal_action_bar_size\">56dp</dimen>\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/styles_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Foundation\" parent=\"Theme.Foundation.Light\" />\n\n    <!--region Do not remove-->\n    <style name=\"Empty\" />\n\n    <style name=\"WidgetFoundation\" parent=\"android:Widget\" />\n\n    <!--endregion-->\n\n    <style name=\"Foundation.Default\">\n        <item name=\"android:includeFontPadding\">false</item>\n    </style>\n\n    <style name=\"Foundation.Floating\" parent=\"Empty\">\n        <item name=\"android:windowIsFloating\">true</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/styles_md2_appearance.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--region Display-->\n    <style name=\"AppearanceFoundation.Large\" parent=\"TextAppearance.AppCompat.Large\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Large.Secondary\">\n        <item name=\"android:textColor\">?colorSecondary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Title-->\n    <style name=\"AppearanceFoundation.Title\" parent=\"TextAppearance.AppCompat.Title\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Title.OnPrimary\">\n        <item name=\"android:textColor\">?attr/colorOnPrimary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Body-->\n    <style name=\"AppearanceFoundation.Body\" parent=\"TextAppearance.AppCompat.Body1\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Caption-->\n    <style name=\"AppearanceFoundation.Caption\" parent=\"TextAppearance.AppCompat.Caption\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.Variant\">\n        <item name=\"android:textColor\">?attr/colorOnSurfaceVariant</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.Primary\">\n        <item name=\"android:textColor\">?attr/colorPrimary</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Caption.OnPrimary\">\n        <item name=\"android:textColor\">?attr/colorOnPrimary</item>\n    </style>\n\n    <!--endregion-->\n\n    <!--region Tiny-->\n    <style name=\"AppearanceFoundation.Tiny\" parent=\"TextAppearance.AppCompat.Caption\">\n        <item name=\"android:textColor\">?attr/colorOnSurface</item>\n        <item name=\"android:textSize\">11sp</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Tiny.Bold\">\n        <item name=\"android:textStyle\">bold</item>\n    </style>\n\n    <style name=\"AppearanceFoundation.Tiny.Variant\">\n        <item name=\"android:textColor\">?attr/colorOnSurfaceVariant</item>\n    </style>\n\n    <!--endregion-->\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/styles_md2_impl.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"WidgetFoundation.Card\" parent=\"Widget.MaterialComponents.CardView\">\n        <item name=\"android:focusable\">auto</item>\n        <item name=\"cardBackgroundColor\">?colorSurfaceVariant</item>\n        <item name=\"cardCornerRadius\">@dimen/l_50</item>\n        <item name=\"cardElevation\">0dp</item>\n        <item name=\"cardPreventCornerOverlap\">false</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Card.Primary\">\n        <item name=\"cardBackgroundColor\">?colorPrimary</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Card.Elevated\">\n        <item name=\"cardBackgroundColor\">?colorSurfaceSurfaceVariant</item>\n        <item name=\"cardCornerRadius\">@dimen/l_50</item>\n        <item name=\"cardElevation\">@dimen/l_125</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Button\" parent=\"Widget.MaterialComponents.Button\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Outlined\" parent=\"Widget.MaterialComponents.Button.OutlinedButton\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n        <item name=\"strokeColor\">?colorPrimary</item>\n        <item name=\"android:textColor\">?colorPrimary</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Outlined.Error\">\n        <item name=\"strokeColor\">@color/color_error_transient</item>\n        <item name=\"rippleColor\">@color/color_error_transient</item>\n        <item name=\"android:textColor\">@color/color_error_transient</item>\n        <item name=\"iconTint\">@color/color_error_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Text\" parent=\"Widget.MaterialComponents.Button.TextButton\">\n        <item name=\"android:textStyle\">bold</item>\n        <item name=\"iconGravity\">textStart</item>\n        <item name=\"iconPadding\">@dimen/l_50</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Button.Text.OnPrimary\">\n        <item name=\"rippleColor\">?colorOnPrimary</item>\n        <item name=\"android:textColor\">?colorOnPrimary</item>\n        <item name=\"iconTint\">?colorOnPrimary</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Image\">\n        <item name=\"android:layout_width\">32dp</item>\n        <item name=\"android:layout_height\">32dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Image.Big\">\n        <item name=\"android:layout_width\">48dp</item>\n        <item name=\"android:layout_height\">48dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Image.Small\">\n        <item name=\"android:layout_width\">24dp</item>\n        <item name=\"android:layout_height\">24dp</item>\n    </style>\n\n\n    <style name=\"WidgetFoundation.Icon\" parent=\"WidgetFoundation.Image.Big\">\n        <item name=\"android:padding\">@dimen/l_75</item>\n        <item name=\"android:background\">?selectableItemBackgroundBorderless</item>\n        <item name=\"tint\">@color/color_text_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Icon.Primary\">\n        <item name=\"tint\">@color/color_primary_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Icon.OnPrimary\">\n        <item name=\"tint\">@color/color_on_primary_transient</item>\n    </style>\n\n    <style name=\"WidgetFoundation.Checkbox\" parent=\"Widget.AppCompat.CompoundButton.CheckBox\">\n        <item name=\"android:paddingStart\">@dimen/l1</item>\n        <item name=\"android:paddingEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"WidgetFoundation.RadioButton\" parent=\"Widget.AppCompat.CompoundButton.RadioButton\">\n        <item name=\"android:paddingStart\">@dimen/l1</item>\n        <item name=\"android:paddingEnd\">@dimen/l1</item>\n    </style>\n\n    <style name=\"WidgetFoundation.ProgressBar\" parent=\"Widget.AppCompat.ProgressBar.Horizontal\">\n        <item name=\"android:indeterminate\">false</item>\n        <item name=\"android:layout_height\">4dp</item>\n    </style>\n\n    <style name=\"WidgetFoundation.ProgressBar.Indeterminate\" parent=\"Widget.AppCompat.ProgressBar.Horizontal\">\n        <item name=\"android:indeterminate\">true</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:layout_width\">100dp</item>\n        <item name=\"android:indeterminateTint\">?colorPrimary</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Base.V23.Theme.Foundation.Light\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"android:windowBackground\">?colorSurface</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"dialogTheme\">@style/ThemeOverlay.Foundation.Dialog</item>\n        <item name=\"android:statusBarColor\">#40bdbdbd</item>\n        <item name=\"android:navigationBarColor\">#38000000</item>\n        <item name=\"android:windowLightStatusBar\">true</item>\n    </style>\n\n    <style name=\"Base.V23.Theme.Foundation\" parent=\"Theme.MaterialComponents.NoActionBar\">\n        <item name=\"android:windowBackground\">?colorSurface</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n        <item name=\"dialogTheme\">@style/ThemeOverlay.Foundation.Dialog</item>\n        <item name=\"android:statusBarColor\">#60000000</item>\n        <item name=\"android:navigationBarColor\">#60000000</item>\n    </style>\n\n    <style name=\"Theme.Foundation.Light\" parent=\"Base.V23.Theme.Foundation.Light\" />\n\n    <style name=\"Theme.Foundation\" parent=\"Base.V23.Theme.Foundation\" />\n\n    <style name=\"Base.V23.ThemeOverlay.Foundation.Dialog\" parent=\"ThemeOverlay.MaterialComponents.Dialog\">\n        <item name=\"android:windowMinWidthMajor\">@dimen/abc_dialog_min_width_major</item>\n        <item name=\"android:windowMinWidthMinor\">@dimen/abc_dialog_min_width_minor</item>\n    </style>\n\n    <style name=\"ThemeOverlay.Foundation.Dialog\" parent=\"Base.V23.ThemeOverlay.Foundation.Dialog\" />\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/themes_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--\n    # Theme guide\n\n    This should provide all the information you need to create new !color! themes.\n\n    ## Inheritance\n\n    You might want to inherit default values from \"base\" theme. You can do so implicitly by just\n    writing `<style name=\"ThemeFoundationMD2.MySuperAwesomeTheme\" parent=\"ThemeFoundationMD2.Piplup\">`.\n    With this approach you can only change values you (don't) like - such as `colorPrimary` or\n    `colorSecondary`.\n\n    ## Day / Night ? How do I define both in one theme?\n\n    Define Theme here for \"Day\" theme and in `values-night` directory define theme with same name\n    and parent if applicable. The framework will automatically switch between day/night themes based\n    on user's requested configuration. (Always light, Always dark, Follow system)\n\n    You might choose to define only \"Day\" theme with Dark colors to have dark theme regardless.\n    That's super lazy approach and is discouraged but the framework permits it.\n\n    ## What to theme\n\n    Generally I'd suggest theming only `colorPrimary`, `colorSecondary` and their variants. Make\n    sure that text is readable if displayed on primary or secondary color!\n\n    You can check that very easily by visiting https://www.colorhexa.com/. After you put in your\n    color hex, you can scroll down to \"Preview\" where it will compute whether dark/bright text\n    should be displayed on top of it. Then you must edit respective `colorOn...`.\n\n    Also check \"Color Blindness Simulator\". Primary and secondary colors must not match or be\n    similar in type. Text on color must have enough contrast so it can be read by everyone!\n\n    !! Themes that will not satisfy these requirements will be promptly deleted without further\n    !! notice. In repeated attempts to push such themes you will be automatically blacklisted.\n    -->\n\n    <style name=\"ThemeFoundationMD2\" parent=\"Foundation.Default\" />\n\n    <!--1st party themes-->\n\n    <style name=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#4EAFF5</item>\n        <item name=\"colorPrimaryVariant\">#804EAFF5</item>\n        <item name=\"colorSecondary\">#3E78AF</item>\n        <item name=\"colorSecondaryVariant\">#803E78AF</item>\n        <item name=\"colorSurface\">#F9F9F9</item>\n        <item name=\"colorSurfaceVariant\">#EEEEEE</item>\n        <item name=\"colorSurfaceSurfaceVariant\">?colorSurface</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n        <item name=\"colorOnSecondary\">#F9F9F9</item>\n        <item name=\"colorOnBackground\">?colorOnSurface</item>\n        <item name=\"colorError\">#CC0047</item>\n        <item name=\"colorOnError\">#F9F9F9</item>\n        <item name=\"colorOnSurface\">#444444</item>\n        <item name=\"colorOnSurfaceVariant\">#BF444444</item>\n        <item name=\"colorDisabled\">#808080</item>\n        <item name=\"colorDisabledVariant\">#66808080</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Amoled\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSurface\">#FFF</item>\n        <item name=\"colorOnPrimary\">#FFF</item>\n        <item name=\"colorOnSecondary\">#FFF</item>\n        <item name=\"colorOnBackground\">#FFF</item>\n        <item name=\"colorOnError\">#FFF</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Rayquaza\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#68A17F</item>\n        <item name=\"colorPrimaryVariant\">#8068A17F</item>\n        <item name=\"colorSecondary\">#2F6D43</item>\n        <item name=\"colorSecondaryVariant\">#802F6D43</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos.Base\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSecondary\">#B29667</item>\n        <item name=\"colorSecondaryVariant\">#80B29667</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos\" parent=\"ThemeFoundationMD2.Zapdos.Base\">\n        <item name=\"colorPrimary\">#F2B90D</item>\n        <item name=\"colorPrimaryVariant\">#80F2B90D</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Charmeleon\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#DB7366</item>\n        <item name=\"colorPrimaryVariant\">#80DB7366</item>\n        <item name=\"colorSecondary\">#B65247</item>\n        <item name=\"colorSecondaryVariant\">#80B65247</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew.Base\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSecondary\">#B5889B</item>\n        <item name=\"colorSecondaryVariant\">#80B5889B</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew\" parent=\"ThemeFoundationMD2.Mew.Base\">\n        <item name=\"colorPrimary\">#B3566C</item>\n        <item name=\"colorPrimaryVariant\">#80B3566C</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Salamence\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#70B2C6</item>\n        <item name=\"colorPrimaryVariant\">#8070B2C6</item>\n        <item name=\"colorSecondary\">#C06A75</item>\n        <item name=\"colorSecondaryVariant\">#80C06A75</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Fraxure\" parent=\"ThemeFoundationMD2.Amoled\">\n        <item name=\"colorPrimary\">#009688</item>\n        <item name=\"colorPrimaryVariant\">#8000796B</item>\n        <item name=\"colorSecondary\">?colorError</item>\n        <item name=\"colorSecondaryVariant\">#806D1111</item>\n    </style>\n\n    <!--3rd party themes-->\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values/themes_override.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"PrivateResource\">\n\n    <!-- Removes minimal size for MaterialComponents CheckBox and RadioButton,\n         so that they will not have extra space in menus.\n    -->\n    <style name=\"Widget.MaterialComponents.CompoundButton.CheckBox\" parent=\"Widget.AppCompat.CompoundButton.CheckBox\" tools:override=\"true\">\n        <item name=\"enforceMaterialTheme\">true</item>\n        <item name=\"useMaterialThemeColors\">true</item>\n    </style>\n\n    <style name=\"Widget.MaterialComponents.CompoundButton.RadioButton\" parent=\"Widget.AppCompat.CompoundButton.RadioButton\" tools:override=\"true\">\n        <item name=\"enforceMaterialTheme\">true</item>\n        <item name=\"useMaterialThemeColors\">true</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values-night/styles_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Foundation\" parent=\"Theme.Foundation\" />\n\n</resources>\n"
  },
  {
    "path": "app/apk-ng/src/main/res/values-night/themes_md2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!--1st party themes-->\n\n    <style name=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorPrimary\">#4EAFF5</item>\n        <item name=\"colorPrimaryVariant\">#804EAFF5</item>\n        <item name=\"colorSecondary\">#3E78AF</item>\n        <item name=\"colorSecondaryVariant\">#803E78AF</item>\n        <item name=\"colorSurface\">#0D0D0D</item>\n        <item name=\"colorSurfaceVariant\">#171717</item>\n        <item name=\"colorSurfaceSurfaceVariant\">#292929</item>\n        <item name=\"colorOnPrimary\">#F9F9F9</item>\n        <item name=\"colorOnPrimaryVariant\">#D9E6E6E6</item>\n        <item name=\"colorOnSecondary\">#F9F9F9</item>\n        <item name=\"colorOnBackground\">?colorOnSurface</item>\n        <item name=\"colorError\">#EF8282</item>\n        <item name=\"colorOnError\">#0D0D0D</item>\n        <item name=\"colorOnSurface\">#D8D8D8</item>\n        <item name=\"colorOnSurfaceVariant\">#CCBABABA</item>\n        <item name=\"colorDisabled\">#808080</item>\n        <item name=\"colorDisabledVariant\">#66808080</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Amoled\" parent=\"ThemeFoundationMD2.Piplup\">\n        <item name=\"colorSurface\">#000</item>\n        <item name=\"colorOnPrimary\">#FFF</item>\n        <item name=\"colorOnSecondary\">#FFF</item>\n        <item name=\"colorOnBackground\">#000</item>\n        <item name=\"colorOnError\">#FFF</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Zapdos\" parent=\"ThemeFoundationMD2.Zapdos.Base\">\n        <item name=\"colorPrimary\">#FBD179</item>\n        <item name=\"colorPrimaryVariant\">#80FBD179</item>\n    </style>\n\n    <style name=\"ThemeFoundationMD2.Mew\" parent=\"ThemeFoundationMD2.Mew.Base\">\n        <item name=\"colorPrimary\">#D9ADB7</item>\n        <item name=\"colorPrimaryVariant\">#80E9BBC5</item>\n        <item name=\"colorOnPrimary\">#000000</item>\n        <item name=\"colorOnPrimaryVariant\">#D9222222</item>\n    </style>\n\n    <!--3rd party themes-->\n\n</resources>"
  },
  {
    "path": "app/apk-ng/src/main/res/values-v27/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Base.V27.Theme.Foundation.Light\" parent=\"Base.V23.Theme.Foundation.Light\">\n        <item name=\"android:navigationBarColor\">#e0fafafa</item>\n        <item name=\"android:navigationBarDividerColor\">#1f000000</item>\n        <item name=\"android:windowLightNavigationBar\">true</item>\n    </style>\n\n    <style name=\"Base.V27.Theme.Foundation\" parent=\"Base.V23.Theme.Foundation\">\n        <item name=\"android:navigationBarDividerColor\">@android:color/transparent</item>\n    </style>\n\n    <style name=\"Theme.Foundation.Light\" parent=\"Base.V27.Theme.Foundation.Light\" />\n\n    <style name=\"Theme.Foundation\" parent=\"Base.V27.Theme.Foundation\" />\n\n</resources>\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "plugins {\n    id(\"MagiskPlugin\")\n}\n\ntasks.register(\"clean\", Delete::class) {\n    delete(rootProject.layout.buildDirectory)\n\n    subprojects.forEach {\n        dependsOn(\":${it.name}:clean\")\n    }\n}\n"
  },
  {
    "path": "app/buildSrc/build.gradle.kts",
    "content": "plugins {\n    `kotlin-dsl`\n}\n\nrepositories {\n    google()\n    mavenCentral()\n}\n\ngradlePlugin {\n    plugins {\n        register(\"MagiskPlugin\") {\n            id = \"MagiskPlugin\"\n            implementationClass = \"MagiskPlugin\"\n        }\n    }\n}\n\ndependencies {\n    implementation(kotlin(\"gradle-plugin\", libs.versions.kotlin.get()))\n    implementation(\"org.jetbrains.kotlin:compose-compiler-gradle-plugin:${libs.versions.kotlin.get()}\")\n    implementation(\"org.jetbrains.kotlin:kotlin-serialization:${libs.versions.kotlin.get()}\")\n    implementation(libs.android.gradle.plugin)\n}\n"
  },
  {
    "path": "app/buildSrc/settings.gradle.kts",
    "content": "dependencyResolutionManagement {\n    versionCatalogs {\n        create(\"libs\") {\n            from(files(\"../gradle/libs.versions.toml\"))\n        }\n    }\n}\n"
  },
  {
    "path": "app/buildSrc/src/main/java/AddCommentTask.kt",
    "content": "import com.android.build.api.artifact.ArtifactTransformationRequest\nimport com.android.build.api.dsl.ApkSigningConfig\nimport com.android.builder.internal.packaging.IncrementalPackager\nimport com.android.tools.build.apkzlib.sign.SigningExtension\nimport com.android.tools.build.apkzlib.sign.SigningOptions\nimport com.android.tools.build.apkzlib.zfile.ZFiles\nimport com.android.tools.build.apkzlib.zip.ZFileOptions\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.file.DirectoryProperty\nimport org.gradle.api.provider.Property\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.InputFiles\nimport org.gradle.api.tasks.Internal\nimport org.gradle.api.tasks.OutputDirectory\nimport org.gradle.api.tasks.TaskAction\nimport java.io.File\nimport java.security.KeyStore\nimport java.security.cert.X509Certificate\nimport java.util.jar.JarFile\n\nabstract class AddCommentTask: DefaultTask() {\n    @get:Input\n    abstract val comment: Property<String>\n\n    @get:Input\n    abstract val signingConfig: Property<ApkSigningConfig>\n\n    @get:InputFiles\n    abstract val apkFolder: DirectoryProperty\n\n    @get:OutputDirectory\n    abstract val outFolder: DirectoryProperty\n\n    @get:Internal\n    abstract val transformationRequest: Property<ArtifactTransformationRequest<AddCommentTask>>\n\n    @TaskAction\n    fun taskAction() = transformationRequest.get().submit(this) { artifact ->\n        val inFile = File(artifact.outputFile)\n        val outFile = outFolder.file(inFile.name).get().asFile\n\n        val privateKey = signingConfig.get().getPrivateKey()\n        val signingOptions = SigningOptions.builder()\n            .setMinSdkVersion(0)\n            .setV1SigningEnabled(true)\n            .setV2SigningEnabled(true)\n            .setKey(privateKey.privateKey)\n            .setCertificates(privateKey.certificate as X509Certificate)\n            .setValidation(SigningOptions.Validation.ASSUME_INVALID)\n            .build()\n        val options = ZFileOptions().apply {\n            noTimestamps = true\n            autoSortFiles = true\n        }\n        outFile.parentFile?.mkdirs()\n        inFile.copyTo(outFile, overwrite = true)\n        ZFiles.apk(outFile, options).use {\n            SigningExtension(signingOptions).register(it)\n            it.eocdComment = comment.get().toByteArray()\n            it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete()\n            it.get(IncrementalPackager.VERSION_CONTROL_INFO_ENTRY_PATH)?.delete()\n            it.get(JarFile.MANIFEST_NAME)?.delete()\n        }\n\n        outFile\n    }\n\n    private fun ApkSigningConfig.getPrivateKey(): KeyStore.PrivateKeyEntry {\n        val keyStore = KeyStore.getInstance(storeType ?: KeyStore.getDefaultType())\n        storeFile!!.inputStream().use {\n            keyStore.load(it, storePassword!!.toCharArray())\n        }\n        val keyPwdArray = keyPassword!!.toCharArray()\n        val entry = keyStore.getEntry(keyAlias!!, KeyStore.PasswordProtection(keyPwdArray))\n        return entry as KeyStore.PrivateKeyEntry\n    }\n}"
  },
  {
    "path": "app/buildSrc/src/main/java/DesugarClassVisitorFactory.kt",
    "content": "import com.android.build.api.instrumentation.AsmClassVisitorFactory\nimport com.android.build.api.instrumentation.ClassContext\nimport com.android.build.api.instrumentation.ClassData\nimport com.android.build.api.instrumentation.InstrumentationParameters\nimport org.objectweb.asm.ClassVisitor\nimport org.objectweb.asm.MethodVisitor\nimport org.objectweb.asm.Opcodes\nimport org.objectweb.asm.Opcodes.ASM9\n\nprivate const val DESUGAR_CLASS_NAME = \"com.topjohnwu.magisk.core.utils.Desugar\"\nprivate const val ZIP_ENTRY_CLASS_NAME = \"java.util.zip.ZipEntry\"\nprivate const val ZIP_OUT_STREAM_CLASS_NAME = \"org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream\"\nprivate const val ZIP_UTIL_CLASS_NAME = \"org/apache/commons/compress/archivers/zip/ZipUtil\"\nprivate const val ZIP_ENTRY_GET_TIME_DESC = \"()Ljava/nio/file/attribute/FileTime;\"\nprivate const val DESUGAR_GET_TIME_DESC =\n    \"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;\"\n\nprivate fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)\n\nabstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {\n    override fun createClassVisitor(\n        classContext: ClassContext,\n        nextClassVisitor: ClassVisitor\n    ): ClassVisitor {\n        return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {\n            ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))\n        } else {\n            ZipEntryPatcher(classContext, nextClassVisitor)\n        }\n    }\n\n    override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME\n\n    // Patch ALL references to ZipEntry#getXXXTime\n    class ZipEntryPatcher(\n        private val classContext: ClassContext,\n        cv: ClassVisitor\n    ) : ClassVisitor(ASM9, cv) {\n        override fun visitMethod(\n            access: Int,\n            name: String?,\n            descriptor: String?,\n            signature: String?,\n            exceptions: Array<out String>?\n        ) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))\n\n        inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {\n            override fun visitMethodInsn(\n                opcode: Int,\n                owner: String,\n                name: String,\n                descriptor: String,\n                isInterface: Boolean\n            ) {\n                if (!process(owner, name, descriptor)) {\n                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)\n                }\n            }\n\n            private fun process(owner: String, name: String, descriptor: String): Boolean {\n                val classData = classContext.loadClassData(owner.replace(\"/\", \".\")) ?: return false\n                if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))\n                    return false\n                if (descriptor != ZIP_ENTRY_GET_TIME_DESC)\n                    return false\n                return when (name) {\n                    \"getLastModifiedTime\", \"getLastAccessTime\", \"getCreationTime\" -> {\n                        mv.visitMethodInsn(\n                            Opcodes.INVOKESTATIC,\n                            DESUGAR_CLASS_NAME.replace('.', '/'),\n                            name,\n                            DESUGAR_GET_TIME_DESC,\n                            false\n                        )\n                        true\n                    }\n                    else -> false\n                }\n            }\n        }\n    }\n\n    // Patch ZipArchiveOutputStream#copyFromZipInputStream\n    class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {\n        override fun visitMethod(\n            access: Int,\n            name: String,\n            descriptor: String,\n            signature: String?,\n            exceptions: Array<out String?>?\n        ): MethodVisitor? {\n            return if (name == \"copyFromZipInputStream\") {\n                MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))\n            } else {\n                super.visitMethod(access, name, descriptor, signature, exceptions)\n            }\n        }\n\n        class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {\n            override fun visitMethodInsn(\n                opcode: Int,\n                owner: String,\n                name: String,\n                descriptor: String?,\n                isInterface: Boolean\n            ) {\n                if (owner == ZIP_UTIL_CLASS_NAME && name == \"checkRequestedFeatures\") {\n                    // Redirect\n                    mv.visitMethodInsn(\n                        Opcodes.INVOKESTATIC,\n                        DESUGAR_CLASS_NAME.replace('.', '/'),\n                        name,\n                        descriptor,\n                        false\n                    )\n                } else {\n                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/buildSrc/src/main/java/Plugin.kt",
    "content": "\nimport org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport org.gradle.kotlin.dsl.provideDelegate\nimport java.io.File\nimport java.util.Properties\nimport java.util.Random\n\n// Set non-zero value here to fix the random seed for reproducible builds\n// CI builds are always reproducible\nval RAND_SEED = if (System.getenv(\"CI\") != null) 42 else 0\nlateinit var RANDOM: Random\n\nprivate val props = Properties()\n\nobject Config {\n    operator fun get(key: String): String? {\n        val v = props[key] as? String ?: return null\n        return v.ifBlank { null }\n    }\n\n    fun contains(key: String) = get(key) != null\n\n    val version: String get() = get(\"version\")!!\n    val versionCode: Int get() = get(\"magisk.versionCode\")!!.toInt()\n    val stubVersion: String get() = get(\"magisk.stubVersion\")!!\n    val abiList: List<String> get() = get(\"abiList\")!!.split(\",\")\n}\n\nfun Project.rootFile(path: String): File {\n    val file = File(path)\n    return if (file.isAbsolute) file\n    else File(rootProject.file(\"..\"), path)\n}\n\nclass MagiskPlugin : Plugin<Project> {\n    override fun apply(project: Project) = project.applyPlugin()\n\n    private fun Project.applyPlugin() {\n        initRandom(rootProject.file(\"dict.txt\"))\n        props.clear()\n\n        // Get gradle properties relevant to Magisk\n        props.putAll(properties.filter { (key, _) -> key.startsWith(\"magisk.\") })\n\n        // Load config.prop\n        val configPath: String? by this\n        val configFile = rootFile(configPath ?: \"config.prop\")\n        if (configFile.exists()) {\n            configFile.inputStream().use {\n                val config = Properties()\n                config.load(it)\n                props.putAll(config)\n            }\n        }\n\n        // Load flags.prop, generated by build.py\n        val flagsProp = rootProject.layout.buildDirectory.file(\"flags.prop\").get().asFile\n        if (flagsProp.exists()) {\n            flagsProp.inputStream().use {\n                val flags = Properties()\n                flags.load(it)\n                props.putAll(flags)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/buildSrc/src/main/java/Setup.kt",
    "content": "import com.android.build.api.artifact.SingleArtifact\nimport com.android.build.api.dsl.ApplicationExtension\nimport com.android.build.api.dsl.CommonExtension\nimport com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS\nimport com.android.build.api.instrumentation.InstrumentationScope\nimport com.android.build.api.variant.AndroidComponentsExtension\nimport com.android.build.api.variant.ApplicationAndroidComponentsExtension\nimport org.apache.tools.ant.filters.FixCrLfFilter\nimport org.gradle.api.Action\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.Project\nimport org.gradle.api.file.DirectoryProperty\nimport org.gradle.api.tasks.OutputDirectory\nimport org.gradle.api.tasks.StopExecutionException\nimport org.gradle.api.tasks.Sync\nimport org.gradle.kotlin.dsl.assign\nimport org.gradle.kotlin.dsl.exclude\nimport org.gradle.kotlin.dsl.filter\nimport org.gradle.kotlin.dsl.get\nimport org.gradle.kotlin.dsl.register\nimport org.gradle.kotlin.dsl.withType\nimport org.jetbrains.kotlin.gradle.dsl.JvmTarget\nimport org.jetbrains.kotlin.gradle.tasks.KotlinCompile\nimport java.io.File\nimport java.net.URI\nimport java.security.MessageDigest\nimport java.util.HexFormat\n\nprivate fun Project.android(configure: Action<CommonExtension>) =\n    extensions.configure(\"android\", configure)\n\nprivate fun Project.androidApp(configure: Action<ApplicationExtension>) =\n    extensions.configure(\"android\", configure)\n\ninternal val Project.androidApp: ApplicationExtension\n    get() = extensions[\"android\"] as ApplicationExtension\n\nprivate fun Project.androidComponents(configure: Action<AndroidComponentsExtension<*, *, *>>) =\n    extensions.configure(AndroidComponentsExtension::class.java, configure)\n\nprivate val Project.androidComponents: AndroidComponentsExtension<*, *, *>\n    get() = extensions[\"androidComponents\"] as AndroidComponentsExtension<*, *, *>\n\ninternal fun Project.androidAppComponents(configure: Action<ApplicationAndroidComponentsExtension>) =\n    extensions.configure(ApplicationAndroidComponentsExtension::class.java, configure)\n\nfun Project.setupCommon() {\n    android {\n        compileSdk {\n            version = release(36) {\n                minorApiLevel = 1\n            }\n        }\n        buildToolsVersion = \"36.1.0\"\n        ndkPath = \"${androidComponents.sdkComponents.sdkDirectory.get().asFile}/ndk/magisk\"\n        ndkVersion = \"29.0.14206865\"\n\n        defaultConfig.apply {\n            minSdk = 23\n        }\n\n        compileOptions.apply {\n            sourceCompatibility = JavaVersion.VERSION_21\n            targetCompatibility = JavaVersion.VERSION_21\n        }\n\n        packaging.apply {\n            resources {\n                excludes += arrayOf(\n                    \"/META-INF/*\",\n                    \"/META-INF/androidx/**\",\n                    \"/META-INF/versions/**\",\n                    \"/org/bouncycastle/**\",\n                    \"/org/apache/commons/**\",\n                    \"/kotlin/**\",\n                    \"/kotlinx/**\",\n                    \"/okhttp3/**\",\n                    \"/*.txt\",\n                    \"/*.bin\",\n                    \"/*.json\",\n                )\n            }\n        }\n    }\n\n    configurations.all {\n        exclude(\"org.jetbrains.kotlin\", \"kotlin-stdlib-jdk7\")\n        exclude(\"org.jetbrains.kotlin\", \"kotlin-stdlib-jdk8\")\n    }\n\n    tasks.withType<KotlinCompile> {\n        compilerOptions {\n            jvmTarget = JvmTarget.JVM_21\n        }\n    }\n}\n\nprivate fun Project.downloadFile(url: String, checksum: String): File {\n    val file = layout.buildDirectory.file(checksum).get().asFile\n    if (file.exists()) {\n        val md = MessageDigest.getInstance(\"SHA-256\")\n        file.inputStream().use { md.update(it.readAllBytes()) }\n        val hash = HexFormat.of().formatHex(md.digest())\n        if (hash != checksum) {\n            file.delete()\n        }\n    }\n    if (!file.exists()) {\n        file.parentFile.mkdirs()\n        URI(url).toURL().openStream().use { dl ->\n            file.outputStream().use {\n                dl.copyTo(it)\n            }\n        }\n    }\n    return file\n}\n\nconst val BUSYBOX_DOWNLOAD_URL =\n    \"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip\"\nconst val BUSYBOX_ZIP_CHECKSUM =\n    \"b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951\"\n\nprivate abstract class SyncWithDir : Sync() {\n    @get:OutputDirectory\n    abstract val outputFolder: DirectoryProperty\n}\n\nfun Project.setupCoreLib() {\n    setupCommon()\n\n    val abiList = Config.abiList\n\n    androidComponents {\n        onVariants { variant ->\n            val variantName = variant.name\n            val variantCapped = variantName.replaceFirstChar { it.uppercase() }\n\n            val syncLibs = tasks.register(\"sync${variantCapped}JniLibs\", SyncWithDir::class) {\n                outputFolder.set(layout.buildDirectory.dir(\"$variantName/jniLibs\"))\n                into(outputFolder)\n\n                for (abi in abiList) {\n                    into(abi) {\n                        from(rootFile(\"native/out/$abi\")) {\n                            include(\"magiskboot\", \"magiskinit\", \"magiskpolicy\", \"magisk\", \"libinit-ld.so\")\n                            rename { if (it.endsWith(\".so\")) it else \"lib$it.so\" }\n                        }\n                    }\n                }\n                from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))\n                include(abiList.map { \"$it/libbusybox.so\" })\n                onlyIf {\n                    if (inputs.sourceFiles.files.size != abiList.size * 6)\n                        throw StopExecutionException(\"Please build binaries first! (./build.py binary)\")\n                    true\n                }\n            }\n\n            variant.sources.jniLibs?.let {\n                it.addGeneratedSourceDirectory(syncLibs, SyncWithDir::outputFolder)\n            }\n\n            val syncResources = tasks.register(\"sync${variantCapped}Resources\", SyncWithDir::class) {\n                outputFolder.set(layout.buildDirectory.dir(\"$variantName/resources\"))\n                into(outputFolder)\n\n                into(\"META-INF/com/google/android\") {\n                    from(rootFile(\"scripts/update_binary.sh\")) {\n                        rename { \"update-binary\" }\n                    }\n                    from(rootFile(\"scripts/flash_script.sh\")) {\n                        rename { \"updater-script\" }\n                    }\n                }\n            }\n\n            variant.sources.resources?.let {\n                it.addGeneratedSourceDirectory(syncResources, SyncWithDir::outputFolder)\n            }\n\n            val stubTask = tasks.getByPath(\":stub:comment$variantCapped\")\n            val syncAssets = tasks.register(\"sync${variantCapped}Assets\", SyncWithDir::class) {\n                outputFolder.set(layout.buildDirectory.dir(\"$variantName/assets\"))\n                into(outputFolder)\n\n                inputs.property(\"version\", Config.version)\n                inputs.property(\"versionCode\", Config.versionCode)\n                from(rootFile(\"scripts\")) {\n                    include(\"util_functions.sh\", \"boot_patch.sh\", \"addon.d.sh\",\n                        \"app_functions.sh\", \"uninstaller.sh\", \"module_installer.sh\")\n                }\n                from(rootFile(\"tools/bootctl\"))\n                into(\"chromeos\") {\n                    from(rootFile(\"tools/futility\"))\n                    from(rootFile(\"tools/keys\")) {\n                        include(\"kernel_data_key.vbprivk\", \"kernel.keyblock\")\n                    }\n                }\n                from(stubTask) {\n                    include { it.name.endsWith(\".apk\") }\n                    rename { \"stub.apk\" }\n                }\n                filesMatching(\"**/util_functions.sh\") {\n                    filter {\n                        it.replace(\n                            \"#MAGISK_VERSION_STUB\",\n                            \"MAGISK_VER='${Config.version}'\\nMAGISK_VER_CODE=${Config.versionCode}\"\n                        )\n                    }\n                    filter<FixCrLfFilter>(\"eol\" to FixCrLfFilter.CrLf.newInstance(\"lf\"))\n                }\n            }\n\n            variant.sources.assets?.let {\n                it.addGeneratedSourceDirectory(syncAssets, SyncWithDir::outputFolder)\n            }\n        }\n    }\n}\n\nfun Project.setupAppCommon() {\n    setupCommon()\n\n    androidApp {\n        signingConfigs {\n            Config[\"keyStore\"]?.also {\n                create(\"config\") {\n                    storeFile = rootFile(it)\n                    storePassword = Config[\"keyStorePass\"]\n                    keyAlias = Config[\"keyAlias\"]\n                    keyPassword = Config[\"keyPass\"]\n                }\n            }\n        }\n\n        defaultConfig {\n            targetSdk = 36\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\")\n            )\n        }\n\n        buildTypes {\n            val config = signingConfigs.findByName(\"config\") ?: signingConfigs[\"debug\"]\n            debug {\n                signingConfig = config\n            }\n            release {\n                signingConfig = config\n            }\n        }\n\n        lint {\n            disable += \"MissingTranslation\"\n            checkReleaseBuilds = false\n        }\n\n        dependenciesInfo {\n            includeInApk = false\n        }\n\n        packaging {\n            jniLibs {\n                useLegacyPackaging = true\n            }\n        }\n    }\n\n    androidAppComponents {\n        onVariants { variant ->\n            val commentTask = tasks.register(\n                \"comment${variant.name.replaceFirstChar { it.uppercase() }}\",\n                AddCommentTask::class.java\n            )\n            val transformationRequest = variant.artifacts.use(commentTask)\n                .wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder)\n                .toTransformMany(SingleArtifact.APK)\n            val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig\n            commentTask.configure {\n                this.transformationRequest = transformationRequest\n                this.signingConfig = signingConfig\n                this.comment = \"version=${Config.version}\\n\" +\n                        \"versionCode=${Config.versionCode}\\n\" +\n                        \"stubVersion=${Config.stubVersion}\\n\"\n                this.outFolder.set(layout.buildDirectory.dir(\"outputs/apk/${variant.name}\"))\n            }\n\n        }\n    }\n}\n\nfun Project.setupMainApk() {\n    setupAppCommon()\n\n    androidApp {\n        namespace = \"com.topjohnwu.magisk\"\n\n        defaultConfig {\n            applicationId = \"com.topjohnwu.magisk\"\n            vectorDrawables.useSupportLibrary = true\n            versionName = Config.version\n            versionCode = Config.versionCode\n            ndk {\n                abiFilters += listOf(\"armeabi-v7a\", \"arm64-v8a\", \"x86\", \"x86_64\", \"riscv64\")\n                debugSymbolLevel = \"FULL\"\n            }\n        }\n    }\n\n    androidComponents {\n        onVariants { variant ->\n            variant.instrumentation.apply {\n                setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)\n                transformClassesWith(\n                    DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}\n            }\n        }\n    }\n}\n\nconst val LSPOSED_DOWNLOAD_URL =\n    \"https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip\"\nconst val LSPOSED_CHECKSUM =\n    \"0ebc6bcb465d1c4b44b7220ab5f0252e6b4eb7fe43da74650476d2798bb29622\"\n\nconst val SHAMIKO_DOWNLOAD_URL =\n    \"https://github.com/LSPosed/LSPosed.github.io/releases/download/shamiko-383/Shamiko-v1.2.1-383-release.zip\"\nconst val SHAMIKO_CHECKSUM =\n    \"93754a038c2d8f0e985bad45c7303b96f70a93d8335060e50146f028d3a9b13f\"\n\nfun Project.setupTestApk() {\n    setupAppCommon()\n\n    androidComponents {\n        onVariants { variant ->\n            val variantName = variant.name\n            val variantCapped = variantName.replaceFirstChar { it.uppercase() }\n\n            val dlTask = tasks.register(\"download${variantCapped}Lsposed\", SyncWithDir::class) {\n                outputFolder.set(layout.buildDirectory.dir(\"$variantName/lsposed\"))\n                into(outputFolder)\n\n                from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {\n                    rename { \"lsposed.zip\" }\n                }\n                from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {\n                    rename { \"shamiko.zip\" }\n                }\n            }\n\n            variant.sources.assets?.let {\n                it.addGeneratedSourceDirectory(dlTask, SyncWithDir::outputFolder)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/buildSrc/src/main/java/Stub.kt",
    "content": "import com.android.build.api.artifact.SingleArtifact\nimport org.gradle.api.DefaultTask\nimport org.gradle.api.Project\nimport org.gradle.api.file.DirectoryProperty\nimport org.gradle.api.file.RegularFileProperty\nimport org.gradle.api.provider.Property\nimport org.gradle.api.tasks.CacheableTask\nimport org.gradle.api.tasks.Delete\nimport org.gradle.api.tasks.Input\nimport org.gradle.api.tasks.InputFile\nimport org.gradle.api.tasks.OutputDirectory\nimport org.gradle.api.tasks.OutputFile\nimport org.gradle.api.tasks.PathSensitive\nimport org.gradle.api.tasks.PathSensitivity\nimport org.gradle.api.tasks.TaskAction\nimport org.gradle.kotlin.dsl.assign\nimport org.gradle.kotlin.dsl.named\nimport org.gradle.kotlin.dsl.register\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.File\nimport java.io.PrintStream\nimport java.security.SecureRandom\nimport java.util.Random\nimport java.util.zip.Deflater\nimport java.util.zip.DeflaterOutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipOutputStream\nimport javax.crypto.Cipher\nimport javax.crypto.CipherOutputStream\nimport javax.crypto.spec.IvParameterSpec\nimport javax.crypto.spec.SecretKeySpec\nimport kotlin.random.asKotlinRandom\n\nprivate val kRANDOM get() = RANDOM.asKotlinRandom()\n\nprivate val c1 = mutableListOf<String>()\nprivate val c2 = mutableListOf<String>()\nprivate val c3 = mutableListOf<String>()\n\nfun initRandom(dict: File) {\n    RANDOM = if (RAND_SEED != 0) Random(RAND_SEED.toLong()) else SecureRandom()\n    c1.clear()\n    c2.clear()\n    c3.clear()\n    for (a in chain('a'..'z', 'A'..'Z')) {\n        if (a != 'a' && a != 'A') {\n            c1.add(\"$a\")\n        }\n        for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) {\n            c2.add(\"$a$b\")\n            for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) {\n                c3.add(\"$a$b$c\")\n            }\n        }\n    }\n    c1.shuffle(RANDOM)\n    c2.shuffle(RANDOM)\n    c3.shuffle(RANDOM)\n    PrintStream(dict).use {\n        for (c in chain(c1, c2, c3)) {\n            it.println(c)\n        }\n    }\n}\n\nprivate fun <T> chain(vararg iters: Iterable<T>) = sequence {\n    iters.forEach { it.forEach { v -> yield(v) } }\n}\n\nprivate fun PrintStream.byteField(name: String, bytes: ByteArray) {\n    println(\"public static byte[] $name() {\")\n    print(\"byte[] buf = {\")\n    print(bytes.joinToString(\",\") { it.toString() })\n    println(\"};\")\n    println(\"return buf;\")\n    println(\"}\")\n}\n\n@CacheableTask\nprivate abstract class ManifestUpdater: DefaultTask() {\n    @get:Input\n    abstract val applicationId: Property<String>\n\n    @get:Input\n    abstract val factoryClass: Property<String>\n\n    @get:Input\n    abstract val appClass: Property<String>\n\n    @get:InputFile\n    @get:PathSensitive(PathSensitivity.RELATIVE)\n    abstract val mergedManifest: RegularFileProperty\n\n    @get:OutputFile\n    abstract val outputManifest: RegularFileProperty\n\n    @TaskAction\n    fun taskAction() {\n        fun String.ind(level: Int) = replaceIndentByMargin(\"    \".repeat(level))\n\n        val cmpList = mutableListOf<String>()\n\n        cmpList.add(\"\"\"\n            |<provider\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_0\"\n            |    android:authorities=\"${'$'}{applicationId}.provider\"\n            |    android:directBootAware=\"true\"\n            |    android:exported=\"false\"\n            |    android:grantUriPermissions=\"true\" />\"\"\".ind(2)\n        )\n\n        cmpList.add(\"\"\"\n            |<receiver\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_1\"\n            |    android:exported=\"false\">\n            |    <intent-filter>\n            |        <action android:name=\"android.intent.action.LOCALE_CHANGED\" />\n            |        <action android:name=\"android.intent.action.UID_REMOVED\" />\n            |        <action android:name=\"android.intent.action.MY_PACKAGE_REPLACED\" />\n            |    </intent-filter>\n            |    <intent-filter>\n            |        <action android:name=\"android.intent.action.PACKAGE_REPLACED\" />\n            |        <action android:name=\"android.intent.action.PACKAGE_FULLY_REMOVED\" />\n            |\n            |        <data android:scheme=\"package\" />\n            |    </intent-filter>\n            |</receiver>\"\"\".ind(2)\n        )\n\n        cmpList.add(\"\"\"\n            |<activity\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_2\"\n            |    android:exported=\"true\">\n            |    <intent-filter>\n            |        <action android:name=\"android.intent.action.MAIN\" />\n            |        <category android:name=\"android.intent.category.LAUNCHER\" />\n            |    </intent-filter>\n            |</activity>\"\"\".ind(2)\n        )\n\n        cmpList.add(\"\"\"\n            |<activity\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_3\"\n            |    android:directBootAware=\"true\"\n            |    android:exported=\"false\"\n            |    android:taskAffinity=\"\">\n            |    <intent-filter>\n            |        <action android:name=\"android.intent.action.VIEW\"/>\n            |        <category android:name=\"android.intent.category.DEFAULT\"/>\n            |    </intent-filter>\n            |</activity>\"\"\".ind(2)\n        )\n\n        cmpList.add(\"\"\"\n            |<service\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_4\"\n            |    android:exported=\"false\"\n            |    android:foregroundServiceType=\"dataSync\" />\"\"\".ind(2)\n        )\n\n        cmpList.add(\"\"\"\n            |<service\n            |    android:name=\"x.COMPONENT_PLACEHOLDER_5\"\n            |    android:exported=\"false\"\n            |    android:permission=\"android.permission.BIND_JOB_SERVICE\" />\"\"\".ind(2)\n        )\n\n        // Shuffle the order of the components\n        cmpList.shuffle(RANDOM)\n        val components = cmpList.joinToString(\"\\n\\n\")\n            .replace(\"\\${applicationId}\", applicationId.get())\n        val manifest = mergedManifest.asFile.get().readText().replace(Regex(\".*\\\\<application\"), \"\"\"\n            |<application\n            |    android:appComponentFactory=\"${factoryClass.get()}\"\n            |    android:name=\"${appClass.get()}\"\"\"\".ind(1)\n        ).replace(Regex(\".*\\\\<\\\\/application\"), \"$components\\n    </application\")\n        outputManifest.get().asFile.writeText(manifest)\n    }\n}\n\nprivate fun genStubClasses(outDir: File): Pair<String, String> {\n    val classNameGenerator = sequence {\n        fun notJavaKeyword(name: String) = when (name) {\n            \"do\", \"if\", \"for\", \"int\", \"new\", \"try\" -> false\n            else -> true\n        }\n\n        fun List<String>.process() = asSequence()\n            .filter(::notJavaKeyword)\n            // Distinct by lower case to support case insensitive file systems\n            .distinctBy { it.lowercase() }\n\n        val names = mutableListOf<String>()\n        names.addAll(c1)\n        names.addAll(c2.process().take(30))\n        names.addAll(c3.process().take(30))\n        names.shuffle(RANDOM)\n\n        while (true) {\n            val cls = StringBuilder()\n            cls.append(names.random(kRANDOM))\n            cls.append('.')\n            cls.append(names.random(kRANDOM))\n            // Old Android does not support capitalized package names\n            // Check Android 7.0.0 PackageParser#buildClassName\n            yield(cls.toString().replaceFirstChar { it.lowercase() })\n        }\n    }.distinct().iterator()\n\n    fun genClass(type: String, outDir: File): String {\n        val clzName = classNameGenerator.next()\n        val (pkg, name) = clzName.split('.')\n        val pkgDir = File(outDir, pkg)\n        pkgDir.mkdirs()\n        PrintStream(File(pkgDir, \"$name.java\")).use {\n            it.println(\"package $pkg;\")\n            it.println(\"public class $name extends com.topjohnwu.magisk.$type {}\")\n        }\n        return clzName\n    }\n\n    val factory = genClass(\"DelegateComponentFactory\", outDir)\n    val app = genClass(\"StubApplication\", outDir)\n    return Pair(factory, app)\n}\n\nprivate fun genEncryptedResources(res: ByteArray, outDir: File) {\n    val mainPkgDir = File(outDir, \"com/topjohnwu/magisk\")\n    mainPkgDir.mkdirs()\n\n    // Generate iv and key\n    val iv = ByteArray(16)\n    val key = ByteArray(32)\n    RANDOM.nextBytes(iv)\n    RANDOM.nextBytes(key)\n\n    val cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\")\n    cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, \"AES\"), IvParameterSpec(iv))\n    val bos = ByteArrayOutputStream()\n\n    ByteArrayInputStream(res).use {\n        CipherOutputStream(bos, cipher).use { os ->\n            it.transferTo(os)\n        }\n    }\n\n    PrintStream(File(mainPkgDir, \"Bytes.java\")).use {\n        it.println(\"package com.topjohnwu.magisk;\")\n        it.println(\"public final class Bytes {\")\n\n        it.byteField(\"key\", key)\n        it.byteField(\"iv\", iv)\n        it.byteField(\"res\", bos.toByteArray())\n\n        it.println(\"}\")\n    }\n}\n\nprivate abstract class TaskWithDir : DefaultTask() {\n    @get:OutputDirectory\n    abstract val outputFolder: DirectoryProperty\n}\n\nfun Project.setupStubApk() {\n    setupAppCommon()\n\n    androidAppComponents {\n        onVariants { variant ->\n            val variantName = variant.name\n            val variantCapped = variantName.replaceFirstChar { it.uppercase() }\n            val variantLowered = variantName.lowercase()\n\n            val componentJavaOutDir = layout.buildDirectory\n                .dir(\"generated/${variantLowered}/components\").get().asFile\n\n            val (factory, app) = genStubClasses(componentJavaOutDir)\n\n            val manifestUpdater =\n                project.tasks.register(\"${variantName}ManifestProducer\", ManifestUpdater::class.java) {\n                    applicationId = variant.applicationId\n                    factoryClass.set(factory)\n                    appClass.set(app)\n                }\n            variant.artifacts.use(manifestUpdater)\n                .wiredWithFiles(\n                    ManifestUpdater::mergedManifest,\n                    ManifestUpdater::outputManifest)\n                .toTransform(SingleArtifact.MERGED_MANIFEST)\n\n            val aapt = sdkComponents.aapt2.get().executable.get().asFile\n            val apk = layout.buildDirectory.file(\"intermediates/linked_resources_binary_format/\" +\n                    \"${variantLowered}/process${variantCapped}Resources/\" +\n                    \"linked-resources-binary-format-${variantLowered}.ap_\").get().asFile\n\n            val genResourcesTask = tasks.register(\"generate${variantCapped}BundledResources\", TaskWithDir::class) {\n                dependsOn(\"process${variantCapped}Resources\")\n                outputFolder.set(layout.buildDirectory.dir(\"generated/${variantLowered}/resources\"))\n\n                doLast {\n                    val apkTmp = File(\"${apk}.tmp\")\n                    providers.exec {\n                        commandLine(aapt, \"optimize\", \"-o\", apkTmp, \"--collapse-resource-names\", apk)\n                    }.result.get()\n\n                    val bos = ByteArrayOutputStream()\n                    ZipFile(apkTmp).use { src ->\n                        ZipOutputStream(apk.outputStream()).use {\n                            it.setLevel(Deflater.BEST_COMPRESSION)\n                            it.putNextEntry(ZipEntry(\"AndroidManifest.xml\"))\n                            src.getInputStream(src.getEntry(\"AndroidManifest.xml\")).transferTo(it)\n                            it.closeEntry()\n                        }\n                        DeflaterOutputStream(bos, Deflater(Deflater.BEST_COMPRESSION)).use {\n                            src.getInputStream(src.getEntry(\"resources.arsc\")).transferTo(it)\n                        }\n                    }\n                    apkTmp.delete()\n                    genEncryptedResources(bos.toByteArray(), outputFolder.get().asFile)\n                }\n            }\n\n            variant.sources.java?.let {\n                it.addStaticSourceDirectory(componentJavaOutDir.path)\n                it.addGeneratedSourceDirectory(genResourcesTask, TaskWithDir::outputFolder)\n            }\n        }\n    }\n\n    // Override optimizeReleaseResources task\n    val apk = layout.buildDirectory.file(\"intermediates/linked_resources_binary_format/\" +\n            \"release/processReleaseResources/linked-resources-binary-format-release.ap_\").get().asFile\n    val optRes = layout.buildDirectory.file(\"intermediates/optimized_processed_res/\" +\n            \"release/optimizeReleaseResources/resources-release-optimize.ap_\").get().asFile\n    afterEvaluate {\n        tasks.named(\"optimizeReleaseResources\") {\n            doLast { apk.copyTo(optRes, true) }\n        }\n    }\n    tasks.named<Delete>(\"clean\") {\n        delete.addAll(listOf(\"src/debug/AndroidManifest.xml\", \"src/release/AndroidManifest.xml\"))\n    }\n}\n"
  },
  {
    "path": "app/core/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n    kotlin(\"plugin.parcelize\")\n    alias(libs.plugins.moshix)\n    alias(libs.plugins.ksp)\n}\n\nsetupCoreLib()\n\nksp {\n    arg(\"room.generateKotlin\", \"true\")\n}\n\nandroid {\n    namespace = \"com.topjohnwu.magisk.core\"\n\n    defaultConfig {\n        buildConfigField(\"String\", \"APP_PACKAGE_NAME\", \"\\\"com.topjohnwu.magisk\\\"\")\n        buildConfigField(\"int\", \"APP_VERSION_CODE\", \"${Config.versionCode}\")\n        buildConfigField(\"String\", \"APP_VERSION_NAME\", \"\\\"${Config.version}\\\"\")\n        buildConfigField(\"int\", \"STUB_VERSION\", Config.stubVersion)\n        consumerProguardFile(\"proguard-rules.pro\")\n    }\n\n    buildFeatures {\n        aidl = true\n        buildConfig = true\n    }\n\n    compileOptions {\n        isCoreLibraryDesugaringEnabled = true\n    }\n}\n\ndependencies {\n    api(project(\":shared\"))\n    coreLibraryDesugaring(libs.jdk.libs)\n\n    api(libs.timber)\n    api(libs.markwon.core)\n    implementation(libs.bcpkix)\n    implementation(libs.commons.compress)\n\n    api(libs.libsu.core)\n    api(libs.libsu.service)\n    api(libs.libsu.nio)\n\n    implementation(libs.retrofit)\n    implementation(libs.retrofit.moshi)\n    implementation(libs.retrofit.scalars)\n\n    implementation(libs.okhttp)\n    implementation(libs.okhttp.logging)\n    implementation(libs.okhttp.dnsoverhttps)\n\n    implementation(libs.room.runtime)\n    implementation(libs.room.ktx)\n    ksp(libs.room.compiler)\n\n    implementation(libs.core.splashscreen)\n    implementation(libs.core.ktx)\n    implementation(libs.activity)\n    implementation(libs.collection.ktx)\n    implementation(libs.profileinstaller)\n\n    // We also implement all our tests in this module.\n    // However, we don't want to bundle test dependencies.\n    // That's why we make it compileOnly.\n    compileOnly(libs.test.junit)\n    compileOnly(libs.test.uiautomator)\n}\n"
  },
  {
    "path": "app/core/proguard-rules.pro",
    "content": "# Parcelable\n-keepclassmembers class * implements android.os.Parcelable {\n    public static final ** CREATOR;\n}\n\n# Kotlin\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n\tpublic static void check*(...);\n\tpublic static void throw*(...);\n}\n-assumenosideeffects class java.util.Objects {\n    public static ** requireNonNull(...);\n}\n-assumenosideeffects public class kotlin.coroutines.jvm.internal.DebugMetadataKt {\n   private static ** getDebugMetadataAnnotation(...) return null;\n}\n\n# Stub\n-keep class com.topjohnwu.magisk.core.App { <init>(java.lang.Object); }\n-keepclassmembers class androidx.appcompat.app.AppCompatDelegateImpl {\n  boolean mActivityHandlesConfigFlagsChecked;\n  int mActivityHandlesConfigFlags;\n}\n\n# Strip Timber verbose and debug logging\n-assumenosideeffects class timber.log.Timber$Tree {\n  public void v(**);\n  public void d(**);\n}\n\n# With R8 full mode generic signatures are stripped for classes that are not\n# kept. Suspend functions are wrapped in continuations where the type argument\n# is used.\n-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation\n\n-dontwarn org.junit.**\n-dontwarn org.apache.**\n"
  },
  {
    "path": "app/core/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <permission\n        android:name=\"${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION\"\n        android:protectionLevel=\"signature\"\n        tools:node=\"remove\" />\n\n    <uses-permission\n        android:name=\"${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION\"\n        tools:node=\"remove\" />\n\n    <application\n        android:name=\".App\"\n        android:icon=\"@drawable/ic_launcher\"\n        tools:ignore=\"UnusedAttribute,GoogleAppIndexingWarning\"\n        tools:remove=\"android:appComponentFactory\">\n\n        <receiver\n            android:name=\".Receiver\"\n            android:exported=\"false\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.LOCALE_CHANGED\" />\n                <action android:name=\"android.intent.action.UID_REMOVED\" />\n                <action android:name=\"android.intent.action.MY_PACKAGE_REPLACED\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.PACKAGE_REPLACED\" />\n                <action android:name=\"android.intent.action.PACKAGE_FULLY_REMOVED\" />\n\n                <data android:scheme=\"package\" />\n            </intent-filter>\n        </receiver>\n\n        <service\n            android:name=\".Service\"\n            android:exported=\"false\"\n            android:enabled=\"@bool/enable_fg_service\"\n            android:foregroundServiceType=\"dataSync\" />\n\n        <service\n            android:name=\".JobService\"\n            android:exported=\"false\"\n            android:permission=\"android.permission.BIND_JOB_SERVICE\" />\n\n        <provider\n            android:name=\".Provider\"\n            android:authorities=\"${applicationId}.provider\"\n            android:directBootAware=\"true\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\" />\n\n        <!-- We don't invalidate Room -->\n        <service\n            android:name=\"androidx.room.MultiInstanceInvalidationService\"\n            tools:node=\"remove\" />\n\n        <!-- We handle initialization ourselves -->\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            tools:node=\"remove\" />\n\n        <!-- We handle profile installation ourselves  -->\n        <receiver\n            android:name=\"androidx.profileinstaller.ProfileInstallReceiver\"\n            tools:node=\"remove\" />\n\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/core/src/main/aidl/com/topjohnwu/magisk/core/utils/IRootUtils.aidl",
    "content": "// IRootUtils.aidl\npackage com.topjohnwu.magisk.core.utils;\n\n// Declare any non-default types here with import statements\n\ninterface IRootUtils {\n    android.app.ActivityManager.RunningAppProcessInfo getAppProcess(int pid);\n    IBinder getFileSystem();\n    boolean addSystemlessHosts();\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/App.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.app.Application\nimport android.content.Context\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.utils.RootUtils\n\nopen class App() : Application() {\n\n    constructor(o: Any) : this() {\n        val data = StubApk.Data(o)\n        // Add the root service name mapping\n        data.classToComponent[RootUtils::class.java.name] = data.rootService.name\n        // Send back the actual root service class\n        data.rootService = RootUtils::class.java\n        Info.stub = data\n    }\n\n    override fun attachBaseContext(context: Context) {\n        if (context is Application) {\n            AppContext.attachApplication(context)\n        } else {\n            super.attachBaseContext(context)\n            AppContext.attachApplication(this)\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/AppContext.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.app.Activity\nimport android.app.Application\nimport android.app.LocaleManager\nimport android.content.ComponentCallbacks2\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.content.res.Configuration\nimport android.os.Build\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Bundle\nimport android.system.Os\nimport androidx.profileinstaller.ProfileInstaller\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.base.UntrackedActivity\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport com.topjohnwu.magisk.core.utils.NetworkObserver\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.core.utils.ShellInit\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport com.topjohnwu.superuser.ipc.RootService\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.asExecutor\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.lang.ref.WeakReference\nimport kotlin.system.exitProcess\n\nlateinit var AppApkPath: String\n    private set\n\nobject AppContext : ContextWrapper(null),\n    Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {\n\n    val foregroundActivity: Activity? get() = ref.get()\n\n    private var ref = WeakReference<Activity>(null)\n    private lateinit var application: Application\n    private lateinit var networkObserver: NetworkObserver\n\n    init {\n        // Always log full stack trace with Timber\n        Timber.plant(Timber.DebugTree())\n        Thread.setDefaultUncaughtExceptionHandler { _, e ->\n            Timber.e(e)\n            exitProcess(1)\n        }\n\n        Os.setenv(\"PATH\", \"${Os.getenv(\"PATH\")}:/debug_ramdisk:/sbin\", true)\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        LocaleSetting.instance.updateResource(resources)\n    }\n\n    override fun onActivityStarted(activity: Activity) {\n        networkObserver.postCurrentState()\n    }\n\n    override fun onActivityResumed(activity: Activity) {\n        if (activity is UntrackedActivity) return\n        ref = WeakReference(activity)\n    }\n\n    override fun onActivityPaused(activity: Activity) {\n        if (activity is UntrackedActivity) return\n        ref.clear()\n    }\n\n    override fun getApplicationContext() = application\n\n    fun attachApplication(app: Application) {\n        application = app\n        val base = app.baseContext\n        attachBaseContext(base)\n        app.registerActivityLifecycleCallbacks(this)\n        app.registerComponentCallbacks(this)\n\n        AppApkPath = if (isRunningAsStub) {\n            StubApk.current(base).path\n        } else {\n            base.packageResourcePath\n        }\n        resources.patch()\n\n        val shellBuilder = Shell.Builder.create()\n            .setFlags(Shell.FLAG_MOUNT_MASTER)\n            .setInitializers(ShellInit::class.java)\n            .setContext(this)\n            .setTimeout(2)\n        Shell.setDefaultBuilder(shellBuilder)\n        Shell.EXECUTOR = Dispatchers.IO.asExecutor()\n        RootUtils.bindTask = RootService.bindOrTask(\n            intent<RootUtils>(),\n            UiThreadHandler.executor,\n            RootUtils.Connection\n        )\n        // Pre-heat the shell ASAP\n        Shell.getShell(null) {}\n\n        if (SDK_INT >= 34 && isRunningAsStub) {\n            // Send over the locale config manually\n            val lm = getSystemService(LocaleManager::class.java)\n            lm.overrideLocaleConfig = LocaleSetting.localeConfig\n        }\n        networkObserver = NetworkObserver.init(this)\n        if (!BuildConfig.DEBUG && !isRunningAsStub) {\n            GlobalScope.launch(Dispatchers.IO) {\n                ProfileInstaller.writeProfile(this@AppContext)\n            }\n        }\n    }\n\n    override fun createDeviceProtectedStorageContext(): Context {\n        return if (SDK_INT >= Build.VERSION_CODES.N) {\n            super.createDeviceProtectedStorageContext()\n        } else {\n            this\n        }\n    }\n\n    override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}\n    override fun onActivityStopped(activity: Activity) {}\n    override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {}\n    override fun onActivityDestroyed(activity: Activity) {}\n    override fun onLowMemory() {}\n    override fun onTrimMemory(level: Int) {}\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Config.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.os.Bundle\nimport androidx.core.content.edit\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.repository.DBConfig\nimport com.topjohnwu.magisk.core.repository.PreferenceConfig\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport kotlinx.coroutines.GlobalScope\n\nobject Config : PreferenceConfig, DBConfig {\n\n    override val stringDB get() = ServiceLocator.stringDB\n    override val settingsDB get() = ServiceLocator.settingsDB\n    override val context get() = ServiceLocator.deContext\n    override val coroutineScope get() = GlobalScope\n\n    object Key {\n        // db configs\n        const val ROOT_ACCESS = \"root_access\"\n        const val SU_MULTIUSER_MODE = \"multiuser_mode\"\n        const val SU_MNT_NS = \"mnt_ns\"\n        const val SU_BIOMETRIC = \"su_biometric\"\n        const val ZYGISK = \"zygisk\"\n        const val BOOTLOOP = \"bootloop\"\n        const val SU_MANAGER = \"requester\"\n        const val KEYSTORE = \"keystore\"\n\n        // prefs\n        const val SU_REQUEST_TIMEOUT = \"su_request_timeout\"\n        const val SU_AUTO_RESPONSE = \"su_auto_response\"\n        const val SU_NOTIFICATION = \"su_notification\"\n        const val SU_REAUTH = \"su_reauth\"\n        const val SU_TAPJACK = \"su_tapjack\"\n        const val SU_RESTRICT = \"su_restrict\"\n        const val CHECK_UPDATES = \"check_update\"\n        const val RELEASE_CHANNEL = \"release_channel\"\n        const val CUSTOM_CHANNEL = \"custom_channel\"\n        const val LOCALE = \"locale\"\n        const val DARK_THEME = \"dark_theme_extended\"\n        const val COLOR_MODE = \"color_mode\"\n        const val DOWNLOAD_DIR = \"download_dir\"\n        const val SAFETY = \"safety_notice\"\n        const val THEME_ORDINAL = \"theme_ordinal\"\n        const val ASKED_HOME = \"asked_home\"\n        const val DOH = \"doh\"\n        const val RAND_NAME = \"rand_name\"\n\n        val NO_MIGRATION = setOf(ASKED_HOME, SU_REQUEST_TIMEOUT,\n            SU_AUTO_RESPONSE, SU_REAUTH, SU_TAPJACK)\n    }\n\n    object OldValue {\n        // Update channels\n        const val DEFAULT_CHANNEL = -1\n        const val STABLE_CHANNEL = 0\n        const val BETA_CHANNEL = 1\n        const val CUSTOM_CHANNEL = 2\n        const val CANARY_CHANNEL = 3\n        const val DEBUG_CHANNEL = 4\n    }\n\n    object Value {\n        // Update channels\n        const val DEFAULT_CHANNEL = -1\n        const val STABLE_CHANNEL = 0\n        const val BETA_CHANNEL = 1\n        const val DEBUG_CHANNEL = 2\n        const val CUSTOM_CHANNEL = 3\n\n        // root access mode\n        const val ROOT_ACCESS_DISABLED = 0\n        const val ROOT_ACCESS_APPS_ONLY = 1\n        const val ROOT_ACCESS_ADB_ONLY = 2\n        const val ROOT_ACCESS_APPS_AND_ADB = 3\n\n        // su multiuser\n        const val MULTIUSER_MODE_OWNER_ONLY = 0\n        const val MULTIUSER_MODE_OWNER_MANAGED = 1\n        const val MULTIUSER_MODE_USER = 2\n\n        // su mnt ns\n        const val NAMESPACE_MODE_GLOBAL = 0\n        const val NAMESPACE_MODE_REQUESTER = 1\n        const val NAMESPACE_MODE_ISOLATE = 2\n\n        // su notification\n        const val NO_NOTIFICATION = 0\n        const val NOTIFICATION_TOAST = 1\n        const val NOTIFICATION_STATUS_BAR = 2\n\n        // su auto response\n        const val SU_PROMPT = 0\n        const val SU_AUTO_DENY = 1\n        const val SU_AUTO_ALLOW = 2\n\n        // su timeout\n        val TIMEOUT_LIST = longArrayOf(0, -1, 10, 20, 30, 60)\n    }\n\n    @JvmField var keepVerity = false\n    @JvmField var keepEnc = false\n    @JvmField var recovery = false\n    var denyList = false\n\n    var askedHome by preference(Key.ASKED_HOME, false)\n    var bootloop by dbSettings(Key.BOOTLOOP, 0)\n\n    var safetyNotice by preference(Key.SAFETY, true)\n    var darkTheme by preference(Key.DARK_THEME, -1)\n    var themeOrdinal by preference(Key.THEME_ORDINAL, 0)\n    var colorMode by preference(Key.COLOR_MODE, 0)\n\n    private var checkUpdatePrefs by preference(Key.CHECK_UPDATES, true)\n    private var localePrefs by preference(Key.LOCALE, \"\")\n    var doh by preference(Key.DOH, false)\n    var updateChannel by preference(Key.RELEASE_CHANNEL, Value.DEFAULT_CHANNEL)\n    var customChannelUrl by preference(Key.CUSTOM_CHANNEL, \"\")\n    var downloadDir by preference(Key.DOWNLOAD_DIR, \"\")\n    var randName by preference(Key.RAND_NAME, true)\n    var checkUpdate\n        get() = checkUpdatePrefs\n        set(value) {\n            if (checkUpdatePrefs != value) {\n                checkUpdatePrefs = value\n                JobService.schedule(AppContext)\n            }\n        }\n    var locale\n        get() = localePrefs\n        set(value) {\n            localePrefs = value\n            LocaleSetting.instance.setLocale(value)\n        }\n\n    var zygisk by dbSettings(Key.ZYGISK, Info.isEmulator)\n    var suManager by dbStrings(Key.SU_MANAGER, \"\", true)\n    var keyStoreRaw by dbStrings(Key.KEYSTORE, \"\", true)\n\n    var suDefaultTimeout by preferenceStrInt(Key.SU_REQUEST_TIMEOUT, 10)\n    var suAutoResponse by preferenceStrInt(Key.SU_AUTO_RESPONSE, Value.SU_PROMPT)\n    var suNotification by preferenceStrInt(Key.SU_NOTIFICATION, Value.NOTIFICATION_TOAST)\n    var rootMode by dbSettings(Key.ROOT_ACCESS, Value.ROOT_ACCESS_APPS_AND_ADB)\n    var suMntNamespaceMode by dbSettings(Key.SU_MNT_NS, Value.NAMESPACE_MODE_REQUESTER)\n    var suMultiuserMode by dbSettings(Key.SU_MULTIUSER_MODE, Value.MULTIUSER_MODE_OWNER_ONLY)\n    private var suBiometric by dbSettings(Key.SU_BIOMETRIC, false)\n    var suAuth\n        get() = Info.isDeviceSecure && suBiometric\n        set(value) {\n            suBiometric = value\n        }\n    var suReAuth by preference(Key.SU_REAUTH, false)\n    var suTapjack by preference(Key.SU_TAPJACK, true)\n    var suRestrict by preference(Key.SU_RESTRICT, false)\n\n    private const val SU_FINGERPRINT = \"su_fingerprint\"\n    private const val UPDATE_CHANNEL = \"update_channel\"\n\n    fun toBundle(): Bundle {\n        val map = prefs.all - Key.NO_MIGRATION\n        return Bundle().apply {\n            for ((key, value) in map) {\n                when (value) {\n                    is String -> putString(key, value)\n                    is Int -> putInt(key, value)\n                    is Boolean -> putBoolean(key, value)\n                }\n            }\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private fun fromBundle(bundle: Bundle) {\n        val keys = bundle.keySet().apply { removeAll(Key.NO_MIGRATION) }\n        prefs.edit {\n            for (key in keys) {\n                when (val value = bundle.get(key)) {\n                    is String -> putString(key, value)\n                    is Int -> putInt(key, value)\n                    is Boolean -> putBoolean(key, value)\n                }\n            }\n        }\n    }\n\n    fun init(bundle: Bundle?) {\n        // Only try to load prefs when fresh install\n        if (bundle != null && prefs.all.isEmpty()) {\n            fromBundle(bundle)\n        }\n\n        prefs.edit {\n            // Migrate su_fingerprint\n            if (prefs.getBoolean(SU_FINGERPRINT, false))\n                suBiometric = true\n            remove(SU_FINGERPRINT)\n\n            // Migrate update_channel\n            prefs.getString(UPDATE_CHANNEL, null)?.let {\n                val channel = when (it.toInt()) {\n                    OldValue.STABLE_CHANNEL -> Value.STABLE_CHANNEL\n                    OldValue.CANARY_CHANNEL, OldValue.BETA_CHANNEL -> Value.BETA_CHANNEL\n                    OldValue.DEBUG_CHANNEL -> Value.DEBUG_CHANNEL\n                    OldValue.CUSTOM_CHANNEL -> Value.CUSTOM_CHANNEL\n                    else -> Value.DEFAULT_CHANNEL\n                }\n                putInt(Key.RELEASE_CHANNEL, channel)\n            }\n            remove(UPDATE_CHANNEL)\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.os.Build\nimport android.os.Process\nimport com.topjohnwu.magisk.core.BuildConfig.APP_VERSION_CODE\n\n@Suppress(\"DEPRECATION\")\nobject Const {\n\n    val CPU_ABI: String get() = Build.SUPPORTED_ABIS[0]\n\n    // Null if 32-bit only or 64-bit only\n    val CPU_ABI_32 =\n        if (Build.SUPPORTED_64_BIT_ABIS.isEmpty()) null\n        else Build.SUPPORTED_32_BIT_ABIS.firstOrNull()\n\n    // Paths\n    const val MODULE_PATH  = \"/data/adb/modules\"\n    const val TMPDIR = \"/dev/tmp\"\n    const val MAGISK_LOG = \"/cache/magisk.log\"\n\n    // Misc\n    val USER_ID = Process.myUid() / 100000\n\n    object Version {\n        const val MIN_VERSION = \"v22.0\"\n        const val MIN_VERCODE = 22000\n\n        private fun isCanary() = (Info.env.versionCode % 100) != 0\n        fun atLeast_24_0() = Info.env.versionCode >= 24000 || isCanary()\n        fun atLeast_25_0() = Info.env.versionCode >= 25000 || isCanary()\n        fun atLeast_28_0() = Info.env.versionCode >= 28000 || isCanary()\n        fun atLeast_30_1() = Info.env.versionCode >= 30100 || isCanary()\n    }\n\n    object ID {\n        const val DOWNLOAD_JOB_ID = 6\n        const val CHECK_UPDATE_JOB_ID = 7\n    }\n\n    object Url {\n        const val PATREON_URL = \"https://www.patreon.com/topjohnwu\"\n        const val SOURCE_CODE_URL = \"https://github.com/topjohnwu/Magisk\"\n\n        const val GITHUB_API_URL = \"https://api.github.com/\"\n        const val GITHUB_PAGE_URL = \"https://topjohnwu.github.io/magisk-files/\"\n        const val INVALID_URL = \"https://example.com/\"\n    }\n\n    object Key {\n        // intents\n        const val OPEN_SECTION = \"section\"\n        const val PREV_CONFIG = \"prev_config\"\n    }\n\n    object Value {\n        const val FLASH_ZIP = \"flash\"\n        const val PATCH_FILE = \"patch\"\n        const val FLASH_MAGISK = \"magisk\"\n        const val FLASH_INACTIVE_SLOT = \"slot\"\n        const val UNINSTALL = \"uninstall\"\n    }\n\n    object Nav {\n        const val HOME = \"home\"\n        const val SETTINGS = \"settings\"\n        const val MODULES = \"modules\"\n        const val SUPERUSER = \"superuser\"\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Hacks.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage com.topjohnwu.magisk.core\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.ktx.unwrap\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\n\nfun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)\n\nfun Resources.patch(): Resources {\n    if (isRunningAsStub)\n        addAssetPath(AppApkPath)\n    LocaleSetting.instance.updateResource(this)\n    return this\n}\n\nfun Context.patch(): Context {\n    unwrap().resources.patch()\n    return this\n}\n\n// Wrapping is only necessary for ContextThemeWrapper to support configuration overrides\nfun Context.wrap(): Context {\n    patch()\n    return object : ContextWrapper(this) {\n        override fun createConfigurationContext(config: Configuration): Context {\n            return super.createConfigurationContext(config).wrap()\n        }\n    }\n}\n\nfun Class<*>.cmp(pkg: String) =\n    ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)\n\ninline fun <reified T> Context.intent() = Intent().setComponent(T::class.java.cmp(packageName))\n\n// Keep a reference to these resources to prevent it from\n// being removed when running \"remove unused resources\"\nval shouldKeepResources = listOf(\n    R.string.no_info_provided,\n    R.string.release_notes,\n    R.string.invalid_update_channel,\n    R.string.update_available,\n    R.string.app_changelog,\n    R.string.home_item_source,\n    R.drawable.ic_more,\n    R.array.allow_timeout,\n)\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Info.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.app.KeyguardManager\nimport android.os.Build\nimport androidx.lifecycle.MutableLiveData\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.ktx.getProperty\nimport com.topjohnwu.magisk.core.model.UpdateInfo\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.ShellUtils.fastCmd\nimport com.topjohnwu.superuser.ShellUtils.fastCmdResult\nimport kotlinx.coroutines.Runnable\n\nval isRunningAsStub get() = Info.stub != null\n\nobject Info {\n\n    var stub: StubApk.Data? = null\n\n    private val EMPTY_UPDATE = UpdateInfo()\n    var update = EMPTY_UPDATE\n        private set\n\n    suspend fun fetchUpdate(svc: NetworkService): UpdateInfo? {\n        return if (update === EMPTY_UPDATE) {\n            svc.fetchUpdate()?.apply { update = this }\n        } else update\n    }\n\n    fun resetUpdate() {\n        update = EMPTY_UPDATE\n    }\n\n    var isRooted = false\n    var noDataExec = false\n    var patchBootVbmeta = false\n\n    @JvmStatic var env = Env()\n        private set\n    @JvmStatic var isSAR = false\n        private set\n    var legacySAR = false\n        private set\n    var isAB = false\n        private set\n    var slot = \"\"\n        private set\n    var isVendorBoot = false\n        private set\n    @JvmField val isZygiskEnabled = System.getenv(\"ZYGISK_ENABLED\") == \"1\"\n    @JvmStatic val isFDE get() = crypto == \"block\"\n    @JvmStatic var ramdisk = false\n        private set\n    private var crypto = \"\"\n\n    val isEmulator =\n        Build.DEVICE.contains(\"vsoc\")\n            || getProperty(\"ro.kernel.qemu\", \"0\") == \"1\"\n            || getProperty(\"ro.boot.qemu\", \"0\") == \"1\"\n\n    val isConnected = MutableLiveData(false)\n\n    val showSuperUser: Boolean get() {\n        return env.isActive && (Const.USER_ID == 0\n                || Config.suMultiuserMode == Config.Value.MULTIUSER_MODE_USER)\n    }\n\n    val isDeviceSecure get() =\n        AppContext.getSystemService(KeyguardManager::class.java).isDeviceSecure\n\n    class Env(\n        val versionString: String = \"\",\n        val isDebug: Boolean = false,\n        code: Int = -1\n    ) {\n        val versionCode = when {\n            code < Const.Version.MIN_VERCODE -> -1\n            isRooted -> code\n            else -> -1\n        }\n        val isUnsupported = code > 0 && code < Const.Version.MIN_VERCODE\n        val isActive = versionCode > 0\n    }\n\n    fun init(shell: Shell) {\n        if (shell.isRoot) {\n            val v = fastCmd(shell, \"magisk -v\").split(\":\")\n            env = Env(\n                v[0], v.size >= 3 && v[2] == \"D\",\n                runCatching { fastCmd(\"magisk -V\").toInt() }.getOrDefault(-1)\n            )\n            Config.denyList = fastCmdResult(shell, \"magisk --denylist status\")\n        }\n\n        val map = mutableMapOf<String, String>()\n        val list = object : CallbackList<String>(Runnable::run) {\n            override fun onAddElement(e: String) {\n                val split = e.split(\"=\")\n                if (split.size >= 2) {\n                    map[split[0]] = split[1]\n                }\n            }\n        }\n        shell.newJob().add(\"(app_init)\").to(list).exec()\n\n        fun getVar(name: String) = map[name] ?: \"\"\n        fun getBool(name: String) = map[name].toBoolean()\n\n        isSAR = getBool(\"SYSTEM_AS_ROOT\")\n        ramdisk = getBool(\"RAMDISKEXIST\")\n        isAB = getBool(\"ISAB\")\n        patchBootVbmeta = getBool(\"PATCHVBMETAFLAG\")\n        crypto = getVar(\"CRYPTOTYPE\")\n        slot = getVar(\"SLOT\")\n        legacySAR = getBool(\"LEGACYSAR\")\n        isVendorBoot = getBool(\"VENDORBOOT\")\n\n        // Default presets\n        Config.recovery = getBool(\"RECOVERYMODE\")\n        Config.keepVerity = getBool(\"KEEPVERITY\")\n        Config.keepEnc = getBool(\"KEEPFORCEENCRYPT\")\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/JobService.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.annotation.SuppressLint\nimport android.annotation.TargetApi\nimport android.app.Notification\nimport android.app.job.JobInfo\nimport android.app.job.JobParameters\nimport android.app.job.JobScheduler\nimport android.content.Context\nimport androidx.core.content.getSystemService\nimport com.topjohnwu.magisk.core.base.BaseJobService\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.DownloadSession\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.TimeUnit\n\nclass JobService : BaseJobService() {\n\n    private var mSession: Session? = null\n\n    @TargetApi(value = 34)\n    inner class Session(\n        private var params: JobParameters\n    ) : DownloadSession {\n\n        override val context get() = this@JobService\n        val engine = DownloadEngine(this)\n\n        fun updateParams(params: JobParameters) {\n            this.params = params\n            engine.reattach()\n        }\n\n        override fun attachNotification(id: Int, builder: Notification.Builder) {\n            setNotification(params, id, builder.build(), JOB_END_NOTIFICATION_POLICY_REMOVE)\n        }\n\n        override fun onDownloadComplete() {\n            jobFinished(params, false)\n        }\n    }\n\n    @SuppressLint(\"NewApi\")\n    override fun onStartJob(params: JobParameters): Boolean {\n        return when (params.jobId) {\n            Const.ID.CHECK_UPDATE_JOB_ID -> checkUpdate(params)\n            Const.ID.DOWNLOAD_JOB_ID -> downloadFile(params)\n            else -> false\n        }\n    }\n\n    override fun onStopJob(params: JobParameters?) = false\n\n    @TargetApi(value = 34)\n    private fun downloadFile(params: JobParameters): Boolean {\n        params.transientExtras.classLoader = Subject::class.java.classLoader\n        val subject = params.transientExtras\n            .getParcelable(DownloadEngine.SUBJECT_KEY, Subject::class.java) ?:\n            return false\n\n        val session = mSession?.also {\n            it.updateParams(params)\n        } ?: run {\n            Session(params).also { mSession = it }\n        }\n\n        session.engine.download(subject)\n        return true\n    }\n\n    private fun checkUpdate(params: JobParameters): Boolean {\n        GlobalScope.launch(Dispatchers.IO) {\n            Info.fetchUpdate(ServiceLocator.networkService)?.let {\n                if (Info.env.isActive && BuildConfig.APP_VERSION_CODE < it.versionCode)\n                    Notifications.updateAvailable()\n                jobFinished(params, false)\n            }\n        }\n        return true\n    }\n\n    companion object {\n        fun schedule(context: Context) {\n            val scheduler = context.getSystemService<JobScheduler>() ?: return\n            if (Config.checkUpdate) {\n                val cmp = JobService::class.java.cmp(context.packageName)\n                val info = JobInfo.Builder(Const.ID.CHECK_UPDATE_JOB_ID, cmp)\n                    .setPeriodic(TimeUnit.HOURS.toMillis(12))\n                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)\n                    .setRequiresDeviceIdle(true)\n                    .build()\n                scheduler.schedule(info)\n            } else {\n                scheduler.cancel(Const.ID.CHECK_UPDATE_JOB_ID)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Provider.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.os.Bundle\nimport com.topjohnwu.magisk.core.base.BaseProvider\nimport com.topjohnwu.magisk.core.su.SuCallbackHandler\n\nclass Provider : BaseProvider() {\n\n    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {\n        return when (method) {\n            SuCallbackHandler.LOG, SuCallbackHandler.NOTIFY -> {\n                SuCallbackHandler.run(context!!, method, extras)\n                Bundle.EMPTY\n            }\n            else -> Bundle.EMPTY\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Receiver.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.content.IntentCompat\nimport com.topjohnwu.magisk.core.base.BaseReceiver\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.view.Notifications\nimport com.topjohnwu.magisk.view.Shortcuts\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\nopen class Receiver : BaseReceiver() {\n\n    private val policyDB get() = ServiceLocator.policyDB\n\n    @SuppressLint(\"InlinedApi\")\n    private fun getPkg(intent: Intent): String? {\n        val pkg = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)\n        return pkg ?: intent.data?.schemeSpecificPart\n    }\n\n    private fun getUid(intent: Intent): Int? {\n        val uid = intent.getIntExtra(Intent.EXTRA_UID, -1)\n        return if (uid == -1) null else uid\n    }\n\n    override fun onReceive(context: Context, intent: Intent?) {\n        intent ?: return\n        super.onReceive(context, intent)\n\n        fun rmPolicy(uid: Int) = GlobalScope.launch {\n            policyDB.delete(uid)\n        }\n\n        when (intent.action ?: return) {\n            DownloadEngine.ACTION -> {\n                IntentCompat.getParcelableExtra(\n                    intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)?.let {\n                        DownloadEngine.start(context, it)\n                    }\n            }\n            Intent.ACTION_PACKAGE_REPLACED -> {\n                // This will only work pre-O\n                if (Config.suReAuth)\n                    getUid(intent)?.let { rmPolicy(it) }\n            }\n            Intent.ACTION_UID_REMOVED -> {\n                getUid(intent)?.let { rmPolicy(it) }\n            }\n            Intent.ACTION_PACKAGE_FULLY_REMOVED -> {\n                getPkg(intent)?.let { Shell.cmd(\"magisk --denylist rm $it\").submit() }\n            }\n            Intent.ACTION_LOCALE_CHANGED -> Shortcuts.setupDynamic(context)\n            Intent.ACTION_MY_PACKAGE_REPLACED -> {\n                @Suppress(\"DEPRECATION\")\n                val installer = context.packageManager.getInstallerPackageName(context.packageName)\n                if (installer == context.packageName) {\n                    Notifications.updateDone()\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/Service.kt",
    "content": "package com.topjohnwu.magisk.core\n\nimport android.app.Notification\nimport android.content.Intent\nimport android.os.Build\nimport androidx.core.app.ServiceCompat\nimport androidx.core.content.IntentCompat\nimport com.topjohnwu.magisk.core.base.BaseService\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.DownloadSession\nimport com.topjohnwu.magisk.core.download.Subject\n\nclass Service : BaseService(), DownloadSession {\n\n    private var mEngine: DownloadEngine? = null\n    override val context get() = this\n\n    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {\n        if (intent.action == DownloadEngine.ACTION) {\n            IntentCompat\n                .getParcelableExtra(intent, DownloadEngine.SUBJECT_KEY, Subject::class.java)\n                ?.let { subject ->\n                    val engine = mEngine ?: DownloadEngine(this).also { mEngine = it }\n                    engine.download(subject)\n                }\n        }\n        return START_NOT_STICKY\n    }\n\n    override fun attachNotification(id: Int, builder: Notification.Builder) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)\n            builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)\n        startForeground(id, builder.build())\n    }\n\n    override fun onDownloadComplete() {\n        ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.Manifest.permission.POST_NOTIFICATIONS\nimport android.Manifest.permission.REQUEST_INSTALL_PACKAGES\nimport android.Manifest.permission.WRITE_EXTERNAL_STORAGE\nimport android.app.Activity\nimport android.content.ActivityNotFoundException\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.widget.Toast\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.contract.ActivityResultContracts.GetContent\nimport androidx.activity.result.contract.ActivityResultContracts.RequestPermission\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.reflectField\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.utils.RequestAuthentication\nimport com.topjohnwu.magisk.core.utils.RequestInstall\n\ninterface ContentResultCallback: ActivityResultCallback<Uri>, Parcelable {\n    fun onActivityLaunch() {}\n    // Make the result type explicitly non-null\n    override fun onActivityResult(result: Uri)\n}\n\ninterface UntrackedActivity\n\ninterface IActivityExtension {\n    val extension: ActivityExtension\n    fun withPermission(permission: String, callback: (Boolean) -> Unit) {\n        extension.withPermission(permission, callback)\n    }\n    fun withAuthentication(callback: (Boolean) -> Unit) {\n        extension.withAuthentication(callback)\n    }\n    fun getContent(type: String, callback: ContentResultCallback) {\n        extension.getContent(type, callback)\n    }\n}\n\nclass ActivityExtension(private val activity: ComponentActivity) {\n\n    private var permissionCallback: ((Boolean) -> Unit)? = null\n    private val requestPermission = activity.registerForActivityResult(RequestPermission()) {\n        permissionCallback?.invoke(it)\n        permissionCallback = null\n    }\n\n    private var installCallback: ((Boolean) -> Unit)? = null\n    private val requestInstall = activity.registerForActivityResult(RequestInstall()) {\n        installCallback?.invoke(it)\n        installCallback = null\n    }\n\n    private var authenticateCallback: ((Boolean) -> Unit)? = null\n    private val requestAuthenticate = activity.registerForActivityResult(RequestAuthentication()) {\n        authenticateCallback?.invoke(it)\n        authenticateCallback = null\n    }\n\n    private var contentCallback: ContentResultCallback? = null\n    private val getContent = activity.registerForActivityResult(GetContent()) {\n        if (it != null) contentCallback?.onActivityResult(it)\n        contentCallback = null\n    }\n\n    fun onCreate(savedInstanceState: Bundle?) {\n        contentCallback = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {\n            savedInstanceState?.getParcelable(CONTENT_CALLBACK_KEY)\n        } else {\n            savedInstanceState\n                ?.getParcelable(CONTENT_CALLBACK_KEY, ContentResultCallback::class.java)\n        }\n    }\n\n    fun onSaveInstanceState(outState: Bundle) {\n        contentCallback?.let {\n            outState.putParcelable(CONTENT_CALLBACK_KEY, it)\n        }\n    }\n\n    fun withPermission(permission: String, callback: (Boolean) -> Unit) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R &&\n            permission == WRITE_EXTERNAL_STORAGE) {\n            // We do not need external rw on R+\n            callback(true)\n            return\n        }\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU &&\n            permission == POST_NOTIFICATIONS) {\n            // All apps have notification permissions before T\n            callback(true)\n            return\n        }\n        if (permission == REQUEST_INSTALL_PACKAGES) {\n            installCallback = callback\n            requestInstall.launch(Unit)\n        } else {\n            permissionCallback = callback\n            requestPermission.launch(permission)\n        }\n    }\n\n    fun withAuthentication(callback: (Boolean) -> Unit) {\n        authenticateCallback = callback\n        requestAuthenticate.launch(Unit)\n    }\n\n    fun getContent(type: String, callback: ContentResultCallback) {\n        contentCallback = callback\n        try {\n            getContent.launch(type)\n            callback.onActivityLaunch()\n        } catch (e: ActivityNotFoundException) {\n            activity.toast(R.string.app_not_found, Toast.LENGTH_SHORT)\n        }\n    }\n\n    companion object {\n        private const val CONTENT_CALLBACK_KEY = \"content_callback\"\n    }\n}\n\nval Activity.launchPackage: String? get() {\n    return if (Build.VERSION.SDK_INT >= 34) {\n        launchedFromPackage\n    } else {\n        Activity::class.java.reflectField(\"mReferrer\").get(this) as String?\n    }\n}\n\nfun Activity.relaunch() {\n    startActivity(Intent(intent).setFlags(0))\n    finish()\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/BaseJobService.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.app.job.JobService\nimport android.content.Context\nimport com.topjohnwu.magisk.core.patch\n\nabstract class BaseJobService : JobService() {\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base.patch())\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/BaseProvider.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.content.ContentProvider\nimport android.content.ContentValues\nimport android.content.Context\nimport android.content.pm.ProviderInfo\nimport android.database.Cursor\nimport android.net.Uri\nimport com.topjohnwu.magisk.core.patch\n\nopen class BaseProvider : ContentProvider() {\n    override fun attachInfo(context: Context, info: ProviderInfo) {\n        super.attachInfo(context.patch(), info)\n    }\n    override fun onCreate() = true\n    override fun getType(uri: Uri): String? = null\n    override fun insert(uri: Uri, values: ContentValues?): Uri? = null\n    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0\n    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0\n    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/BaseReceiver.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport androidx.annotation.CallSuper\nimport com.topjohnwu.magisk.core.patch\n\nabstract class BaseReceiver : BroadcastReceiver() {\n    @CallSuper\n    override fun onReceive(context: Context, intent: Intent?) {\n        context.patch()\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/BaseService.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.app.Service\nimport android.content.Context\nimport android.content.Intent\nimport android.os.IBinder\nimport com.topjohnwu.magisk.core.patch\n\nopen class BaseService : Service() {\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base.patch())\n    }\n    override fun onBind(intent: Intent?): IBinder? = null\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/base/SplashScreen.kt",
    "content": "package com.topjohnwu.magisk.core.base\n\nimport android.Manifest.permission.REQUEST_INSTALL_PACKAGES\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.JobService\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.view.Notifications\nimport com.topjohnwu.magisk.view.Shortcuts\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.launch\nimport timber.log.Timber\nimport java.io.File\nimport java.io.IOException\n\ninterface SplashScreenHost : IActivityExtension {\n    val splashController: SplashController<*>\n\n    fun onCreateUi(savedInstanceState: Bundle?)\n    fun showInvalidStateMessage()\n}\n\nclass SplashController<T>(private val activity: T)\n    where T : ComponentActivity, T: SplashScreenHost {\n\n    companion object {\n        private var splashShown = false\n    }\n\n    private var shouldCreateUiOnResume = false\n\n    fun preOnCreate() {\n        if (isRunningAsStub && !splashShown) {\n            // Manually apply splash theme for stub\n            activity.theme.applyStyle(R.style.StubSplashTheme, true)\n        }\n    }\n\n    fun onCreate(savedInstanceState: Bundle?) {\n        if (!isRunningAsStub) {\n            val splashScreen = activity.installSplashScreen()\n            splashScreen.setKeepOnScreenCondition { !splashShown }\n        }\n\n        if (splashShown) {\n            doCreateUi(savedInstanceState)\n        } else {\n            Shell.getShell(Shell.EXECUTOR) {\n                if (isRunningAsStub && !it.isRoot) {\n                    activity.showInvalidStateMessage()\n                    return@getShell\n                }\n                activity.initializeApp()\n                activity.runOnUiThread {\n                    splashShown = true\n                    if (isRunningAsStub) {\n                        // Re-launch main activity without splash theme\n                        activity.relaunch()\n                    } else {\n                        if (activity.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {\n                            doCreateUi(savedInstanceState)\n                        } else {\n                            shouldCreateUiOnResume = true\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fun onResume() {\n        if (shouldCreateUiOnResume) {\n            doCreateUi(null)\n        }\n    }\n\n    private fun doCreateUi(savedInstanceState: Bundle?) {\n        shouldCreateUiOnResume = false\n        activity.onCreateUi(savedInstanceState)\n    }\n\n    private fun T.initializeApp() {\n        val prevPkg = launchPackage\n        val prevConfig = intent.getBundleExtra(Const.Key.PREV_CONFIG)\n        val isPackageMigration = prevPkg != null && prevConfig != null\n\n        Config.init(prevConfig)\n\n        if (packageName != APP_PACKAGE_NAME) {\n            runCatching {\n                // Hidden, remove com.topjohnwu.magisk if exist as it could be malware\n                packageManager.getApplicationInfo(APP_PACKAGE_NAME, 0)\n                Shell.cmd(\"(pm uninstall $APP_PACKAGE_NAME)& >/dev/null 2>&1\").exec()\n            }\n        } else {\n            if (Config.suManager.isNotEmpty()) {\n                Config.suManager = \"\"\n            }\n            if (isPackageMigration) {\n                Shell.cmd(\"(pm uninstall $prevPkg)& >/dev/null 2>&1\").exec()\n            }\n        }\n\n        if (isPackageMigration) {\n            runOnUiThread {\n                // Relaunch the process after package migration\n                StubApk.restartProcess(this)\n            }\n            return\n        }\n\n        // Validate stub APK\n        if (isRunningAsStub && (\n                // Version mismatch\n                Info.stub!!.version != BuildConfig.STUB_VERSION ||\n                // Not properly patched\n                intent.component!!.className.contains(AppMigration.PLACEHOLDER))\n        ) {\n            withPermission(REQUEST_INSTALL_PACKAGES) { granted ->\n                if (granted) {\n                    lifecycleScope.launch {\n                        val apk = File(cacheDir, \"stub.apk\")\n                        try {\n                            assets.open(\"stub.apk\").writeTo(apk)\n                            AppMigration.upgradeStub(activity, apk)?.let {\n                                startActivity(it)\n                            }\n                        } catch (e: IOException) {\n                            Timber.e(e)\n                        }\n                    }\n                }\n            }\n            return\n        }\n\n        Notifications.setup()\n        JobService.schedule(this)\n        Shortcuts.setupDynamic(this)\n\n        // Pre-fetch network services\n        ServiceLocator.networkService\n\n        // Wait for root service\n        RootUtils.Connection.await()\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/RetrofitInterfaces.kt",
    "content": "package com.topjohnwu.magisk.core.data\n\nimport com.topjohnwu.magisk.core.model.ModuleJson\nimport com.topjohnwu.magisk.core.model.Release\nimport com.topjohnwu.magisk.core.model.UpdateJson\nimport okhttp3.ResponseBody\nimport retrofit2.Response\nimport retrofit2.http.GET\nimport retrofit2.http.Headers\nimport retrofit2.http.Path\nimport retrofit2.http.Query\nimport retrofit2.http.Streaming\nimport retrofit2.http.Url\n\ninterface RawUrl {\n\n    @GET\n    @Streaming\n    suspend fun fetchFile(@Url url: String): ResponseBody\n\n    @GET\n    suspend fun fetchString(@Url url: String): String\n\n    @GET\n    suspend fun fetchModuleJson(@Url url: String): ModuleJson\n\n    @GET\n    suspend fun fetchUpdateJson(@Url url: String): UpdateJson\n}\n\ninterface GithubApiServices {\n\n    @GET(\"/repos/{owner}/{repo}/releases\")\n    @Headers(\"Accept: application/vnd.github+json\")\n    suspend fun fetchReleases(\n        @Path(\"owner\") owner: String = \"topjohnwu\",\n        @Path(\"repo\") repo: String = \"Magisk\",\n        @Query(\"per_page\") per: Int = 10,\n        @Query(\"page\") page: Int = 1,\n    ): Response<MutableList<Release>>\n\n    @GET(\"/repos/{owner}/{repo}/releases/latest\")\n    @Headers(\"Accept: application/vnd.github+json\")\n    suspend fun fetchLatestRelease(\n        @Path(\"owner\") owner: String = \"topjohnwu\",\n        @Path(\"repo\") repo: String = \"Magisk\",\n    ): Release\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/SuLogDao.kt",
    "content": "package com.topjohnwu.magisk.core.data\n\nimport androidx.room.Dao\nimport androidx.room.Database\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.RoomDatabase\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport java.util.Calendar\n\n@Database(version = 2, entities = [SuLog::class], exportSchema = false)\nabstract class SuLogDatabase : RoomDatabase() {\n\n    abstract fun suLogDao(): SuLogDao\n\n    companion object {\n        val MIGRATION_1_2 = object : Migration(1, 2) {\n            override fun migrate(db: SupportSQLiteDatabase) = with(db) {\n                execSQL(\"ALTER TABLE logs ADD COLUMN target INTEGER NOT NULL DEFAULT -1\")\n                execSQL(\"ALTER TABLE logs ADD COLUMN context TEXT NOT NULL DEFAULT ''\")\n                execSQL(\"ALTER TABLE logs ADD COLUMN gids TEXT NOT NULL DEFAULT ''\")\n            }\n        }\n    }\n}\n\n@Dao\nabstract class SuLogDao(private val db: SuLogDatabase) {\n\n    private val twoWeeksAgo =\n        Calendar.getInstance().apply { add(Calendar.WEEK_OF_YEAR, -2) }.timeInMillis\n\n    suspend fun deleteAll() = withContext(Dispatchers.IO) { db.clearAllTables() }\n\n    suspend fun fetchAll(): MutableList<SuLog> {\n        deleteOutdated()\n        return fetch()\n    }\n\n    @Query(\"SELECT * FROM logs ORDER BY time DESC\")\n    protected abstract suspend fun fetch(): MutableList<SuLog>\n\n    @Query(\"DELETE FROM logs WHERE time < :timeout\")\n    protected abstract suspend fun deleteOutdated(timeout: Long = twoWeeksAgo)\n\n    @Insert\n    abstract suspend fun insert(log: SuLog)\n\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/magiskdb/MagiskDB.kt",
    "content": "package com.topjohnwu.magisk.core.data.magiskdb\n\nimport com.topjohnwu.magisk.core.ktx.await\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\nopen class MagiskDB {\n\n    class Literal(\n        val str: String\n    )\n\n    suspend inline fun <R> exec(\n        query: String,\n        crossinline mapper: (Map<String, String>) -> R\n    ): List<R> {\n        return withContext(Dispatchers.IO) {\n            val out = Shell.cmd(\"magisk --sqlite '$query'\").await().out\n            out.map { line ->\n                line.split(\"\\\\|\".toRegex())\n                    .map { it.split(\"=\", limit = 2) }\n                    .filter { it.size == 2 }\n                    .associate { it[0] to it[1] }\n                    .let(mapper)\n            }\n        }\n    }\n\n    suspend fun exec(query: String) {\n        withContext(Dispatchers.IO) {\n            Shell.cmd(\"magisk --sqlite '$query'\").await()\n        }\n    }\n\n    fun Map<String, Any>.toQuery(): String {\n        val keys = this.keys.joinToString(\",\")\n        val values = this.values.joinToString(\",\") {\n            when (it) {\n                is Boolean -> if (it) \"1\" else \"0\"\n                is Number -> it.toString()\n                is Literal -> it.str\n                else -> \"\\\"$it\\\"\"\n            }\n        }\n        return \"($keys) VALUES($values)\"\n    }\n\n    object Table {\n        const val POLICY = \"policies\"\n        const val SETTINGS = \"settings\"\n        const val STRINGS = \"strings\"\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/magiskdb/PolicyDao.kt",
    "content": "package com.topjohnwu.magisk.core.data.magiskdb\n\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\n\nprivate const val SELECT_QUERY = \"SELECT (until - strftime(\\\"%s\\\", \\\"now\\\")) AS remain, *\"\n\nclass PolicyDao : MagiskDB() {\n\n    suspend fun deleteOutdated() {\n        val query = \"DELETE FROM ${Table.POLICY} WHERE \" +\n            \"(until > 0 AND until < strftime(\\\"%s\\\", \\\"now\\\")) OR until < 0\"\n        exec(query)\n    }\n\n    suspend fun delete(uid: Int) {\n        val query = \"DELETE FROM ${Table.POLICY} WHERE uid=$uid\"\n        exec(query)\n    }\n\n    suspend fun fetch(uid: Int): SuPolicy? {\n        val query = \"$SELECT_QUERY FROM ${Table.POLICY} WHERE uid=$uid LIMIT 1\"\n        return exec(query, ::toPolicy).firstOrNull()\n    }\n\n    suspend fun update(policy: SuPolicy) {\n        val map = policy.toMap()\n        if (!Const.Version.atLeast_25_0()) {\n            // Put in package_name for old database\n            map[\"package_name\"] = AppContext.packageManager.getNameForUid(policy.uid)!!\n        }\n        val query = \"REPLACE INTO ${Table.POLICY} ${map.toQuery()}\"\n        exec(query)\n    }\n\n    suspend fun fetchAll(): List<SuPolicy> {\n        val query = \"$SELECT_QUERY FROM ${Table.POLICY} WHERE uid/100000=${Const.USER_ID}\"\n        return exec(query, ::toPolicy).filterNotNull()\n    }\n\n    private fun toPolicy(map: Map<String, String>): SuPolicy? {\n        val uid = map[\"uid\"]?.toInt() ?: return null\n        val policy = SuPolicy(uid)\n\n        map[\"until\"]?.toLong()?.let { until ->\n            if (until <= 0) {\n                policy.remain = until\n            } else {\n                map[\"remain\"]?.toLong()?.let { policy.remain = it }\n            }\n        }\n\n        map[\"policy\"]?.toInt()?.let { policy.policy = it }\n        map[\"logging\"]?.toInt()?.let { policy.logging = it != 0 }\n        map[\"notification\"]?.toInt()?.let { policy.notification = it != 0 }\n        return policy\n    }\n\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/magiskdb/SettingsDao.kt",
    "content": "package com.topjohnwu.magisk.core.data.magiskdb\n\nclass SettingsDao : MagiskDB() {\n\n    suspend fun delete(key: String) {\n        val query = \"DELETE FROM ${Table.SETTINGS} WHERE key=\\\"$key\\\"\"\n        exec(query)\n    }\n\n    suspend fun put(key: String, value: Int) {\n        val kv = mapOf(\"key\" to key, \"value\" to value)\n        val query = \"REPLACE INTO ${Table.SETTINGS} ${kv.toQuery()}\"\n        exec(query)\n    }\n\n    suspend fun fetch(key: String, default: Int = -1): Int {\n        val query = \"SELECT value FROM ${Table.SETTINGS} WHERE key=\\\"$key\\\" LIMIT 1\"\n        return exec(query) { it[\"value\"]?.toInt() }.firstOrNull() ?: default\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/data/magiskdb/StringDao.kt",
    "content": "package com.topjohnwu.magisk.core.data.magiskdb\n\nclass StringDao : MagiskDB() {\n\n    suspend fun delete(key: String) {\n        val query = \"DELETE FROM ${Table.STRINGS} WHERE key=\\\"$key\\\"\"\n        exec(query)\n    }\n\n    suspend fun put(key: String, value: String) {\n        val kv = mapOf(\"key\" to key, \"value\" to value)\n        val query = \"REPLACE INTO ${Table.STRINGS} ${kv.toQuery()}\"\n        exec(query)\n    }\n\n    suspend fun fetch(key: String, default: String = \"\"): String {\n        val query = \"SELECT value FROM ${Table.STRINGS} WHERE key=\\\"$key\\\" LIMIT 1\"\n        return exec(query) { it[\"value\"] }.firstOrNull() ?: default\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/di/Networking.kt",
    "content": "package com.topjohnwu.magisk.core.di\n\nimport android.content.Context\nimport com.squareup.moshi.Moshi\nimport com.topjohnwu.magisk.ProviderInstaller\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.model.DateTimeAdapter\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport okhttp3.Cache\nimport okhttp3.ConnectionSpec\nimport okhttp3.Dns\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.OkHttpClient\nimport okhttp3.dnsoverhttps.DnsOverHttps\nimport okhttp3.logging.HttpLoggingInterceptor\nimport retrofit2.Retrofit\nimport retrofit2.converter.moshi.MoshiConverterFactory\nimport retrofit2.converter.scalars.ScalarsConverterFactory\nimport java.io.File\nimport java.net.InetAddress\nimport java.net.UnknownHostException\n\nprivate class DnsResolver(client: OkHttpClient) : Dns {\n\n    private val doh by lazy {\n        DnsOverHttps.Builder().client(client)\n            .url(\"https://cloudflare-dns.com/dns-query\".toHttpUrl())\n            .bootstrapDnsHosts(listOf(\n                InetAddress.getByName(\"162.159.36.1\"),\n                InetAddress.getByName(\"162.159.46.1\"),\n                InetAddress.getByName(\"1.1.1.1\"),\n                InetAddress.getByName(\"1.0.0.1\"),\n                InetAddress.getByName(\"2606:4700:4700::1111\"),\n                InetAddress.getByName(\"2606:4700:4700::1001\"),\n                InetAddress.getByName(\"2606:4700:4700::0064\"),\n                InetAddress.getByName(\"2606:4700:4700::6400\")\n            ))\n            .resolvePrivateAddresses(true)  /* To make PublicSuffixDatabase never used */\n            .build()\n    }\n\n    override fun lookup(hostname: String): List<InetAddress> {\n        if (Config.doh) {\n            try {\n                return doh.lookup(hostname)\n            } catch (e: UnknownHostException) {}\n        }\n        return Dns.SYSTEM.lookup(hostname)\n    }\n}\n\n\nfun createOkHttpClient(context: Context): OkHttpClient {\n    val appCache = Cache(File(context.cacheDir, \"okhttp\"), 10 * 1024 * 1024)\n    val builder = OkHttpClient.Builder().cache(appCache)\n\n    if (BuildConfig.DEBUG) {\n        builder.addInterceptor(HttpLoggingInterceptor().apply {\n            level = HttpLoggingInterceptor.Level.BASIC\n        })\n    } else {\n        builder.connectionSpecs(listOf(ConnectionSpec.MODERN_TLS))\n    }\n\n    builder.dns(DnsResolver(builder.build()))\n\n    builder.addInterceptor { chain ->\n        val request = chain.request().newBuilder()\n        request.header(\"User-Agent\", \"Magisk/${BuildConfig.APP_VERSION_CODE}\")\n        request.header(\"Accept-Language\", LocaleSetting.instance.currentLocale.toLanguageTag())\n        chain.proceed(request.build())\n    }\n\n    ProviderInstaller.install(context)\n\n    return builder.build()\n}\n\nfun createMoshiConverterFactory(): MoshiConverterFactory {\n    val moshi = Moshi.Builder().add(DateTimeAdapter()).build()\n    return MoshiConverterFactory.create(moshi)\n}\n\nfun createRetrofit(okHttpClient: OkHttpClient): Retrofit.Builder {\n    return Retrofit.Builder()\n        .addConverterFactory(ScalarsConverterFactory.create())\n        .addConverterFactory(createMoshiConverterFactory())\n        .client(okHttpClient)\n}\n\ninline fun <reified T> createApiService(retrofitBuilder: Retrofit.Builder, baseUrl: String): T {\n    return retrofitBuilder\n        .baseUrl(baseUrl)\n        .build()\n        .create(T::class.java)\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/di/ServiceLocator.kt",
    "content": "package com.topjohnwu.magisk.core.di\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.text.method.LinkMovementMethod\nimport androidx.room.Room\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.data.SuLogDatabase\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.data.magiskdb.SettingsDao\nimport com.topjohnwu.magisk.core.data.magiskdb.StringDao\nimport com.topjohnwu.magisk.core.ktx.deviceProtectedContext\nimport com.topjohnwu.magisk.core.repository.LogRepository\nimport com.topjohnwu.magisk.core.repository.NetworkService\nimport io.noties.markwon.Markwon\nimport io.noties.markwon.utils.NoCopySpannableFactory\n\n@SuppressLint(\"StaticFieldLeak\")\nobject ServiceLocator {\n\n    val deContext by lazy { AppContext.deviceProtectedContext }\n    val timeoutPrefs by lazy { deContext.getSharedPreferences(\"su_timeout\", 0) }\n\n    // Database\n    val policyDB = PolicyDao()\n    val settingsDB = SettingsDao()\n    val stringDB = StringDao()\n    val sulogDB by lazy { createSuLogDatabase(deContext).suLogDao() }\n    val logRepo by lazy { LogRepository(sulogDB) }\n\n    // Networking\n    val okhttp by lazy { createOkHttpClient(AppContext) }\n    val retrofit by lazy { createRetrofit(okhttp) }\n    val markwon by lazy { createMarkwon(AppContext) }\n    val networkService by lazy {\n        NetworkService(\n            createApiService(retrofit, Const.Url.INVALID_URL),\n            createApiService(retrofit, Const.Url.GITHUB_API_URL),\n        )\n    }\n}\n\nprivate fun createSuLogDatabase(context: Context) =\n    Room.databaseBuilder(context, SuLogDatabase::class.java, \"sulogs.db\")\n        .addMigrations(SuLogDatabase.MIGRATION_1_2)\n        .fallbackToDestructiveMigration(true)\n        .build()\n\nprivate fun createMarkwon(context: Context) =\n    Markwon.builder(context).textSetter { textView, spanned, bufferType, onComplete ->\n        textView.apply {\n            movementMethod = LinkMovementMethod.getInstance()\n            setSpannableFactory(NoCopySpannableFactory.getInstance())\n            setText(spanned, bufferType)\n            onComplete.run()\n        }\n    }.build()\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadEngine.kt",
    "content": "package com.topjohnwu.magisk.core.download\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.PendingIntent\nimport android.app.job.JobInfo\nimport android.app.job.JobScheduler\nimport android.content.Context\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.collection.SparseArrayCompat\nimport androidx.collection.isNotEmpty\nimport androidx.core.content.getSystemService\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.MutableLiveData\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.JobService\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.base.IActivityExtension\nimport com.topjohnwu.magisk.core.cmp\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.intent\nimport com.topjohnwu.magisk.core.ktx.set\nimport com.topjohnwu.magisk.core.utils.ProgressInputStream\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.launch\nimport okhttp3.ResponseBody\nimport timber.log.Timber\nimport java.io.InputStream\n\n/**\n * This class drives the execution of file downloads and notification management.\n *\n * Each download engine instance has to be paired with a \"session\" that is managed by the operating\n * system. A session is an Android component that allows executing long lasting operations and\n * have its state tied to a notification to show progress.\n *\n * A session can only have one single notification representing its state, and the operating system\n * also uses the notification to manage the lifecycle of a session. One goal of this class is\n * to support concurrent download tasks using only one single session, so internally it manages\n * all active tasks and notifications and properly re-assign notifications to be attached to\n * the session to make sure all download operations can be completed without the operating system\n * killing the session.\n *\n * For API 23 - 33, we use a foreground service as a session.\n * For API 34 and higher, we use user-initiated job services as a session.\n */\nclass DownloadEngine(session: DownloadSession) : DownloadSession by session, DownloadNotifier {\n\n    companion object {\n        const val ACTION = \"com.topjohnwu.magisk.DOWNLOAD\"\n        const val SUBJECT_KEY = \"subject\"\n        private const val REQUEST_CODE = 1\n\n        private val progressBroadcast = MutableLiveData<Pair<Float, Subject>?>()\n\n        private fun broadcast(progress: Float, subject: Subject) {\n            progressBroadcast.postValue(progress to subject)\n        }\n\n        fun observeProgress(owner: LifecycleOwner, callback: (Float, Subject) -> Unit) {\n            progressBroadcast.value = null\n            progressBroadcast.observe(owner) {\n                val (progress, subject) = it ?: return@observe\n                callback(progress, subject)\n            }\n        }\n\n        private fun createBroadcastIntent(context: Context, subject: Subject) =\n            context.intent<com.topjohnwu.magisk.core.Receiver>()\n                .setAction(ACTION)\n                .putExtra(SUBJECT_KEY, subject)\n\n        private fun createServiceIntent(context: Context, subject: Subject) =\n            context.intent<com.topjohnwu.magisk.core.Service>()\n                .setAction(ACTION)\n                .putExtra(SUBJECT_KEY, subject)\n\n        @SuppressLint(\"InlinedApi\")\n        fun getPendingIntent(context: Context, subject: Subject): PendingIntent {\n            val flag = PendingIntent.FLAG_IMMUTABLE or\n                PendingIntent.FLAG_UPDATE_CURRENT or\n                PendingIntent.FLAG_ONE_SHOT\n            return if (Build.VERSION.SDK_INT >= 34) {\n                // On API 34+, download tasks are handled with a user-initiated job.\n                // However, there is no way to schedule a new job directly with a pending intent.\n                // As a workaround, we send the subject to a broadcast receiver and have it\n                // schedule the job for us.\n                val intent = createBroadcastIntent(context, subject)\n                PendingIntent.getBroadcast(context, REQUEST_CODE, intent, flag)\n            } else {\n                val intent = createServiceIntent(context, subject)\n                if (Build.VERSION.SDK_INT >= 26) {\n                    PendingIntent.getForegroundService(context, REQUEST_CODE, intent, flag)\n                } else {\n                    PendingIntent.getService(context, REQUEST_CODE, intent, flag)\n                }\n            }\n        }\n\n        @SuppressLint(\"InlinedApi\")\n        fun <T> startWithActivity(\n            activity: T,\n            subject: Subject\n        ) where T : ComponentActivity, T : IActivityExtension {\n            activity.withPermission(Manifest.permission.POST_NOTIFICATIONS) {\n                // Always download regardless of notification permission status\n                start(activity.applicationContext, subject)\n            }\n        }\n\n        @SuppressLint(\"MissingPermission\")\n        fun start(context: Context, subject: Subject) {\n            if (Build.VERSION.SDK_INT >= 34) {\n                val scheduler = context.getSystemService<JobScheduler>()!!\n                val cmp = JobService::class.java.cmp(context.packageName)\n                val extras = Bundle()\n                extras.putParcelable(SUBJECT_KEY, subject)\n                val info = JobInfo.Builder(Const.ID.DOWNLOAD_JOB_ID, cmp)\n                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)\n                    .setUserInitiated(true)\n                    .setTransientExtras(extras)\n                    .build()\n                scheduler.schedule(info)\n            } else {\n                val intent = createServiceIntent(context, subject)\n                if (Build.VERSION.SDK_INT >= 26) {\n                    context.startForegroundService(intent)\n                } else {\n                    context.startService(intent)\n                }\n            }\n        }\n    }\n\n    private val notifications = SparseArrayCompat<Notification.Builder>()\n    private var attachedId = -1\n    private val job = Job()\n    private val processor = DownloadProcessor(this)\n    private val network get() = ServiceLocator.networkService\n\n    fun download(subject: Subject) {\n        notifyUpdate(subject.notifyId)\n        CoroutineScope(job + Dispatchers.IO).launch {\n            try {\n                val stream = network.fetchFile(subject.url).toProgressStream(subject)\n                processor.handle(stream, subject)\n                val activity = AppContext.foregroundActivity\n                if (activity != null && subject.autoLaunch) {\n                    notifyRemove(subject.notifyId)\n                    subject.pendingIntent(activity)?.send()\n                } else {\n                    notifyFinish(subject)\n                }\n            } catch (e: Exception) {\n                Timber.e(e)\n                notifyFail(subject)\n            }\n        }\n    }\n\n    @Synchronized\n    fun reattach() {\n        val builder = notifications[attachedId] ?: return\n        attachNotification(attachedId, builder)\n    }\n\n    private fun attach(id: Int, notification: Notification.Builder) {\n        attachedId = id\n        attachNotification(id, notification)\n    }\n\n    private fun finalNotify(id: Int, editor: (Notification.Builder) -> Unit): Int {\n        val notification = notifyRemove(id)?.also(editor) ?: return -1\n        val newId = Notifications.nextId()\n        Notifications.mgr.notify(newId, notification.build())\n        return newId\n    }\n\n    private fun notifyFail(subject: Subject) = finalNotify(subject.notifyId) {\n        broadcast(-2f, subject)\n        it.setContentText(context.getString(R.string.download_file_error))\n            .setSmallIcon(android.R.drawable.stat_notify_error)\n            .setOngoing(false)\n    }\n\n    private fun notifyFinish(subject: Subject) = finalNotify(subject.notifyId) {\n        broadcast(1f, subject)\n        it.setContentTitle(subject.title)\n            .setContentText(context.getString(R.string.download_complete))\n            .setSmallIcon(android.R.drawable.stat_sys_download_done)\n            .setProgress(0, 0, false)\n            .setOngoing(false)\n            .setAutoCancel(true)\n        subject.pendingIntent(context)?.let { intent -> it.setContentIntent(intent) }\n    }\n\n    @Synchronized\n    override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {\n        val notification = (notifications[id] ?: Notifications.startProgress(\"\").also {\n            notifications[id] = it\n        }).apply(editor)\n\n        if (attachedId < 0)\n            attach(id, notification)\n        else\n            Notifications.mgr.notify(id, notification.build())\n    }\n\n    @Synchronized\n    private fun notifyRemove(id: Int): Notification.Builder? {\n        val idx = notifications.indexOfKey(id)\n        var n: Notification.Builder? = null\n\n        if (idx >= 0) {\n            n = notifications.valueAt(idx)\n            notifications.removeAt(idx)\n\n            // The cancelled notification is the one attached to the session, need special handling\n            if (attachedId == id) {\n                if (notifications.isNotEmpty()) {\n                    // There are still remaining notifications, pick one and attach to the session\n                    val anotherId = notifications.keyAt(0)\n                    val notification = notifications.valueAt(0)\n                    attach(anotherId, notification)\n                } else {\n                    // No more notifications left, terminate the session\n                    attachedId = -1\n                    onDownloadComplete()\n                }\n            }\n        }\n\n        Notifications.mgr.cancel(id)\n        return n\n    }\n\n    private fun ResponseBody.toProgressStream(subject: Subject): InputStream {\n        val max = contentLength()\n        val total = max.toFloat() / 1048576\n        val id = subject.notifyId\n\n        notifyUpdate(id) { it.setContentTitle(subject.title) }\n\n        return ProgressInputStream(byteStream()) {\n            val progress = it.toFloat() / 1048576\n            notifyUpdate(id) { notification ->\n                if (max > 0) {\n                    broadcast(progress / total, subject)\n                    notification\n                        .setProgress(max.toInt(), it.toInt(), false)\n                        .setContentText(\"%.2f / %.2f MB\".format(progress, total))\n                } else {\n                    broadcast(-1f, subject)\n                    notification.setContentText(\"%.2f MB / ??\".format(progress))\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/download/DownloadProcessor.kt",
    "content": "package com.topjohnwu.magisk.core.download\n\nimport android.net.Uri\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.cachedFile\nimport com.topjohnwu.magisk.core.ktx.copyAll\nimport com.topjohnwu.magisk.core.ktx.copyAndClose\nimport com.topjohnwu.magisk.core.ktx.withInOut\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.utils.APKInstall\nimport org.apache.commons.compress.archivers.zip.ZipArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream\nimport org.apache.commons.compress.archivers.zip.ZipFile\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\n\nclass DownloadProcessor(notifier: DownloadNotifier) : DownloadNotifier by notifier {\n\n    suspend fun handle(stream: InputStream, subject: Subject) {\n        when (subject) {\n            is Subject.App -> handleApp(stream, subject)\n            is Subject.Module -> handleModule(stream, subject.file)\n            else -> stream.copyAndClose(subject.file.outputStream())\n        }\n    }\n\n    suspend fun handleApp(stream: InputStream, subject: Subject.App) {\n        val external = subject.file.outputStream()\n\n        if (isRunningAsStub) {\n            val updateApk = StubApk.update(context)\n            try {\n                // Download full APK to stub update path\n                stream.copyAndClose(TeeOutputStream(external, updateApk.outputStream()))\n\n                // Also upgrade stub\n                notifyUpdate(subject.notifyId) {\n                    it.setProgress(0, 0, true)\n                        .setContentTitle(context.getString(R.string.hide_app_title))\n                        .setContentText(\"\")\n                }\n\n                // Extract stub\n                val apk = context.cachedFile(\"stub.apk\")\n                ZipFile.Builder().setFile(updateApk).get().use { zf ->\n                    apk.delete()\n                    zf.getInputStream(zf.getEntry(\"assets/stub.apk\")).writeTo(apk)\n                }\n\n                // Patch and install\n                subject.intent = AppMigration.upgradeStub(context, apk)\n                    ?: throw IOException(\"HideAPK patch error\")\n                apk.delete()\n            } catch (e: Exception) {\n                // If any error occurred, do not let stub load the new APK\n                updateApk.delete()\n                throw e\n            }\n        } else {\n            val session = APKInstall.startSession(context)\n            stream.copyAndClose(TeeOutputStream(external, session.openStream(context)))\n            subject.intent = session.waitIntent()\n        }\n    }\n\n    suspend fun handleModule(src: InputStream, file: Uri) {\n        val tmp = context.cachedFile(\"module.zip\")\n        try {\n            // First download the entire zip into cache so we can process it\n            src.writeTo(tmp)\n\n            val input = ZipFile.Builder().setFile(tmp).get()\n            val output = ZipArchiveOutputStream(file.outputStream())\n            withInOut(input, output) { zin, zout ->\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/\"))\n                zout.closeArchiveEntry()\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/com/\"))\n                zout.closeArchiveEntry()\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/com/google/\"))\n                zout.closeArchiveEntry()\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/com/google/android/\"))\n                zout.closeArchiveEntry()\n\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/com/google/android/update-binary\"))\n                context.assets.open(\"module_installer.sh\").use { it.copyAll(zout) }\n                zout.closeArchiveEntry()\n\n                zout.putArchiveEntry(ZipArchiveEntry(\"META-INF/com/google/android/updater-script\"))\n                zout.write(\"#MAGISK\\n\".toByteArray())\n                zout.closeArchiveEntry()\n\n                // Then simply copy all entries to output\n                zin.copyRawEntries(zout) { entry -> !entry.name.startsWith(\"META-INF\") }\n            }\n        } finally {\n            tmp.delete()\n        }\n    }\n\n    private class TeeOutputStream(\n        private val o1: OutputStream,\n        private val o2: OutputStream\n    ) : OutputStream() {\n        override fun write(b: Int) {\n            o1.write(b)\n            o2.write(b)\n        }\n        override fun write(b: ByteArray?, off: Int, len: Int) {\n            o1.write(b, off, len)\n            o2.write(b, off, len)\n        }\n        override fun close() {\n            o1.close()\n            o2.close()\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/download/Interfaces.kt",
    "content": "package com.topjohnwu.magisk.core.download\n\nimport android.app.Notification\nimport android.content.Context\n\ninterface DownloadSession {\n    val context: Context\n    fun attachNotification(id: Int, builder: Notification.Builder)\n    fun onDownloadComplete()\n}\n\ninterface DownloadNotifier {\n    val context: Context\n    fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit = {})\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/download/Subject.kt",
    "content": "package com.topjohnwu.magisk.core.download\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Parcelable\nimport androidx.core.net.toUri\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.model.UpdateInfo\nimport com.topjohnwu.magisk.core.model.module.OnlineModule\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\nimport java.io.File\nimport java.util.UUID\n\nabstract class Subject : Parcelable {\n\n    abstract val url: String\n    abstract val file: Uri\n    abstract val title: String\n    abstract val notifyId: Int\n    open val autoLaunch: Boolean get() = true\n\n    open fun pendingIntent(context: Context): PendingIntent? = null\n\n    abstract class Module : Subject() {\n        abstract val module: OnlineModule\n        final override val url: String get() = module.zipUrl\n        final override val title: String get() = module.downloadFilename\n        final override val file by lazy {\n            MediaStoreUtils.getFile(title).uri\n        }\n    }\n\n    @Parcelize\n    class App(\n        private val json: UpdateInfo = Info.update,\n        override val notifyId: Int = Notifications.nextId()\n    ) : Subject() {\n        override val title: String get() = \"Magisk-${json.version}(${json.versionCode})\"\n        override val url: String get() = json.link\n\n        @IgnoredOnParcel\n        override val file by lazy {\n            MediaStoreUtils.getFile(\"${title}.apk\").uri\n        }\n\n        @IgnoredOnParcel\n        var intent: Intent? = null\n        override fun pendingIntent(context: Context) = intent?.toPending(context)\n    }\n\n    @Parcelize\n    class Test(\n        override val notifyId: Int = Notifications.nextId(),\n        override val title: String = UUID.randomUUID().toString().substring(0, 6)\n    ) : Subject() {\n        override val url get() = \"https://link.testfile.org/250MB\"\n        override val file get() = File(\"/dev/null\").toUri()\n        override val autoLaunch get() = false\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    protected fun Intent.toPending(context: Context): PendingIntent {\n        return PendingIntent.getActivity(context, notifyId, this,\n            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT)\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XAndroid.kt",
    "content": "package com.topjohnwu.magisk.core.ktx\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.drawable.AdaptiveIconDrawable\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.LayerDrawable\nimport android.os.Build\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Process\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.Toast\nimport androidx.core.content.getSystemService\nimport com.topjohnwu.magisk.core.utils.LocaleSetting\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.utils.APKInstall\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport java.io.File\n\nfun Context.getBitmap(id: Int): Bitmap {\n    var drawable = getDrawable(id)!!\n    if (drawable is BitmapDrawable)\n        return drawable.bitmap\n    if (SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable) {\n        drawable = LayerDrawable(arrayOf(drawable.background, drawable.foreground))\n    }\n    val bitmap = Bitmap.createBitmap(\n        drawable.intrinsicWidth, drawable.intrinsicHeight,\n        Bitmap.Config.ARGB_8888\n    )\n    val canvas = Canvas(bitmap)\n    drawable.setBounds(0, 0, canvas.width, canvas.height)\n    drawable.draw(canvas)\n    return bitmap\n}\n\nval Context.deviceProtectedContext: Context get() =\n    if (SDK_INT >= Build.VERSION_CODES.N) {\n        createDeviceProtectedStorageContext()\n    } else { this }\n\nfun Context.cachedFile(name: String) = File(cacheDir, name)\n\nfun ApplicationInfo.getLabel(pm: PackageManager): String {\n    runCatching {\n        if (labelRes > 0) {\n            val res = pm.getResourcesForApplication(this)\n            LocaleSetting.instance.updateResource(res)\n            return res.getString(labelRes)\n        }\n    }\n\n    return loadLabel(pm).toString()\n}\n\nfun Context.unwrap(): Context {\n    var context = this\n    while (context is ContextWrapper)\n        context = context.baseContext\n    return context\n}\n\nfun Activity.hideKeyboard() {\n    val view = currentFocus ?: return\n    getSystemService<InputMethodManager>()\n        ?.hideSoftInputFromWindow(view.windowToken, 0)\n    view.clearFocus()\n}\n\nval View.activity: Activity get() {\n    var context = context\n    while(true) {\n        if (context !is ContextWrapper)\n            error(\"View is not attached to activity\")\n        if (context is Activity)\n            return context\n        context = context.baseContext\n    }\n}\n\n@SuppressLint(\"PrivateApi\")\nfun getProperty(key: String, def: String): String {\n    runCatching {\n        val clazz = Class.forName(\"android.os.SystemProperties\")\n        val get = clazz.getMethod(\"get\", String::class.java, String::class.java)\n        return get.invoke(clazz, key, def) as String\n    }\n    return def\n}\n\n@SuppressLint(\"InlinedApi\")\n@Throws(PackageManager.NameNotFoundException::class)\nfun PackageManager.getPackageInfo(uid: Int, pid: Int): PackageInfo? {\n    val flag = PackageManager.MATCH_UNINSTALLED_PACKAGES\n    val pkgs = getPackagesForUid(uid) ?: throw PackageManager.NameNotFoundException()\n    if (pkgs.size > 1) {\n        if (pid <= 0) {\n            return null\n        }\n        // Try to find package name from PID\n        val proc = RootUtils.getAppProcess(pid)\n        if (proc == null) {\n            if (uid == Process.SHELL_UID) {\n                // It is possible that some apps installed are sharing UID with shell.\n                // We will not be able to find a package from the active process list,\n                // because the client is forked from ADB shell, not any app process.\n                return getPackageInfo(\"com.android.shell\", flag)\n            }\n        } else if (uid == proc.uid) {\n            return getPackageInfo(proc.pkgList[0], flag)\n        }\n\n        return null\n    }\n    if (pkgs.size == 1) {\n        return getPackageInfo(pkgs[0], flag)\n    }\n    throw PackageManager.NameNotFoundException()\n}\n\nfun Context.registerRuntimeReceiver(receiver: BroadcastReceiver, filter: IntentFilter) {\n    APKInstall.registerReceiver(this, receiver, filter)\n}\n\nfun Context.selfLaunchIntent(): Intent {\n    val pm = packageManager\n    val intent = pm.getLaunchIntentForPackage(packageName)!!\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)\n    return intent\n}\n\nfun Context.toast(msg: CharSequence, duration: Int) {\n    UiThreadHandler.run { Toast.makeText(this, msg, duration).show() }\n}\n\nfun Context.toast(resId: Int, duration: Int) {\n    UiThreadHandler.run { Toast.makeText(this, resId, duration).show() }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XJVM.kt",
    "content": "package com.topjohnwu.magisk.core.ktx\n\nimport androidx.collection.SparseArrayCompat\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flatMapMerge\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.withContext\nimport java.io.Closeable\nimport java.io.File\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.lang.reflect.Field\nimport java.time.Instant\nimport java.time.ZoneId\nimport java.time.format.DateTimeFormatter\nimport java.time.format.FormatStyle\nimport java.util.Collections\n\ninline fun <In : Closeable, Out : Closeable> withInOut(\n    input: In,\n    output: Out,\n    withBoth: (In, Out) -> Unit\n) {\n    input.use { reader ->\n        output.use { writer ->\n            withBoth(reader, writer)\n        }\n    }\n}\n\n@Throws(IOException::class)\nsuspend fun InputStream.copyAll(\n    out: OutputStream,\n    bufferSize: Int = DEFAULT_BUFFER_SIZE,\n    dispatcher: CoroutineDispatcher = Dispatchers.IO\n): Long {\n    return withContext(dispatcher) {\n        var bytesCopied: Long = 0\n        val buffer = ByteArray(bufferSize)\n        var bytes = read(buffer)\n        while (isActive && bytes >= 0) {\n            out.write(buffer, 0, bytes)\n            bytesCopied += bytes\n            bytes = read(buffer)\n        }\n        bytesCopied\n    }\n}\n\n@Throws(IOException::class)\nsuspend inline fun InputStream.copyAndClose(\n    out: OutputStream,\n    bufferSize: Int = DEFAULT_BUFFER_SIZE,\n    dispatcher: CoroutineDispatcher = Dispatchers.IO\n) = withInOut(this, out) { i, o -> i.copyAll(o, bufferSize, dispatcher) }\n\n@Throws(IOException::class)\nsuspend inline fun InputStream.writeTo(\n    file: File,\n    bufferSize: Int = DEFAULT_BUFFER_SIZE,\n    dispatcher: CoroutineDispatcher = Dispatchers.IO\n) = copyAndClose(file.outputStream(), bufferSize, dispatcher)\n\noperator fun <E> SparseArrayCompat<E>.set(key: Int, value: E) {\n    put(key, value)\n}\n\nfun <T> MutableList<T>.synchronized(): MutableList<T> = Collections.synchronizedList(this)\n\nfun <T> MutableSet<T>.synchronized(): MutableSet<T> = Collections.synchronizedSet(this)\n\nfun <K, V> MutableMap<K, V>.synchronized(): MutableMap<K, V> = Collections.synchronizedMap(this)\n\nfun Class<*>.reflectField(name: String): Field =\n    getDeclaredField(name).apply { isAccessible = true }\n\ninline fun <T, R> Flow<T>.concurrentMap(crossinline transform: suspend (T) -> R): Flow<R> {\n    return flatMapMerge { value ->\n        flow { emit(transform(value)) }\n    }\n}\n\nfun Long.toTime(format: DateTimeFormatter): String = format.format(Instant.ofEpochMilli(this))\n\n// Some devices don't allow filenames containing \":\"\nval timeFormatStandard: DateTimeFormatter by lazy {\n    DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH.mm.ss\").withZone(ZoneId.systemDefault())\n}\nval timeDateFormat: DateTimeFormatter by lazy {\n    DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withZone(ZoneId.systemDefault())\n}\nval dateFormat: DateTimeFormatter by lazy {\n    DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withZone(ZoneId.systemDefault())\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/ktx/XSU.kt",
    "content": "package com.topjohnwu.magisk.core.ktx\n\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\nfun reboot(reason: String = if (Config.recovery) \"recovery\" else \"\") {\n    if (reason == \"recovery\") {\n        // KEYCODE_POWER = 26, hide incorrect \"Factory data reset\" message\n        Shell.cmd(\"/system/bin/input keyevent 26\").submit()\n    }\n    Shell.cmd(\"/system/bin/svc power reboot $reason || /system/bin/reboot $reason\").submit()\n}\n\nsuspend fun Shell.Job.await() = withContext(Dispatchers.IO) { exec() }\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/UpdateInfo.kt",
    "content": "package com.topjohnwu.magisk.core.model\n\nimport android.os.Parcelable\nimport com.squareup.moshi.FromJson\nimport com.squareup.moshi.Json\nimport com.squareup.moshi.JsonClass\nimport com.squareup.moshi.JsonQualifier\nimport com.squareup.moshi.ToJson\nimport kotlinx.parcelize.Parcelize\nimport java.time.Instant\n\n@JsonClass(generateAdapter = true)\nclass UpdateJson(\n    val magisk: UpdateInfo = UpdateInfo(),\n)\n\n@Parcelize\n@JsonClass(generateAdapter = true)\ndata class UpdateInfo(\n    val version: String = \"\",\n    val versionCode: Int = -1,\n    val link: String = \"\",\n    val note: String = \"\"\n) : Parcelable\n\n@JsonClass(generateAdapter = true)\ndata class ModuleJson(\n    val version: String,\n    val versionCode: Int,\n    val zipUrl: String,\n    val changelog: String,\n)\n\n@JsonClass(generateAdapter = true)\ndata class ReleaseAssets(\n    val name: String,\n    @param:Json(name = \"browser_download_url\") val url: String,\n)\n\nclass DateTimeAdapter {\n    @ToJson\n    fun toJson(date: Instant): String {\n        return date.toString()\n    }\n\n    @FromJson\n    fun fromJson(date: String): Instant {\n        return Instant.parse(date)\n    }\n}\n\n@JsonClass(generateAdapter = true)\ndata class Release(\n    @param:Json(name = \"tag_name\") val tag: String,\n    val name: String,\n    val prerelease: Boolean,\n    val assets: List<ReleaseAssets>,\n    val body: String,\n    @param:Json(name = \"created_at\") val createdTime: Instant,\n) {\n    val versionCode: Int get() {\n        return if (tag[0] == 'v') {\n            (tag.drop(1).toFloat() * 1000).toInt()\n        } else {\n            tag.drop(7).toInt()\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt",
    "content": "package com.topjohnwu.magisk.core.model.module\n\nimport com.squareup.moshi.JsonDataException\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.IOException\nimport java.util.Locale\n\ndata class LocalModule(\n    val base: ExtendedFile,\n) : Module() {\n    private val svc get() = ServiceLocator.networkService\n\n    override var id: String = \"\"\n    override var name: String = \"\"\n    override var version: String = \"\"\n    override var versionCode: Int = -1\n    var author: String = \"\"\n    var description: String = \"\"\n    var updateInfo: OnlineModule? = null\n    var outdated = false\n    private var updateUrl: String = \"\"\n\n    private val removeFile = base.getChildFile(\"remove\")\n    private val disableFile = base.getChildFile(\"disable\")\n    private val updateFile = base.getChildFile(\"update\")\n    val zygiskFolder = base.getChildFile(\"zygisk\")\n\n    val updated get() = updateFile.exists()\n    val isRiru = (id == \"riru-core\") || base.getChildFile(\"riru\").exists()\n    val isZygisk = zygiskFolder.exists()\n    val zygiskUnloaded = zygiskFolder.getChildFile(\"unloaded\").exists()\n    val hasAction = base.getChildFile(\"action.sh\").exists()\n\n    var enable: Boolean\n        get() = !disableFile.exists()\n        set(enable) {\n            if (enable) {\n                disableFile.delete()\n                Shell.cmd(\"copy_preinit_files\").submit()\n            } else {\n                !disableFile.createNewFile()\n                Shell.cmd(\"copy_preinit_files\").submit()\n            }\n        }\n\n    var remove: Boolean\n        get() = removeFile.exists()\n        set(remove) {\n            if (remove) {\n                if (updateFile.exists()) return\n                removeFile.createNewFile()\n                Shell.cmd(\"copy_preinit_files\").submit()\n            } else {\n                removeFile.delete()\n                Shell.cmd(\"copy_preinit_files\").submit()\n            }\n        }\n\n    @Throws(NumberFormatException::class)\n    private fun parseProps(props: List<String>) {\n        for (line in props) {\n            val prop = line.split(\"=\".toRegex(), 2).map { it.trim() }\n            if (prop.size != 2)\n                continue\n\n            val key = prop[0]\n            val value = prop[1]\n            if (key.isEmpty() || key[0] == '#')\n                continue\n\n            when (key) {\n                \"id\" -> id = value\n                \"name\" -> name = value\n                \"version\" -> version = value\n                \"versionCode\" -> versionCode = value.toInt()\n                \"author\" -> author = value\n                \"description\" -> description = value\n                \"updateJson\" -> updateUrl = value\n            }\n        }\n    }\n\n    init {\n        runCatching {\n            parseProps(Shell.cmd(\"dos2unix < $base/module.prop\").exec().out)\n        }\n\n        if (id.isEmpty()) {\n            id = base.name\n        }\n\n        if (name.isEmpty()) {\n            name = id\n        }\n    }\n\n    suspend fun fetch(): Boolean {\n        if (updateUrl.isEmpty())\n            return false\n\n        try {\n            val json = svc.fetchModuleJson(updateUrl)\n            updateInfo = OnlineModule(this, json)\n            outdated = json.versionCode > versionCode\n            return true\n        } catch (e: IOException) {\n            Timber.w(e)\n        } catch (e: JsonDataException) {\n            Timber.w(e)\n        }\n\n        return false\n    }\n\n    companion object {\n\n        fun loaded() = RootUtils.fs.getFile(Const.MODULE_PATH).exists()\n\n        suspend fun installed() = withContext(Dispatchers.IO) {\n            RootUtils.fs.getFile(Const.MODULE_PATH)\n                .listFiles()\n                .orEmpty()\n                .filter { !it.isFile && !it.isHidden }\n                .map { LocalModule(it) }\n                .sortedBy { it.name.lowercase(Locale.ROOT) }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/module/Module.kt",
    "content": "package com.topjohnwu.magisk.core.model.module\n\nabstract class Module : Comparable<Module> {\n    abstract var id: String\n        protected set\n    abstract var name: String\n        protected set\n    abstract var version: String\n        protected set\n    abstract var versionCode: Int\n        protected set\n\n    override operator fun compareTo(other: Module) = id.compareTo(other.id)\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/module/OnlineModule.kt",
    "content": "package com.topjohnwu.magisk.core.model.module\n\nimport android.os.Parcelable\nimport com.topjohnwu.magisk.core.model.ModuleJson\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class OnlineModule(\n    override var id: String,\n    override var name: String,\n    override var version: String,\n    override var versionCode: Int,\n    val zipUrl: String,\n    val changelog: String,\n) : Module(), Parcelable {\n    constructor(local: LocalModule, json: ModuleJson) :\n        this(local.id, local.name, json.version, json.versionCode, json.zipUrl, json.changelog)\n\n    val downloadFilename get() = \"$name-$version($versionCode).zip\".legalFilename()\n\n    private fun String.legalFilename() = replace(\" \", \"_\")\n        .replace(\"'\", \"\").replace(\"\\\"\", \"\")\n        .replace(\"$\", \"\").replace(\"`\", \"\")\n        .replace(\"*\", \"\").replace(\"/\", \"_\")\n        .replace(\"#\", \"\").replace(\"@\", \"\")\n        .replace(\"\\\\\", \"_\")\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/su/SuLog.kt",
    "content": "package com.topjohnwu.magisk.core.model.su\n\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport com.topjohnwu.magisk.core.ktx.getLabel\n\n@Entity(tableName = \"logs\")\nclass SuLog(\n    val fromUid: Int,\n    val toUid: Int,\n    val fromPid: Int,\n    val packageName: String,\n    val appName: String,\n    val command: String,\n    val action: Int,\n    val target: Int,\n    val context: String,\n    val gids: String,\n    val time: Long = System.currentTimeMillis()\n) {\n    @PrimaryKey(autoGenerate = true) var id: Int = 0\n}\n\nfun PackageManager.createSuLog(\n    info: ApplicationInfo,\n    toUid: Int,\n    fromPid: Int,\n    command: String,\n    policy: Int,\n    target: Int,\n    context: String,\n    gids: String,\n): SuLog {\n    return SuLog(\n        fromUid = info.uid,\n        toUid = toUid,\n        fromPid = fromPid,\n        packageName = getNameForUid(info.uid)!!,\n        appName = info.getLabel(this),\n        command = command,\n        action = policy,\n        target = target,\n        context = context,\n        gids = gids,\n    )\n}\n\nfun createSuLog(\n    fromUid: Int,\n    toUid: Int,\n    fromPid: Int,\n    command: String,\n    policy: Int,\n    target: Int,\n    context: String,\n    gids: String,\n): SuLog {\n    return SuLog(\n        fromUid = fromUid,\n        toUid = toUid,\n        fromPid = fromPid,\n        packageName = \"[UID] $fromUid\",\n        appName = \"[UID] $fromUid\",\n        command = command,\n        action = policy,\n        target = target,\n        context = context,\n        gids = gids,\n    )\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/model/su/SuPolicy.kt",
    "content": "package com.topjohnwu.magisk.core.model.su\n\nimport com.topjohnwu.magisk.core.data.magiskdb.MagiskDB\n\nclass SuPolicy(\n    val uid: Int,\n    var policy: Int = QUERY,\n    var remain: Long = -1L,\n    var logging: Boolean = true,\n    var notification: Boolean = true,\n) {\n    companion object {\n        const val QUERY = 0\n        const val DENY = 1\n        const val ALLOW = 2\n        const val RESTRICT = 3\n    }\n\n    fun toMap(): MutableMap<String, Any> {\n        val until = if (remain <= 0) {\n            remain\n        } else {\n            MagiskDB.Literal(\"(strftime(\\\"%s\\\", \\\"now\\\") + $remain)\")\n        }\n        return mutableMapOf(\n            \"uid\" to uid,\n            \"policy\" to policy,\n            \"until\" to until,\n            \"logging\" to logging,\n            \"notification\" to notification\n        )\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/repository/DBConfig.kt",
    "content": "package com.topjohnwu.magisk.core.repository\n\nimport com.topjohnwu.magisk.core.data.magiskdb.SettingsDao\nimport com.topjohnwu.magisk.core.data.magiskdb.StringDao\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\ninterface DBConfig {\n    val settingsDB: SettingsDao\n    val stringDB: StringDao\n    val coroutineScope: CoroutineScope\n\n    fun dbSettings(\n        name: String,\n        default: Int\n    ) = IntDBProperty(name, default)\n\n    fun dbSettings(\n        name: String,\n        default: Boolean\n    ) = BoolDBProperty(name, default)\n\n    fun dbStrings(\n        name: String,\n        default: String,\n        sync: Boolean = false\n    ) = StringDBProperty(name, default, sync)\n\n}\n\nclass IntDBProperty(\n    private val name: String,\n    private val default: Int\n) : ReadWriteProperty<DBConfig, Int> {\n\n    var value: Int? = null\n\n    @Synchronized\n    override fun getValue(thisRef: DBConfig, property: KProperty<*>): Int {\n        if (value == null)\n            value = runBlocking { thisRef.settingsDB.fetch(name, default) }\n        return value as Int\n    }\n\n    override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Int) {\n        synchronized(this) {\n            this.value = value\n        }\n        thisRef.coroutineScope.launch {\n            thisRef.settingsDB.put(name, value)\n        }\n    }\n}\n\nopen class BoolDBProperty(\n    name: String,\n    default: Boolean\n) : ReadWriteProperty<DBConfig, Boolean> {\n\n    val base = IntDBProperty(name, if (default) 1 else 0)\n\n    override fun getValue(thisRef: DBConfig, property: KProperty<*>): Boolean =\n        base.getValue(thisRef, property) != 0\n\n    override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: Boolean) =\n        base.setValue(thisRef, property, if (value) 1 else 0)\n}\n\nclass StringDBProperty(\n    private val name: String,\n    private val default: String,\n    private val sync: Boolean\n) : ReadWriteProperty<DBConfig, String> {\n\n    private var value: String? = null\n\n    @Synchronized\n    override fun getValue(thisRef: DBConfig, property: KProperty<*>): String {\n        if (value == null)\n            value = runBlocking {\n                thisRef.stringDB.fetch(name, default)\n            }\n        return value!!\n    }\n\n    override fun setValue(thisRef: DBConfig, property: KProperty<*>, value: String) {\n        synchronized(this) {\n            this.value = value\n        }\n        if (value.isEmpty()) {\n            if (sync) {\n                runBlocking {\n                    thisRef.stringDB.delete(name)\n                }\n            } else {\n                thisRef.coroutineScope.launch {\n                    thisRef.stringDB.delete(name)\n                }\n            }\n        } else {\n            if (sync) {\n                runBlocking {\n                    thisRef.stringDB.put(name, value)\n                }\n            } else {\n                thisRef.coroutineScope.launch {\n                    thisRef.stringDB.put(name, value)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/repository/LogRepository.kt",
    "content": "package com.topjohnwu.magisk.core.repository\n\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.data.SuLogDao\nimport com.topjohnwu.magisk.core.ktx.await\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport com.topjohnwu.superuser.Shell\n\n\nclass LogRepository(\n    private val logDao: SuLogDao\n) {\n\n    suspend fun fetchSuLogs() = logDao.fetchAll()\n\n    suspend fun fetchMagiskLogs(): String {\n        val list = object : AbstractMutableList<String>() {\n            val buf = StringBuilder()\n            override val size get() = 0\n            override fun get(index: Int): String = \"\"\n            override fun removeAt(index: Int): String = \"\"\n            override fun set(index: Int, element: String): String = \"\"\n            override fun add(index: Int, element: String) {\n                if (element.isNotEmpty()) {\n                    buf.append(element)\n                    buf.append('\\n')\n                }\n            }\n        }\n        if (Info.env.isActive) {\n            Shell.cmd(\"cat ${Const.MAGISK_LOG} || logcat -d -s Magisk\").to(list).await()\n        } else {\n            Shell.cmd(\"logcat -d\").to(list).await()\n        }\n        return list.buf.toString()\n    }\n\n    suspend fun clearLogs() = logDao.deleteAll()\n\n    fun clearMagiskLogs(cb: (Shell.Result) -> Unit) =\n        Shell.cmd(\"echo -n > ${Const.MAGISK_LOG}\").submit(cb)\n\n    suspend fun insert(log: SuLog) = logDao.insert(log)\n\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/repository/NetworkService.kt",
    "content": "package com.topjohnwu.magisk.core.repository\n\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Config.Value.BETA_CHANNEL\nimport com.topjohnwu.magisk.core.Config.Value.CUSTOM_CHANNEL\nimport com.topjohnwu.magisk.core.Config.Value.DEBUG_CHANNEL\nimport com.topjohnwu.magisk.core.Config.Value.DEFAULT_CHANNEL\nimport com.topjohnwu.magisk.core.Config.Value.STABLE_CHANNEL\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.data.GithubApiServices\nimport com.topjohnwu.magisk.core.data.RawUrl\nimport com.topjohnwu.magisk.core.ktx.dateFormat\nimport com.topjohnwu.magisk.core.model.Release\nimport com.topjohnwu.magisk.core.model.ReleaseAssets\nimport com.topjohnwu.magisk.core.model.UpdateInfo\nimport retrofit2.HttpException\nimport timber.log.Timber\nimport java.io.IOException\n\nclass NetworkService(\n    private val raw: RawUrl,\n    private val api: GithubApiServices,\n) {\n    suspend fun fetchUpdate() = safe {\n        var info = when (Config.updateChannel) {\n            DEFAULT_CHANNEL -> if (BuildConfig.DEBUG) fetchDebugUpdate() else fetchStableUpdate()\n            STABLE_CHANNEL -> fetchStableUpdate()\n            BETA_CHANNEL -> fetchBetaUpdate()\n            DEBUG_CHANNEL -> fetchDebugUpdate()\n            CUSTOM_CHANNEL -> fetchCustomUpdate(Config.customChannelUrl)\n            else -> throw IllegalArgumentException()\n        }\n        if (info.versionCode < Info.env.versionCode &&\n            Config.updateChannel == DEFAULT_CHANNEL &&\n            !BuildConfig.DEBUG\n        ) {\n            Config.updateChannel = BETA_CHANNEL\n            info = fetchBetaUpdate()\n        }\n        info\n    }\n\n    suspend fun fetchUpdate(version: Int) = safe {\n        findRelease { it.versionCode == version }.asInfo()\n    }\n\n    // Keep going through all release pages until we find a match\n    private suspend inline fun findRelease(predicate: (Release) -> Boolean): Release? {\n        var page = 1\n        while (true) {\n            val response = api.fetchReleases(page = page)\n            val releases = response.body() ?: throw HttpException(response)\n            // Remove all non Magisk releases\n            releases.removeAll { it.tag[0] != 'v' && !it.tag.startsWith(\"canary\") }\n            // Make sure it's sorted correctly\n            releases.sortByDescending { it.createdTime }\n            releases.find(predicate)?.let { return it }\n            if (response.headers()[\"link\"]?.contains(\"rel=\\\"next\\\"\", ignoreCase = true) == true) {\n                page += 1\n            } else {\n                return null\n            }\n        }\n    }\n\n    private inline fun Release?.asInfo(\n        selector: (ReleaseAssets) -> Boolean = {\n            // Default selector picks the non-debug APK\n            it.name.run { endsWith(\".apk\") && !contains(\"debug\") }\n        }): UpdateInfo {\n        return if (this == null) UpdateInfo()\n        else if (tag[0] == 'v') asPublicInfo(selector)\n        else asCanaryInfo(selector)\n    }\n\n    private inline fun Release.asPublicInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {\n        val version = tag.drop(1)\n        val date = dateFormat.format(createdTime)\n        return UpdateInfo(\n            version = version,\n            versionCode = versionCode,\n            link = assets.find(selector)!!.url,\n            note = \"## $date $name\\n\\n$body\"\n        )\n    }\n\n    private inline fun Release.asCanaryInfo(selector: (ReleaseAssets) -> Boolean): UpdateInfo {\n        return UpdateInfo(\n            version = name.substring(8, 16),\n            versionCode = versionCode,\n            link = assets.find(selector)!!.url,\n            note = \"## $name\\n\\n$body\"\n        )\n    }\n\n    // Version number: debug == beta >= stable\n\n    // Find the latest non-prerelease\n    private suspend fun fetchStableUpdate() = api.fetchLatestRelease().asInfo()\n\n    // Find the latest release, regardless whether it's prerelease\n    private suspend fun fetchBetaUpdate() = findRelease { true }.asInfo()\n\n    private suspend fun fetchDebugUpdate() =\n        findRelease { true }.asInfo { it.name == \"app-debug.apk\" }\n\n    private suspend fun fetchCustomUpdate(url: String): UpdateInfo {\n        val info = raw.fetchUpdateJson(url).magisk\n        return info.let { it.copy(note = raw.fetchString(it.note)) }\n    }\n\n    private inline fun <T> safe(factory: () -> T): T? {\n        return try {\n            if (Info.isConnected.value == true)\n                factory()\n            else\n                null\n        } catch (e: Exception) {\n            Timber.e(e)\n            null\n        }\n    }\n\n    private inline fun <T> wrap(factory: () -> T): T {\n        return try {\n            factory()\n        } catch (e: HttpException) {\n            throw IOException(e)\n        }\n    }\n\n    // Fetch files\n    suspend fun fetchFile(url: String) = wrap { raw.fetchFile(url) }\n    suspend fun fetchString(url: String) = wrap { raw.fetchString(url) }\n    suspend fun fetchModuleJson(url: String) = wrap { raw.fetchModuleJson(url) }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/repository/PreferenceConfig.kt",
    "content": "package com.topjohnwu.magisk.core.repository\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport kotlin.properties.ReadWriteProperty\nimport kotlin.reflect.KProperty\n\ninterface PreferenceConfig {\n\n    val context: Context\n\n    val fileName: String\n        get() = \"${context.packageName}_preferences\"\n\n    val prefs: SharedPreferences\n        get() = context.getSharedPreferences(fileName, Context.MODE_PRIVATE)\n\n    fun preferenceStrInt(\n        name: String,\n        default: Int,\n        commit: Boolean = false\n    ) = object: ReadWriteProperty<PreferenceConfig, Int> {\n        val base = StringProperty(name, default.toString(), commit)\n        override fun getValue(thisRef: PreferenceConfig, property: KProperty<*>): Int =\n                base.getValue(thisRef, property).toInt()\n\n        override fun setValue(thisRef: PreferenceConfig, property: KProperty<*>, value: Int) =\n                base.setValue(thisRef, property, value.toString())\n    }\n\n    fun preference(\n        name: String,\n        default: Boolean,\n        commit: Boolean = false\n    ) = BooleanProperty(name, default, commit)\n\n    fun preference(\n        name: String,\n        default: Int,\n        commit: Boolean = false\n    ) = IntProperty(name, default, commit)\n\n    fun preference(\n        name: String,\n        default: String,\n        commit: Boolean = false\n    ) = StringProperty(name, default, commit)\n}\n\nabstract class PreferenceProperty {\n\n    fun SharedPreferences.Editor.put(name: String, value: Boolean) = putBoolean(name, value)\n    fun SharedPreferences.Editor.put(name: String, value: Float) = putFloat(name, value)\n    fun SharedPreferences.Editor.put(name: String, value: Int) = putInt(name, value)\n    fun SharedPreferences.Editor.put(name: String, value: Long) = putLong(name, value)\n    fun SharedPreferences.Editor.put(name: String, value: String) = putString(name, value)\n    fun SharedPreferences.Editor.put(name: String, value: Set<String>) = putStringSet(name, value)\n\n    fun SharedPreferences.get(name: String, value: Boolean) = getBoolean(name, value)\n    fun SharedPreferences.get(name: String, value: Float) = getFloat(name, value)\n    fun SharedPreferences.get(name: String, value: Int) = getInt(name, value)\n    fun SharedPreferences.get(name: String, value: Long) = getLong(name, value)\n    fun SharedPreferences.get(name: String, value: String) = getString(name, value) ?: value\n    fun SharedPreferences.get(name: String, value: Set<String>) = getStringSet(name, value) ?: value\n\n}\n\nclass BooleanProperty(\n    private val name: String,\n    private val default: Boolean,\n    private val commit: Boolean\n) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Boolean> {\n\n    override operator fun getValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>\n    ): Boolean {\n        val prefName = name.ifBlank { property.name }\n        return thisRef.prefs.get(prefName, default)\n    }\n\n    override operator fun setValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>,\n        value: Boolean\n    ) {\n        val prefName = name.ifBlank { property.name }\n        thisRef.prefs.edit(commit) { put(prefName, value) }\n    }\n}\n\nclass IntProperty(\n    private val name: String,\n    private val default: Int,\n    private val commit: Boolean\n) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, Int> {\n\n    override operator fun getValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>\n    ): Int {\n        val prefName = name.ifBlank { property.name }\n        return thisRef.prefs.get(prefName, default)\n    }\n\n    override operator fun setValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>,\n        value: Int\n    ) {\n        val prefName = name.ifBlank { property.name }\n        thisRef.prefs.edit(commit) { put(prefName, value) }\n    }\n}\n\nclass StringProperty(\n    private val name: String,\n    private val default: String,\n    private val commit: Boolean\n) : PreferenceProperty(), ReadWriteProperty<PreferenceConfig, String> {\n\n    override operator fun getValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>\n    ): String {\n        val prefName = name.ifBlank { property.name }\n        return thisRef.prefs.get(prefName, default)\n    }\n\n    override operator fun setValue(\n        thisRef: PreferenceConfig,\n        property: KProperty<*>,\n        value: String\n    ) {\n        val prefName = name.ifBlank { property.name }\n        thisRef.prefs.edit(commit) { put(prefName, value) }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/signing/ApkSignerV2.java",
    "content": "package com.topjohnwu.magisk.core.signing;\n\nimport java.nio.BufferUnderflowException;\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.security.DigestException;\nimport java.security.InvalidAlgorithmParameterException;\nimport java.security.InvalidKeyException;\nimport java.security.KeyFactory;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.Signature;\nimport java.security.SignatureException;\nimport java.security.cert.CertificateEncodingException;\nimport java.security.cert.X509Certificate;\nimport java.security.spec.AlgorithmParameterSpec;\nimport java.security.spec.InvalidKeySpecException;\nimport java.security.spec.MGF1ParameterSpec;\nimport java.security.spec.PSSParameterSpec;\nimport java.security.spec.X509EncodedKeySpec;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * APK Signature Scheme v2 signer.\n *\n * <p>APK Signature Scheme v2 is a whole-file signature scheme which aims to protect every single\n * bit of the APK, as opposed to the JAR Signature Scheme which protects only the names and\n * uncompressed contents of ZIP entries.\n */\npublic abstract class ApkSignerV2 {\n    /*\n     * The two main goals of APK Signature Scheme v2 are:\n     * 1. Detect any unauthorized modifications to the APK. This is achieved by making the signature\n     *    cover every byte of the APK being signed.\n     * 2. Enable much faster signature and integrity verification. This is achieved by requiring\n     *    only a minimal amount of APK parsing before the signature is verified, thus completely\n     *    bypassing ZIP entry decompression and by making integrity verification parallelizable by\n     *    employing a hash tree.\n     *\n     * The generated signature block is wrapped into an APK Signing Block and inserted into the\n     * original APK immediately before the start of ZIP Central Directory. This is to ensure that\n     * JAR and ZIP parsers continue to work on the signed APK. The APK Signing Block is designed for\n     * extensibility. For example, a future signature scheme could insert its signatures there as\n     * well. The contract of the APK Signing Block is that all contents outside of the block must be\n     * protected by signatures inside the block.\n     */\n\n    public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;\n    public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;\n    public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;\n    public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;\n    public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;\n    public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;\n    public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;\n    public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;\n\n    /**\n     * {@code .SF} file header section attribute indicating that the APK is signed not just with\n     * JAR signature scheme but also with APK Signature Scheme v2 or newer. This attribute\n     * facilitates v2 signature stripping detection.\n     *\n     * <p>The attribute contains a comma-separated set of signature scheme IDs.\n     */\n    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = \"X-Android-APK-Signed\";\n    public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = \"2\";\n\n    private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0;\n    private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1;\n\n    private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;\n\n    private static final byte[] APK_SIGNING_BLOCK_MAGIC =\n          new byte[] {\n              0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,\n              0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,\n          };\n    private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;\n\n    private ApkSignerV2() {}\n\n    /**\n     * Signer configuration.\n     */\n    public static final class SignerConfig {\n        /** Private key. */\n        public PrivateKey privateKey;\n\n        /**\n         * Certificates, with the first certificate containing the public key corresponding to\n         * {@link #privateKey}.\n         */\n        public List<X509Certificate> certificates;\n\n        /**\n         * List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants).\n         */\n        public List<Integer> signatureAlgorithms;\n    }\n\n    /**\n     * Signs the provided APK using APK Signature Scheme v2 and returns the signed APK as a list of\n     * consecutive chunks.\n     *\n     * <p>NOTE: To enable APK signature verifier to detect v2 signature stripping, header sections\n     * of META-INF/*.SF files of APK being signed must contain the\n     * {@code X-Android-APK-Signed: true} attribute.\n     *\n     * @param inputApk contents of the APK to be signed. The APK starts at the current position\n     *        of the buffer and ends at the limit of the buffer.\n     * @param signerConfigs signer configurations, one for each signer.\n     *\n     * @throws ApkParseException if the APK cannot be parsed.\n     * @throws InvalidKeyException if a signing key is not suitable for this signature scheme or\n     *         cannot be used in general.\n     * @throws SignatureException if an error occurs when computing digests of generating\n     *         signatures.\n     */\n    public static ByteBuffer[] sign(\n            ByteBuffer inputApk,\n            List<SignerConfig> signerConfigs)\n                    throws ApkParseException, InvalidKeyException, SignatureException {\n        // Slice/create a view in the inputApk to make sure that:\n        // 1. inputApk is what's between position and limit of the original inputApk, and\n        // 2. changes to position, limit, and byte order are not reflected in the original.\n        ByteBuffer originalInputApk = inputApk;\n        inputApk = originalInputApk.slice();\n        inputApk.order(ByteOrder.LITTLE_ENDIAN);\n\n        // Locate ZIP End of Central Directory (EoCD), Central Directory, and check that Central\n        // Directory is immediately followed by the ZIP End of Central Directory.\n        int eocdOffset = ZipUtils.findZipEndOfCentralDirectoryRecord(inputApk);\n        if (eocdOffset == -1) {\n            throw new ApkParseException(\"Failed to locate ZIP End of Central Directory\");\n        }\n        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(inputApk, eocdOffset)) {\n            throw new ApkParseException(\"ZIP64 format not supported\");\n        }\n        inputApk.position(eocdOffset);\n        long centralDirSizeLong = ZipUtils.getZipEocdCentralDirectorySizeBytes(inputApk);\n        if (centralDirSizeLong > Integer.MAX_VALUE) {\n            throw new ApkParseException(\n                    \"ZIP Central Directory size out of range: \" + centralDirSizeLong);\n        }\n        int centralDirSize = (int) centralDirSizeLong;\n        long centralDirOffsetLong = ZipUtils.getZipEocdCentralDirectoryOffset(inputApk);\n        if (centralDirOffsetLong > Integer.MAX_VALUE) {\n            throw new ApkParseException(\n                    \"ZIP Central Directory offset in file out of range: \" + centralDirOffsetLong);\n        }\n        int centralDirOffset = (int) centralDirOffsetLong;\n        int expectedEocdOffset = centralDirOffset + centralDirSize;\n        if (expectedEocdOffset < centralDirOffset) {\n            throw new ApkParseException(\n                    \"ZIP Central Directory extent too large. Offset: \" + centralDirOffset\n                            + \", size: \" + centralDirSize);\n        }\n        if (eocdOffset != expectedEocdOffset) {\n            throw new ApkParseException(\n                    \"ZIP Central Directory not immeiately followed by ZIP End of\"\n                            + \" Central Directory. CD end: \" + expectedEocdOffset\n                            + \", EoCD start: \" + eocdOffset);\n        }\n\n        // Create ByteBuffers holding the contents of everything before ZIP Central Directory,\n        // ZIP Central Directory, and ZIP End of Central Directory.\n        inputApk.clear();\n        ByteBuffer beforeCentralDir = getByteBuffer(inputApk, centralDirOffset);\n        ByteBuffer centralDir = getByteBuffer(inputApk, eocdOffset - centralDirOffset);\n        // Create a copy of End of Central Directory because we'll need modify its contents later.\n        byte[] eocdBytes = new byte[inputApk.remaining()];\n        inputApk.get(eocdBytes);\n        ByteBuffer eocd = ByteBuffer.wrap(eocdBytes);\n        eocd.order(inputApk.order());\n\n        // Figure which which digests to use for APK contents.\n        Set<Integer> contentDigestAlgorithms = new HashSet<>();\n        for (SignerConfig signerConfig : signerConfigs) {\n            for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {\n                contentDigestAlgorithms.add(\n                        getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm));\n            }\n        }\n\n        // Compute digests of APK contents.\n        Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest\n        try {\n            contentDigests =\n                    computeContentDigests(\n                            contentDigestAlgorithms,\n                            new ByteBuffer[] {beforeCentralDir, centralDir, eocd});\n        } catch (DigestException e) {\n            throw new SignatureException(\"Failed to compute digests of APK\", e);\n        }\n\n        // Sign the digests and wrap the signatures and signer info into an APK Signing Block.\n        ByteBuffer apkSigningBlock =\n                ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));\n\n        // Update Central Directory Offset in End of Central Directory Record. Central Directory\n        // follows the APK Signing Block and thus is shifted by the size of the APK Signing Block.\n        centralDirOffset += apkSigningBlock.remaining();\n        eocd.clear();\n        ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);\n\n        // Follow the Java NIO pattern for ByteBuffer whose contents have been consumed.\n        originalInputApk.position(originalInputApk.limit());\n\n        // Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the\n        // Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller.\n        // Contrary to the name, this does not clear the contents of these ByteBuffer.\n        beforeCentralDir.clear();\n        centralDir.clear();\n        eocd.clear();\n\n        // Insert APK Signing Block immediately before the ZIP Central Directory.\n        return new ByteBuffer[] {\n            beforeCentralDir,\n            apkSigningBlock,\n            centralDir,\n            eocd,\n        };\n    }\n\n    private static Map<Integer, byte[]> computeContentDigests(\n            Set<Integer> digestAlgorithms,\n            ByteBuffer[] contents) throws DigestException {\n        // For each digest algorithm the result is computed as follows:\n        // 1. Each segment of contents is split into consecutive chunks of 1 MB in size.\n        //    The final chunk will be shorter iff the length of segment is not a multiple of 1 MB.\n        //    No chunks are produced for empty (zero length) segments.\n        // 2. The digest of each chunk is computed over the concatenation of byte 0xa5, the chunk's\n        //    length in bytes (uint32 little-endian) and the chunk's contents.\n        // 3. The output digest is computed over the concatenation of the byte 0x5a, the number of\n        //    chunks (uint32 little-endian) and the concatenation of digests of chunks of all\n        //    segments in-order.\n\n        int chunkCount = 0;\n        for (ByteBuffer input : contents) {\n            chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);\n        }\n\n        final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());\n        for (int digestAlgorithm : digestAlgorithms) {\n            int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);\n            byte[] concatenationOfChunkCountAndChunkDigests =\n                    new byte[5 + chunkCount * digestOutputSizeBytes];\n            concatenationOfChunkCountAndChunkDigests[0] = 0x5a;\n            setUnsignedInt32LittleEngian(\n                    chunkCount, concatenationOfChunkCountAndChunkDigests, 1);\n            digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);\n        }\n\n        int chunkIndex = 0;\n        byte[] chunkContentPrefix = new byte[5];\n        chunkContentPrefix[0] = (byte) 0xa5;\n        // Optimization opportunity: digests of chunks can be computed in parallel.\n        for (ByteBuffer input : contents) {\n            while (input.hasRemaining()) {\n                int chunkSize =\n                        Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);\n                final ByteBuffer chunk = getByteBuffer(input, chunkSize);\n                for (int digestAlgorithm : digestAlgorithms) {\n                    String jcaAlgorithmName =\n                            getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);\n                    MessageDigest md;\n                    try {\n                        md = MessageDigest.getInstance(jcaAlgorithmName);\n                    } catch (NoSuchAlgorithmException e) {\n                        throw new DigestException(\n                                jcaAlgorithmName + \" MessageDigest not supported\", e);\n                    }\n                    // Reset position to 0 and limit to capacity. Position would've been modified\n                    // by the preceding iteration of this loop. NOTE: Contrary to the method name,\n                    // this does not modify the contents of the chunk.\n                    chunk.clear();\n                    setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);\n                    md.update(chunkContentPrefix);\n                    md.update(chunk);\n                    byte[] concatenationOfChunkCountAndChunkDigests =\n                            digestsOfChunks.get(digestAlgorithm);\n                    int expectedDigestSizeBytes =\n                            getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);\n                    int actualDigestSizeBytes =\n                            md.digest(\n                                    concatenationOfChunkCountAndChunkDigests,\n                                    5 + chunkIndex * expectedDigestSizeBytes,\n                                    expectedDigestSizeBytes);\n                    if (actualDigestSizeBytes != expectedDigestSizeBytes) {\n                        throw new DigestException(\n                                \"Unexpected output size of \" + md.getAlgorithm()\n                                        + \" digest: \" + actualDigestSizeBytes);\n                    }\n                }\n                chunkIndex++;\n            }\n        }\n\n        Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());\n        for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {\n            int digestAlgorithm = entry.getKey();\n            byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();\n            String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);\n            MessageDigest md;\n            try {\n                md = MessageDigest.getInstance(jcaAlgorithmName);\n            } catch (NoSuchAlgorithmException e) {\n                throw new DigestException(jcaAlgorithmName + \" MessageDigest not supported\", e);\n            }\n            result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));\n        }\n        return result;\n    }\n\n    private static int getChunkCount(int inputSize, int chunkSize) {\n        return (inputSize + chunkSize - 1) / chunkSize;\n    }\n\n    private static void setUnsignedInt32LittleEngian(int value, byte[] result, int offset) {\n        result[offset] = (byte) (value & 0xff);\n        result[offset + 1] = (byte) ((value >> 8) & 0xff);\n        result[offset + 2] = (byte) ((value >> 16) & 0xff);\n        result[offset + 3] = (byte) ((value >> 24) & 0xff);\n    }\n\n    private static byte[] generateApkSigningBlock(\n            List<SignerConfig> signerConfigs,\n            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {\n        byte[] apkSignatureSchemeV2Block =\n                generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);\n        return generateApkSigningBlock(apkSignatureSchemeV2Block);\n    }\n\n    private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {\n        // FORMAT:\n        // uint64:  size (excluding this field)\n        // repeated ID-value pairs:\n        //     uint64:           size (excluding this field)\n        //     uint32:           ID\n        //     (size - 4) bytes: value\n        // uint64:  size (same as the one above)\n        // uint128: magic\n\n        int resultSize =\n                8 // size\n                + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair\n                + 8 // size\n                + 16 // magic\n                ;\n        ByteBuffer result = ByteBuffer.allocate(resultSize);\n        result.order(ByteOrder.LITTLE_ENDIAN);\n        long blockSizeFieldValue = resultSize - 8;\n        result.putLong(blockSizeFieldValue);\n\n        long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;\n        result.putLong(pairSizeFieldValue);\n        result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);\n        result.put(apkSignatureSchemeV2Block);\n\n        result.putLong(blockSizeFieldValue);\n        result.put(APK_SIGNING_BLOCK_MAGIC);\n\n        return result.array();\n    }\n\n    private static byte[] generateApkSignatureSchemeV2Block(\n            List<SignerConfig> signerConfigs,\n            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {\n        // FORMAT:\n        // * length-prefixed sequence of length-prefixed signer blocks.\n\n        List<byte[]> signerBlocks = new ArrayList<>(signerConfigs.size());\n        int signerNumber = 0;\n        for (SignerConfig signerConfig : signerConfigs) {\n            signerNumber++;\n            byte[] signerBlock;\n            try {\n                signerBlock = generateSignerBlock(signerConfig, contentDigests);\n            } catch (InvalidKeyException e) {\n                throw new InvalidKeyException(\"Signer #\" + signerNumber + \" failed\", e);\n            } catch (SignatureException e) {\n                throw new SignatureException(\"Signer #\" + signerNumber + \" failed\", e);\n            }\n            signerBlocks.add(signerBlock);\n        }\n\n        return encodeAsSequenceOfLengthPrefixedElements(\n                new byte[][] {\n                    encodeAsSequenceOfLengthPrefixedElements(signerBlocks),\n                });\n    }\n\n    private static byte[] generateSignerBlock(\n            SignerConfig signerConfig,\n            Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {\n        if (signerConfig.certificates.isEmpty()) {\n            throw new SignatureException(\"No certificates configured for signer\");\n        }\n        PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();\n\n        byte[] encodedPublicKey = encodePublicKey(publicKey);\n\n        V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();\n        try {\n            signedData.certificates = encodeCertificates(signerConfig.certificates);\n        } catch (CertificateEncodingException e) {\n            throw new SignatureException(\"Failed to encode certificates\", e);\n        }\n\n        List<Pair<Integer, byte[]>> digests =\n                new ArrayList<>(signerConfig.signatureAlgorithms.size());\n        for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {\n            int contentDigestAlgorithm =\n                    getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm);\n            byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);\n            if (contentDigest == null) {\n                throw new RuntimeException(\n                        getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm)\n                        + \" content digest for \"\n                        + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm)\n                        + \" not computed\");\n            }\n            digests.add(Pair.create(signatureAlgorithm, contentDigest));\n        }\n        signedData.digests = digests;\n\n        V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();\n        // FORMAT:\n        // * length-prefixed sequence of length-prefixed digests:\n        //   * uint32: signature algorithm ID\n        //   * length-prefixed bytes: digest of contents\n        // * length-prefixed sequence of certificates:\n        //   * length-prefixed bytes: X.509 certificate (ASN.1 DER encoded).\n        // * length-prefixed sequence of length-prefixed additional attributes:\n        //   * uint32: ID\n        //   * (length - 4) bytes: value\n        signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {\n            encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),\n            encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),\n            // additional attributes\n            new byte[0],\n        });\n        signer.publicKey = encodedPublicKey;\n        signer.signatures = new ArrayList<>();\n        for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {\n            Pair<String, ? extends AlgorithmParameterSpec> signatureParams =\n                    getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm);\n            String jcaSignatureAlgorithm = signatureParams.getFirst();\n            AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond();\n            byte[] signatureBytes;\n            try {\n                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);\n                signature.initSign(signerConfig.privateKey);\n                if (jcaSignatureAlgorithmParams != null) {\n                    signature.setParameter(jcaSignatureAlgorithmParams);\n                }\n                signature.update(signer.signedData);\n                signatureBytes = signature.sign();\n            } catch (InvalidKeyException e) {\n                throw new InvalidKeyException(\"Failed sign using \" + jcaSignatureAlgorithm, e);\n            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException\n                    | SignatureException e) {\n                throw new SignatureException(\"Failed sign using \" + jcaSignatureAlgorithm, e);\n            }\n\n            try {\n                Signature signature = Signature.getInstance(jcaSignatureAlgorithm);\n                signature.initVerify(publicKey);\n                if (jcaSignatureAlgorithmParams != null) {\n                    signature.setParameter(jcaSignatureAlgorithmParams);\n                }\n                signature.update(signer.signedData);\n                if (!signature.verify(signatureBytes)) {\n                    throw new SignatureException(\"Signature did not verify\");\n                }\n            } catch (InvalidKeyException e) {\n                throw new InvalidKeyException(\"Failed to verify generated \" + jcaSignatureAlgorithm\n                        + \" signature using public key from certificate\", e);\n            } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException\n                    | SignatureException e) {\n                throw new SignatureException(\"Failed to verify generated \" + jcaSignatureAlgorithm\n                        + \" signature using public key from certificate\", e);\n            }\n\n            signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes));\n        }\n\n        // FORMAT:\n        // * length-prefixed signed data\n        // * length-prefixed sequence of length-prefixed signatures:\n        //   * uint32: signature algorithm ID\n        //   * length-prefixed bytes: signature of signed data\n        // * length-prefixed bytes: public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)\n        return encodeAsSequenceOfLengthPrefixedElements(\n                new byte[][] {\n                    signer.signedData,\n                    encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(\n                            signer.signatures),\n                    signer.publicKey,\n                });\n    }\n\n    private static final class V2SignatureSchemeBlock {\n        private static final class Signer {\n            public byte[] signedData;\n            public List<Pair<Integer, byte[]>> signatures;\n            public byte[] publicKey;\n        }\n\n        private static final class SignedData {\n            public List<Pair<Integer, byte[]>> digests;\n            public List<byte[]> certificates;\n        }\n    }\n\n    private static byte[] encodePublicKey(PublicKey publicKey) throws InvalidKeyException {\n        byte[] encodedPublicKey = null;\n        if (\"X.509\".equals(publicKey.getFormat())) {\n            encodedPublicKey = publicKey.getEncoded();\n        }\n        if (encodedPublicKey == null) {\n            try {\n                encodedPublicKey =\n                        KeyFactory.getInstance(publicKey.getAlgorithm())\n                                .getKeySpec(publicKey, X509EncodedKeySpec.class)\n                                .getEncoded();\n            } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {\n                throw new InvalidKeyException(\n                        \"Failed to obtain X.509 encoded form of public key \" + publicKey\n                                + \" of class \" + publicKey.getClass().getName(),\n                        e);\n            }\n        }\n        if ((encodedPublicKey == null) || (encodedPublicKey.length == 0)) {\n            throw new InvalidKeyException(\n                    \"Failed to obtain X.509 encoded form of public key \" + publicKey\n                            + \" of class \" + publicKey.getClass().getName());\n        }\n        return encodedPublicKey;\n    }\n\n    public static List<byte[]> encodeCertificates(List<X509Certificate> certificates)\n            throws CertificateEncodingException {\n        List<byte[]> result = new ArrayList<>();\n        for (X509Certificate certificate : certificates) {\n            result.add(certificate.getEncoded());\n        }\n        return result;\n    }\n\n    private static byte[] encodeAsSequenceOfLengthPrefixedElements(List<byte[]> sequence) {\n        return encodeAsSequenceOfLengthPrefixedElements(\n                sequence.toArray(new byte[sequence.size()][]));\n    }\n\n    private static byte[] encodeAsSequenceOfLengthPrefixedElements(byte[][] sequence) {\n        int payloadSize = 0;\n        for (byte[] element : sequence) {\n            payloadSize += 4 + element.length;\n        }\n        ByteBuffer result = ByteBuffer.allocate(payloadSize);\n        result.order(ByteOrder.LITTLE_ENDIAN);\n        for (byte[] element : sequence) {\n            result.putInt(element.length);\n            result.put(element);\n        }\n        return result.array();\n      }\n\n    private static byte[] encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(\n            List<Pair<Integer, byte[]>> sequence) {\n        int resultSize = 0;\n        for (Pair<Integer, byte[]> element : sequence) {\n            resultSize += 12 + element.getSecond().length;\n        }\n        ByteBuffer result = ByteBuffer.allocate(resultSize);\n        result.order(ByteOrder.LITTLE_ENDIAN);\n        for (Pair<Integer, byte[]> element : sequence) {\n            byte[] second = element.getSecond();\n            result.putInt(8 + second.length);\n            result.putInt(element.getFirst());\n            result.putInt(second.length);\n            result.put(second);\n        }\n        return result.array();\n    }\n\n    /**\n     * Relative <em>get</em> method for reading {@code size} number of bytes from the current\n     * position of this buffer.\n     *\n     * <p>This method reads the next {@code size} bytes at this buffer's current position,\n     * returning them as a {@code ByteBuffer} with start set to 0, limit and capacity set to\n     * {@code size}, byte order set to this buffer's byte order; and then increments the position by\n     * {@code size}.\n     */\n    private static ByteBuffer getByteBuffer(ByteBuffer source, int size) {\n        if (size < 0) {\n            throw new IllegalArgumentException(\"size: \" + size);\n        }\n        int originalLimit = source.limit();\n        int position = source.position();\n        int limit = position + size;\n        if ((limit < position) || (limit > originalLimit)) {\n            throw new BufferUnderflowException();\n        }\n        source.limit(limit);\n        try {\n            ByteBuffer result = source.slice();\n            result.order(source.order());\n            source.position(limit);\n            return result;\n        } finally {\n            source.limit(originalLimit);\n        }\n    }\n\n    private static Pair<String, ? extends AlgorithmParameterSpec>\n            getSignatureAlgorithmJcaSignatureAlgorithm(int sigAlgorithm) {\n        switch (sigAlgorithm) {\n            case SIGNATURE_RSA_PSS_WITH_SHA256:\n                return Pair.create(\n                        \"SHA256withRSA/PSS\",\n                        new PSSParameterSpec(\n                                \"SHA-256\", \"MGF1\", MGF1ParameterSpec.SHA256, 256 / 8, 1));\n            case SIGNATURE_RSA_PSS_WITH_SHA512:\n                return Pair.create(\n                        \"SHA512withRSA/PSS\",\n                        new PSSParameterSpec(\n                                \"SHA-512\", \"MGF1\", MGF1ParameterSpec.SHA512, 512 / 8, 1));\n            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:\n                return Pair.create(\"SHA256withRSA\", null);\n            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:\n                return Pair.create(\"SHA512withRSA\", null);\n            case SIGNATURE_ECDSA_WITH_SHA256:\n                return Pair.create(\"SHA256withECDSA\", null);\n            case SIGNATURE_ECDSA_WITH_SHA512:\n                return Pair.create(\"SHA512withECDSA\", null);\n            case SIGNATURE_DSA_WITH_SHA256:\n                return Pair.create(\"SHA256withDSA\", null);\n            case SIGNATURE_DSA_WITH_SHA512:\n                return Pair.create(\"SHA512withDSA\", null);\n            default:\n                throw new IllegalArgumentException(\n                        \"Unknown signature algorithm: 0x\"\n                                + Long.toHexString(sigAlgorithm & 0xffffffff));\n        }\n    }\n\n    private static int getSignatureAlgorithmContentDigestAlgorithm(int sigAlgorithm) {\n        switch (sigAlgorithm) {\n            case SIGNATURE_RSA_PSS_WITH_SHA256:\n            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:\n            case SIGNATURE_ECDSA_WITH_SHA256:\n            case SIGNATURE_DSA_WITH_SHA256:\n                return CONTENT_DIGEST_CHUNKED_SHA256;\n            case SIGNATURE_RSA_PSS_WITH_SHA512:\n            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:\n            case SIGNATURE_ECDSA_WITH_SHA512:\n            case SIGNATURE_DSA_WITH_SHA512:\n                return CONTENT_DIGEST_CHUNKED_SHA512;\n            default:\n                throw new IllegalArgumentException(\n                        \"Unknown signature algorithm: 0x\"\n                                + Long.toHexString(sigAlgorithm & 0xffffffff));\n        }\n    }\n\n    private static String getContentDigestAlgorithmJcaDigestAlgorithm(int digestAlgorithm) {\n        switch (digestAlgorithm) {\n            case CONTENT_DIGEST_CHUNKED_SHA256:\n                return \"SHA-256\";\n            case CONTENT_DIGEST_CHUNKED_SHA512:\n                return \"SHA-512\";\n            default:\n                throw new IllegalArgumentException(\n                        \"Unknown content digest algorithm: \" + digestAlgorithm);\n        }\n    }\n\n    private static int getContentDigestAlgorithmOutputSizeBytes(int digestAlgorithm) {\n        switch (digestAlgorithm) {\n            case CONTENT_DIGEST_CHUNKED_SHA256:\n                return 256 / 8;\n            case CONTENT_DIGEST_CHUNKED_SHA512:\n                return 512 / 8;\n            default:\n                throw new IllegalArgumentException(\n                        \"Unknown content digest algorithm: \" + digestAlgorithm);\n        }\n    }\n\n    /**\n     * Indicates that APK file could not be parsed.\n     */\n    public static class ApkParseException extends Exception {\n        private static final long serialVersionUID = 1L;\n\n        public ApkParseException(String message) {\n            super(message);\n        }\n\n        public ApkParseException(String message, Throwable cause) {\n            super(message, cause);\n        }\n    }\n\n    /**\n     * Pair of two elements.\n     */\n    private static class Pair<A, B> {\n        private final A mFirst;\n        private final B mSecond;\n\n        private Pair(A first, B second) {\n            mFirst = first;\n            mSecond = second;\n        }\n\n        public static <A, B> Pair<A, B> create(A first, B second) {\n            return new Pair<>(first, second);\n        }\n\n        public A getFirst() {\n            return mFirst;\n        }\n\n        public B getSecond() {\n            return mSecond;\n        }\n\n        @Override\n        public int hashCode() {\n            final int prime = 31;\n            int result = 1;\n            result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());\n            result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());\n            return result;\n        }\n\n        @Override\n        public boolean equals(Object obj) {\n            if (this == obj) {\n                return true;\n            }\n            if (obj == null) {\n                return false;\n            }\n            if (getClass() != obj.getClass()) {\n                return false;\n            }\n            @SuppressWarnings(\"rawtypes\")\n            Pair other = (Pair) obj;\n            if (mFirst == null) {\n                if (other.mFirst != null) {\n                    return false;\n                }\n            } else if (!mFirst.equals(other.mFirst)) {\n                return false;\n            }\n            if (mSecond == null) {\n                return other.mSecond == null;\n            } else return mSecond.equals(other.mSecond);\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/signing/ByteArrayStream.java",
    "content": "package com.topjohnwu.magisk.core.signing;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.nio.ByteBuffer;\n\npublic class ByteArrayStream extends ByteArrayOutputStream {\n\n    public synchronized void readFrom(InputStream is) {\n        readFrom(is, Integer.MAX_VALUE);\n    }\n\n    public synchronized void readFrom(InputStream is, int len) {\n        int read;\n        byte buffer[] = new byte[4096];\n        try {\n            while ((read = is.read(buffer, 0, Math.min(len, buffer.length))) > 0) {\n                write(buffer, 0, read);\n                len -= read;\n            }\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    public ByteArrayInputStream getInputStream() {\n        return new ByteArrayInputStream(buf, 0, count);\n    }\n\n    public ByteBuffer toByteBuffer() {\n        return ByteBuffer.wrap(buf, 0, count);\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/signing/JarMap.java",
    "content": "package com.topjohnwu.magisk.core.signing;\n\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.Collections;\nimport java.util.Enumeration;\nimport java.util.LinkedHashMap;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\nimport java.util.jar.JarInputStream;\nimport java.util.jar.Manifest;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\n\npublic abstract class JarMap implements Closeable {\n\n    LinkedHashMap<String, JarEntry> entryMap;\n\n    public static JarMap open(File file, boolean verify) throws IOException {\n        return new FileMap(file, verify, ZipFile.OPEN_READ);\n    }\n\n    public static JarMap open(InputStream is, boolean verify) throws IOException {\n        return new StreamMap(is, verify);\n    }\n\n    public File getFile() {\n        return null;\n    }\n\n    public abstract Manifest getManifest() throws IOException;\n\n    public InputStream getInputStream(ZipEntry ze) throws IOException {\n        JarMapEntry e = getMapEntry(ze.getName());\n        return e != null ? e.data.getInputStream() : null;\n    }\n\n    public OutputStream getOutputStream(ZipEntry ze) {\n        if (entryMap == null)\n            entryMap = new LinkedHashMap<>();\n        JarMapEntry e = new JarMapEntry(ze.getName());\n        entryMap.put(ze.getName(), e);\n        return e.data;\n    }\n\n    public byte[] getRawData(ZipEntry ze) throws IOException {\n        JarMapEntry e = getMapEntry(ze.getName());\n        return e != null ? e.data.toByteArray() : null;\n    }\n\n    public abstract Enumeration<JarEntry> entries();\n\n    public final ZipEntry getEntry(String name) {\n        return getJarEntry(name);\n    }\n\n    public JarEntry getJarEntry(String name) {\n        return getMapEntry(name);\n    }\n\n    JarMapEntry getMapEntry(String name) {\n        JarMapEntry e = null;\n        if (entryMap != null)\n            e = (JarMapEntry) entryMap.get(name);\n        return e;\n    }\n\n    private static class FileMap extends JarMap {\n\n        private JarFile jarFile;\n\n        FileMap(File file, boolean verify, int mode) throws IOException {\n            jarFile = new JarFile(file, verify, mode);\n        }\n\n        @Override\n        public File getFile() {\n            return new File(jarFile.getName());\n        }\n\n        @Override\n        public Manifest getManifest() throws IOException {\n            return jarFile.getManifest();\n        }\n\n        @Override\n        public InputStream getInputStream(ZipEntry ze) throws IOException {\n            InputStream is = super.getInputStream(ze);\n            return is != null ? is : jarFile.getInputStream(ze);\n        }\n\n        @Override\n        public byte[] getRawData(ZipEntry ze) throws IOException {\n            byte[] b = super.getRawData(ze);\n            if (b != null)\n                return b;\n            ByteArrayStream bytes = new ByteArrayStream();\n            bytes.readFrom(jarFile.getInputStream(ze));\n            return bytes.toByteArray();\n        }\n\n        @Override\n        public Enumeration<JarEntry> entries() {\n            return jarFile.entries();\n        }\n\n        @Override\n        public JarEntry getJarEntry(String name) {\n            JarEntry e = getMapEntry(name);\n            return e != null ? e : jarFile.getJarEntry(name);\n        }\n\n        @Override\n        public void close() throws IOException {\n            jarFile.close();\n        }\n    }\n\n    private static class StreamMap extends JarMap {\n\n        private JarInputStream jis;\n\n        StreamMap(InputStream is, boolean verify) throws IOException {\n            jis = new JarInputStream(is, verify);\n            entryMap = new LinkedHashMap<>();\n            JarEntry entry;\n            while ((entry = jis.getNextJarEntry()) != null) {\n                entryMap.put(entry.getName(), new JarMapEntry(entry, jis));\n            }\n        }\n\n        @Override\n        public Manifest getManifest() {\n            return jis.getManifest();\n        }\n\n        @Override\n        public Enumeration<JarEntry> entries() {\n            return Collections.enumeration(entryMap.values());\n        }\n\n        @Override\n        public void close() throws IOException {\n            jis.close();\n        }\n    }\n\n    private static class JarMapEntry extends JarEntry {\n\n        ByteArrayStream data;\n\n        JarMapEntry(JarEntry je, InputStream is) {\n            super(je);\n            data = new ByteArrayStream();\n            data.readFrom(is);\n        }\n\n        JarMapEntry(String s) {\n            super(s);\n            data = new ByteArrayStream();\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/signing/SignApk.java",
    "content": "package com.topjohnwu.magisk.core.signing;\n\nimport org.bouncycastle.asn1.ASN1Encoding;\nimport org.bouncycastle.asn1.ASN1InputStream;\nimport org.bouncycastle.asn1.ASN1OutputStream;\nimport org.bouncycastle.cert.jcajce.JcaCertStore;\nimport org.bouncycastle.cms.CMSException;\nimport org.bouncycastle.cms.CMSProcessableByteArray;\nimport org.bouncycastle.cms.CMSSignedData;\nimport org.bouncycastle.cms.CMSSignedDataGenerator;\nimport org.bouncycastle.cms.CMSTypedData;\nimport org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;\nimport org.bouncycastle.operator.ContentSigner;\nimport org.bouncycastle.operator.OperatorCreationException;\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;\nimport org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;\nimport org.bouncycastle.util.encoders.Base64;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.nio.ByteBuffer;\nimport java.security.DigestOutputStream;\nimport java.security.GeneralSecurityException;\nimport java.security.InvalidKeyException;\nimport java.security.MessageDigest;\nimport java.security.PrivateKey;\nimport java.security.PublicKey;\nimport java.security.cert.CertificateEncodingException;\nimport java.security.cert.X509Certificate;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.TimeZone;\nimport java.util.TreeMap;\nimport java.util.jar.Attributes;\nimport java.util.jar.JarEntry;\nimport java.util.jar.JarFile;\nimport java.util.jar.JarOutputStream;\nimport java.util.jar.Manifest;\nimport java.util.regex.Pattern;\n\n/*\n * Modified from AOSP\n * https://android.googlesource.com/platform/build/+/refs/tags/android-7.1.2_r39/tools/signapk/src/com/android/signapk/SignApk.java\n * */\n\npublic class SignApk {\n    private static final String CERT_SF_NAME = \"META-INF/CERT.SF\";\n    private static final String CERT_SIG_NAME = \"META-INF/CERT.%s\";\n    private static final String CERT_SF_MULTI_NAME = \"META-INF/CERT%d.SF\";\n    private static final String CERT_SIG_MULTI_NAME = \"META-INF/CERT%d.%s\";\n\n    // bitmasks for which hash algorithms we need the manifest to include.\n    private static final int USE_SHA1 = 1;\n    private static final int USE_SHA256 = 2;\n\n    /**\n     * Digest algorithm used when signing the APK using APK Signature Scheme v2.\n     */\n    private static final String APK_SIG_SCHEME_V2_DIGEST_ALGORITHM = \"SHA-256\";\n    // Files matching this pattern are not copied to the output.\n    private static final Pattern stripPattern =\n            Pattern.compile(\"^(META-INF/((.*)[.](SF|RSA|DSA|EC)|com/android/otacert))|(\" +\n                    Pattern.quote(JarFile.MANIFEST_NAME) + \")$\");\n\n    /**\n     * Return one of USE_SHA1 or USE_SHA256 according to the signature\n     * algorithm specified in the cert.\n     */\n    private static int getDigestAlgorithm(X509Certificate cert) {\n        String sigAlg = cert.getSigAlgName().toUpperCase(Locale.US);\n        if (\"SHA1WITHRSA\".equals(sigAlg) || \"MD5WITHRSA\".equals(sigAlg)) {\n            return USE_SHA1;\n        } else if (sigAlg.startsWith(\"SHA256WITH\")) {\n            return USE_SHA256;\n        } else {\n            throw new IllegalArgumentException(\"unsupported signature algorithm \\\"\" + sigAlg +\n                    \"\\\" in cert [\" + cert.getSubjectDN());\n        }\n    }\n\n    /**\n     * Returns the expected signature algorithm for this key type.\n     */\n    private static String getSignatureAlgorithm(X509Certificate cert) {\n        String keyType = cert.getPublicKey().getAlgorithm().toUpperCase(Locale.US);\n        if (\"RSA\".equalsIgnoreCase(keyType)) {\n            if (getDigestAlgorithm(cert) == USE_SHA256) {\n                return \"SHA256withRSA\";\n            } else {\n                return \"SHA1withRSA\";\n            }\n        } else if (\"EC\".equalsIgnoreCase(keyType)) {\n            return \"SHA256withECDSA\";\n        } else {\n            throw new IllegalArgumentException(\"unsupported key type: \" + keyType);\n        }\n    }\n\n    /**\n     * Add the hash(es) of every file to the manifest, creating it if\n     * necessary.\n     */\n    private static Manifest addDigestsToManifest(JarMap jar, int hashes)\n            throws IOException, GeneralSecurityException {\n        Manifest input = jar.getManifest();\n        Manifest output = new Manifest();\n        Attributes main = output.getMainAttributes();\n        if (input != null) {\n            main.putAll(input.getMainAttributes());\n        } else {\n            main.putValue(\"Manifest-Version\", \"1.0\");\n            main.putValue(\"Created-By\", \"1.0 (Android SignApk)\");\n        }\n\n        MessageDigest md_sha1 = null;\n        MessageDigest md_sha256 = null;\n        if ((hashes & USE_SHA1) != 0) {\n            md_sha1 = MessageDigest.getInstance(\"SHA1\");\n        }\n        if ((hashes & USE_SHA256) != 0) {\n            md_sha256 = MessageDigest.getInstance(\"SHA256\");\n        }\n\n        byte[] buffer = new byte[4096];\n        int num;\n\n        // We sort the input entries by name, and add them to the\n        // output manifest in sorted order.  We expect that the output\n        // map will be deterministic.\n\n        TreeMap<String, JarEntry> byName = new TreeMap<>();\n\n        for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {\n            JarEntry entry = e.nextElement();\n            byName.put(entry.getName(), entry);\n        }\n\n        for (JarEntry entry : byName.values()) {\n            String name = entry.getName();\n            if (!entry.isDirectory() && !stripPattern.matcher(name).matches()) {\n                InputStream data = jar.getInputStream(entry);\n                while ((num = data.read(buffer)) > 0) {\n                    if (md_sha1 != null) md_sha1.update(buffer, 0, num);\n                    if (md_sha256 != null) md_sha256.update(buffer, 0, num);\n                }\n\n                Attributes attr = null;\n                if (input != null) attr = input.getAttributes(name);\n                attr = attr != null ? new Attributes(attr) : new Attributes();\n                // Remove any previously computed digests from this entry's attributes.\n                for (Iterator<Object> i = attr.keySet().iterator(); i.hasNext(); ) {\n                    Object key = i.next();\n                    if (!(key instanceof Attributes.Name)) {\n                        continue;\n                    }\n                    String attributeNameLowerCase =\n                            key.toString().toLowerCase(Locale.US);\n                    if (attributeNameLowerCase.endsWith(\"-digest\")) {\n                        i.remove();\n                    }\n                }\n                // Add SHA-1 digest if requested\n                if (md_sha1 != null) {\n                    attr.putValue(\"SHA1-Digest\",\n                            new String(Base64.encode(md_sha1.digest()), \"ASCII\"));\n                }\n                // Add SHA-256 digest if requested\n                if (md_sha256 != null) {\n                    attr.putValue(\"SHA-256-Digest\",\n                            new String(Base64.encode(md_sha256.digest()), \"ASCII\"));\n                }\n                output.getEntries().put(name, attr);\n            }\n        }\n\n        return output;\n    }\n\n    /**\n     * Write a .SF file with a digest of the specified manifest.\n     */\n    private static void writeSignatureFile(Manifest manifest, OutputStream out,\n                                           int hash)\n            throws IOException, GeneralSecurityException {\n        Manifest sf = new Manifest();\n        Attributes main = sf.getMainAttributes();\n        main.putValue(\"Signature-Version\", \"1.0\");\n        main.putValue(\"Created-By\", \"1.0 (Android SignApk)\");\n        // Add APK Signature Scheme v2 signature stripping protection.\n        // This attribute indicates that this APK is supposed to have been signed using one or\n        // more APK-specific signature schemes in addition to the standard JAR signature scheme\n        // used by this code. APK signature verifier should reject the APK if it does not\n        // contain a signature for the signature scheme the verifier prefers out of this set.\n        main.putValue(\n                ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME,\n                ApkSignerV2.SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE);\n\n        MessageDigest md = MessageDigest.getInstance(hash == USE_SHA256 ? \"SHA256\" : \"SHA1\");\n        PrintStream print = new PrintStream(new DigestOutputStream(new ByteArrayOutputStream(), md),\n                true, \"UTF-8\");\n\n        // Digest of the entire manifest\n        manifest.write(print);\n        print.flush();\n        main.putValue(hash == USE_SHA256 ? \"SHA-256-Digest-Manifest\" : \"SHA1-Digest-Manifest\",\n                new String(Base64.encode(md.digest()), \"ASCII\"));\n\n        Map<String, Attributes> entries = manifest.getEntries();\n        for (Map.Entry<String, Attributes> entry : entries.entrySet()) {\n            // Digest of the manifest stanza for this entry.\n            print.print(\"Name: \" + entry.getKey() + \"\\r\\n\");\n            for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {\n                print.print(att.getKey() + \": \" + att.getValue() + \"\\r\\n\");\n            }\n            print.print(\"\\r\\n\");\n            print.flush();\n\n            Attributes sfAttr = new Attributes();\n            sfAttr.putValue(hash == USE_SHA256 ? \"SHA-256-Digest\" : \"SHA1-Digest\",\n                    new String(Base64.encode(md.digest()), \"ASCII\"));\n            sf.getEntries().put(entry.getKey(), sfAttr);\n        }\n\n        CountOutputStream cout = new CountOutputStream(out);\n        sf.write(cout);\n\n        // A bug in the java.util.jar implementation of Android platforms\n        // up to version 1.6 will cause a spurious IOException to be thrown\n        // if the length of the signature file is a multiple of 1024 bytes.\n        // As a workaround, add an extra CRLF in this case.\n        if ((cout.size() % 1024) == 0) {\n            cout.write('\\r');\n            cout.write('\\n');\n        }\n    }\n\n    /**\n     * Sign data and write the digital signature to 'out'.\n     */\n    private static void writeSignatureBlock(\n            CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, OutputStream out)\n            throws IOException,\n            CertificateEncodingException,\n            OperatorCreationException,\n            CMSException {\n        ArrayList<X509Certificate> certList = new ArrayList<>(1);\n        certList.add(publicKey);\n        JcaCertStore certs = new JcaCertStore(certList);\n\n        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();\n        ContentSigner signer = new JcaContentSignerBuilder(getSignatureAlgorithm(publicKey))\n                .build(privateKey);\n        gen.addSignerInfoGenerator(\n                new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build())\n                        .setDirectSignature(true)\n                        .build(signer, publicKey)\n        );\n        gen.addCertificates(certs);\n        CMSSignedData sigData = gen.generate(data, false);\n\n        try (ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded())) {\n            ASN1OutputStream dos = ASN1OutputStream.create(out, ASN1Encoding.DER);\n            dos.writeObject(asn1.readObject());\n        }\n    }\n\n    /**\n     * Copy all the files in a manifest from input to output.  We set\n     * the modification times in the output to a fixed time, so as to\n     * reduce variation in the output file and make incremental OTAs\n     * more efficient.\n     */\n    private static void copyFiles(Manifest manifest, JarMap in, JarOutputStream out,\n                                  long timestamp, int defaultAlignment) throws IOException {\n        byte[] buffer = new byte[4096];\n        int num;\n\n        Map<String, Attributes> entries = manifest.getEntries();\n        ArrayList<String> names = new ArrayList<>(entries.keySet());\n        Collections.sort(names);\n\n        boolean firstEntry = true;\n        long offset = 0L;\n\n        // We do the copy in two passes -- first copying all the\n        // entries that are STORED, then copying all the entries that\n        // have any other compression flag (which in practice means\n        // DEFLATED).  This groups all the stored entries together at\n        // the start of the file and makes it easier to do alignment\n        // on them (since only stored entries are aligned).\n\n        for (String name : names) {\n            JarEntry inEntry = in.getJarEntry(name);\n            JarEntry outEntry;\n            if (inEntry.getMethod() != JarEntry.STORED) continue;\n            // Preserve the STORED method of the input entry.\n            outEntry = new JarEntry(inEntry);\n            outEntry.setTime(timestamp);\n            // Discard comment and extra fields of this entry to\n            // simplify alignment logic below and for consistency with\n            // how compressed entries are handled later.\n            outEntry.setComment(null);\n            outEntry.setExtra(null);\n\n            // 'offset' is the offset into the file at which we expect\n            // the file data to begin.  This is the value we need to\n            // make a multiple of 'alignement'.\n            offset += JarFile.LOCHDR + outEntry.getName().length();\n            if (firstEntry) {\n                // The first entry in a jar file has an extra field of\n                // four bytes that you can't get rid of; any extra\n                // data you specify in the JarEntry is appended to\n                // these forced four bytes.  This is JAR_MAGIC in\n                // JarOutputStream; the bytes are 0xfeca0000.\n                offset += 4;\n                firstEntry = false;\n            }\n            int alignment = getStoredEntryDataAlignment(name, defaultAlignment);\n            if (alignment > 0 && (offset % alignment != 0)) {\n                // Set the \"extra data\" of the entry to between 1 and\n                // alignment-1 bytes, to make the file data begin at\n                // an aligned offset.\n                int needed = alignment - (int) (offset % alignment);\n                outEntry.setExtra(new byte[needed]);\n                offset += needed;\n            }\n\n            out.putNextEntry(outEntry);\n\n            InputStream data = in.getInputStream(inEntry);\n            while ((num = data.read(buffer)) > 0) {\n                out.write(buffer, 0, num);\n                offset += num;\n            }\n            out.flush();\n        }\n\n        // Copy all the non-STORED entries.  We don't attempt to\n        // maintain the 'offset' variable past this point; we don't do\n        // alignment on these entries.\n\n        for (String name : names) {\n            JarEntry inEntry = in.getJarEntry(name);\n            JarEntry outEntry;\n            if (inEntry.getMethod() == JarEntry.STORED) continue;\n            // Create a new entry so that the compressed len is recomputed.\n            outEntry = new JarEntry(name);\n            outEntry.setTime(timestamp);\n            out.putNextEntry(outEntry);\n\n            InputStream data = in.getInputStream(inEntry);\n            while ((num = data.read(buffer)) > 0) {\n                out.write(buffer, 0, num);\n            }\n            out.flush();\n        }\n    }\n\n    /**\n     * Returns the multiple (in bytes) at which the provided {@code STORED} entry's data must start\n     * relative to start of file or {@code 0} if alignment of this entry's data is not important.\n     */\n    private static int getStoredEntryDataAlignment(String entryName, int defaultAlignment) {\n        if (defaultAlignment <= 0) {\n            return 0;\n        }\n\n        if (entryName.endsWith(\".so\")) {\n            // Align .so contents to memory page boundary to enable memory-mapped\n            // execution.\n            return 4096;\n        } else {\n            return defaultAlignment;\n        }\n    }\n\n    private static void signFile(Manifest manifest,\n                                 X509Certificate[] publicKey, PrivateKey[] privateKey,\n                                 long timestamp, JarOutputStream outputJar) throws Exception {\n        // MANIFEST.MF\n        JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);\n        je.setTime(timestamp);\n        outputJar.putNextEntry(je);\n        manifest.write(outputJar);\n\n        int numKeys = publicKey.length;\n        for (int k = 0; k < numKeys; ++k) {\n            // CERT.SF / CERT#.SF\n            je = new JarEntry(numKeys == 1 ? CERT_SF_NAME :\n                    (String.format(Locale.US, CERT_SF_MULTI_NAME, k)));\n            je.setTime(timestamp);\n            outputJar.putNextEntry(je);\n            ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            writeSignatureFile(manifest, baos, getDigestAlgorithm(publicKey[k]));\n            byte[] signedData = baos.toByteArray();\n            outputJar.write(signedData);\n\n            // CERT.{EC,RSA} / CERT#.{EC,RSA}\n            final String keyType = publicKey[k].getPublicKey().getAlgorithm();\n            je = new JarEntry(numKeys == 1 ? (String.format(CERT_SIG_NAME, keyType)) :\n                    (String.format(Locale.US, CERT_SIG_MULTI_NAME, k, keyType)));\n            je.setTime(timestamp);\n            outputJar.putNextEntry(je);\n            writeSignatureBlock(new CMSProcessableByteArray(signedData),\n                    publicKey[k], privateKey[k], outputJar);\n        }\n    }\n\n    /**\n     * Converts the provided lists of private keys, their X.509 certificates, and digest algorithms\n     * into a list of APK Signature Scheme v2 {@code SignerConfig} instances.\n     */\n    private static List<ApkSignerV2.SignerConfig> createV2SignerConfigs(\n            PrivateKey[] privateKeys, X509Certificate[] certificates, String[] digestAlgorithms)\n            throws InvalidKeyException {\n        if (privateKeys.length != certificates.length) {\n            throw new IllegalArgumentException(\n                    \"The number of private keys must match the number of certificates: \"\n                            + privateKeys.length + \" vs\" + certificates.length);\n        }\n        List<ApkSignerV2.SignerConfig> result = new ArrayList<>(privateKeys.length);\n        for (int i = 0; i < privateKeys.length; i++) {\n            PrivateKey privateKey = privateKeys[i];\n            X509Certificate certificate = certificates[i];\n            PublicKey publicKey = certificate.getPublicKey();\n            String keyAlgorithm = privateKey.getAlgorithm();\n            if (!keyAlgorithm.equalsIgnoreCase(publicKey.getAlgorithm())) {\n                throw new InvalidKeyException(\n                        \"Key algorithm of private key #\" + (i + 1) + \" does not match key\"\n                                + \" algorithm of public key #\" + (i + 1) + \": \" + keyAlgorithm\n                                + \" vs \" + publicKey.getAlgorithm());\n            }\n            ApkSignerV2.SignerConfig signerConfig = new ApkSignerV2.SignerConfig();\n            signerConfig.privateKey = privateKey;\n            signerConfig.certificates = Collections.singletonList(certificate);\n            List<Integer> signatureAlgorithms = new ArrayList<>(digestAlgorithms.length);\n            for (String digestAlgorithm : digestAlgorithms) {\n                try {\n                    signatureAlgorithms.add(getV2SignatureAlgorithm(keyAlgorithm, digestAlgorithm));\n                } catch (IllegalArgumentException e) {\n                    throw new InvalidKeyException(\n                            \"Unsupported key and digest algorithm combination for signer #\"\n                                    + (i + 1), e);\n                }\n            }\n            signerConfig.signatureAlgorithms = signatureAlgorithms;\n            result.add(signerConfig);\n        }\n        return result;\n    }\n\n    private static int getV2SignatureAlgorithm(String keyAlgorithm, String digestAlgorithm) {\n        if (\"SHA-256\".equalsIgnoreCase(digestAlgorithm)) {\n            if (\"RSA\".equalsIgnoreCase(keyAlgorithm)) {\n                // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee\n                // deterministic signatures which make life easier for OTA updates (fewer files\n                // changed when deterministic signature schemes are used).\n                return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256;\n            } else if (\"EC\".equalsIgnoreCase(keyAlgorithm)) {\n                return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA256;\n            } else if (\"DSA\".equalsIgnoreCase(keyAlgorithm)) {\n                return ApkSignerV2.SIGNATURE_DSA_WITH_SHA256;\n            } else {\n                throw new IllegalArgumentException(\"Unsupported key algorithm: \" + keyAlgorithm);\n            }\n        } else if (\"SHA-512\".equalsIgnoreCase(digestAlgorithm)) {\n            if (\"RSA\".equalsIgnoreCase(keyAlgorithm)) {\n                // Use RSASSA-PKCS1-v1_5 signature scheme instead of RSASSA-PSS to guarantee\n                // deterministic signatures which make life easier for OTA updates (fewer files\n                // changed when deterministic signature schemes are used).\n                return ApkSignerV2.SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512;\n            } else if (\"EC\".equalsIgnoreCase(keyAlgorithm)) {\n                return ApkSignerV2.SIGNATURE_ECDSA_WITH_SHA512;\n            } else if (\"DSA\".equalsIgnoreCase(keyAlgorithm)) {\n                return ApkSignerV2.SIGNATURE_DSA_WITH_SHA512;\n            } else {\n                throw new IllegalArgumentException(\"Unsupported key algorithm: \" + keyAlgorithm);\n            }\n        } else {\n            throw new IllegalArgumentException(\"Unsupported digest algorithm: \" + digestAlgorithm);\n        }\n    }\n\n    public static void sign(X509Certificate cert, PrivateKey key,\n                            JarMap inputJar, OutputStream outputStream) throws Exception {\n        int alignment = 4;\n        int hashes = 0;\n\n        X509Certificate[] publicKey = new X509Certificate[1];\n        publicKey[0] = cert;\n        hashes |= getDigestAlgorithm(publicKey[0]);\n\n        // Set all ZIP file timestamps to Jan 1 2009 00:00:00.\n        long timestamp = 1230768000000L;\n        // The Java ZipEntry API we're using converts milliseconds since epoch into MS-DOS\n        // timestamp using the current timezone. We thus adjust the milliseconds since epoch\n        // value to end up with MS-DOS timestamp of Jan 1 2009 00:00:00.\n        timestamp -= TimeZone.getDefault().getOffset(timestamp);\n\n        PrivateKey[] privateKey = new PrivateKey[1];\n        privateKey[0] = key;\n\n        // Generate, in memory, an APK signed using standard JAR Signature Scheme.\n        ByteArrayStream v1SignedApkBuf = new ByteArrayStream();\n        JarOutputStream outputJar = new JarOutputStream(v1SignedApkBuf);\n        // Use maximum compression for compressed entries because the APK lives forever on\n        // the system partition.\n        outputJar.setLevel(9);\n        Manifest manifest = addDigestsToManifest(inputJar, hashes);\n        copyFiles(manifest, inputJar, outputJar, timestamp, alignment);\n        signFile(manifest, publicKey, privateKey, timestamp, outputJar);\n        outputJar.close();\n        ByteBuffer v1SignedApk = v1SignedApkBuf.toByteBuffer();\n\n        ByteBuffer[] outputChunks;\n        List<ApkSignerV2.SignerConfig> signerConfigs = createV2SignerConfigs(privateKey, publicKey,\n                new String[]{APK_SIG_SCHEME_V2_DIGEST_ALGORITHM});\n        outputChunks = ApkSignerV2.sign(v1SignedApk, signerConfigs);\n\n        // This assumes outputChunks are array-backed. To avoid this assumption, the\n        // code could be rewritten to use FileChannel.\n        for (ByteBuffer outputChunk : outputChunks) {\n            outputStream.write(outputChunk.array(),\n                    outputChunk.arrayOffset() + outputChunk.position(), outputChunk.remaining());\n            outputChunk.position(outputChunk.limit());\n        }\n    }\n\n    /**\n     * Write to another stream and track how many bytes have been\n     * written.\n     */\n    private static class CountOutputStream extends FilterOutputStream {\n        private int mCount;\n\n        public CountOutputStream(OutputStream out) {\n            super(out);\n            mCount = 0;\n        }\n\n        @Override\n        public void write(int b) throws IOException {\n            super.write(b);\n            mCount++;\n        }\n\n        @Override\n        public void write(byte[] b, int off, int len) throws IOException {\n            super.write(b, off, len);\n            mCount += len;\n        }\n\n        public int size() {\n            return mCount;\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/signing/ZipUtils.java",
    "content": "package com.topjohnwu.magisk.core.signing;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\n\n/**\n * Assorted ZIP format helpers.\n *\n * <p>NOTE: Most helper methods operating on {@code ByteBuffer} instances expect that the byte\n * order of these buffers is little-endian.\n */\npublic abstract class ZipUtils {\n\n    private static final int ZIP_EOCD_REC_MIN_SIZE = 22;\n    private static final int ZIP_EOCD_REC_SIG = 0x06054b50;\n    private static final int ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET = 12;\n    private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16;\n    private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20;\n\n    private static final int ZIP64_EOCD_LOCATOR_SIZE = 20;\n    private static final int ZIP64_EOCD_LOCATOR_SIG = 0x07064b50;\n\n    private static final int UINT16_MAX_VALUE = 0xffff;\n\n    private ZipUtils() {\n    }\n\n    /**\n     * Returns the position at which ZIP End of Central Directory record starts in the provided\n     * buffer or {@code -1} if the record is not present.\n     *\n     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.\n     */\n    public static int findZipEndOfCentralDirectoryRecord(ByteBuffer zipContents) {\n        assertByteOrderLittleEndian(zipContents);\n\n        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.\n        // The record can be identified by its 4-byte signature/magic which is located at the very\n        // beginning of the record. A complication is that the record is variable-length because of\n        // the comment field.\n        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from\n        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check\n        // the candidate record's comment length is such that the remainder of the record takes up\n        // exactly the remaining bytes in the buffer. The search is bounded because the maximum\n        // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.\n\n        int archiveSize = zipContents.capacity();\n        if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) {\n            return -1;\n        }\n        int maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE);\n        int eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE;\n        for (int expectedCommentLength = 0; expectedCommentLength < maxCommentLength; expectedCommentLength++) {\n            int eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength;\n            if (zipContents.getInt(eocdStartPos) == ZIP_EOCD_REC_SIG) {\n                int actualCommentLength = getUnsignedInt16(zipContents, eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET);\n                if (actualCommentLength == expectedCommentLength) {\n                    return eocdStartPos;\n                }\n            }\n        }\n\n        return -1;\n    }\n\n    /**\n     * Returns {@code true} if the provided buffer contains a ZIP64 End of Central Directory\n     * Locator.\n     *\n     * <p>NOTE: Byte order of {@code zipContents} must be little-endian.\n     */\n    public static boolean isZip64EndOfCentralDirectoryLocatorPresent(ByteBuffer zipContents, int zipEndOfCentralDirectoryPosition) {\n        assertByteOrderLittleEndian(zipContents);\n\n        // ZIP64 End of Central Directory Locator immediately precedes the ZIP End of Central\n        // Directory Record.\n\n        int locatorPosition = zipEndOfCentralDirectoryPosition - ZIP64_EOCD_LOCATOR_SIZE;\n        if (locatorPosition < 0) {\n            return false;\n        }\n\n        return zipContents.getInt(locatorPosition) == ZIP64_EOCD_LOCATOR_SIG;\n    }\n\n    /**\n     * Returns the offset of the start of the ZIP Central Directory in the archive.\n     *\n     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.\n     */\n    public static long getZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory) {\n        assertByteOrderLittleEndian(zipEndOfCentralDirectory);\n        return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET);\n    }\n\n    /**\n     * Sets the offset of the start of the ZIP Central Directory in the archive.\n     *\n     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.\n     */\n    public static void setZipEocdCentralDirectoryOffset(ByteBuffer zipEndOfCentralDirectory, long offset) {\n        assertByteOrderLittleEndian(zipEndOfCentralDirectory);\n        setUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET, offset);\n    }\n\n    /**\n     * Returns the size (in bytes) of the ZIP Central Directory.\n     *\n     * <p>NOTE: Byte order of {@code zipEndOfCentralDirectory} must be little-endian.\n     */\n    public static long getZipEocdCentralDirectorySizeBytes(ByteBuffer zipEndOfCentralDirectory) {\n        assertByteOrderLittleEndian(zipEndOfCentralDirectory);\n        return getUnsignedInt32(zipEndOfCentralDirectory, zipEndOfCentralDirectory.position() + ZIP_EOCD_CENTRAL_DIR_SIZE_FIELD_OFFSET);\n    }\n\n    private static void assertByteOrderLittleEndian(ByteBuffer buffer) {\n        if (buffer.order() != ByteOrder.LITTLE_ENDIAN) {\n            throw new IllegalArgumentException(\"ByteBuffer byte order must be little endian\");\n        }\n    }\n\n    private static int getUnsignedInt16(ByteBuffer buffer, int offset) {\n        return buffer.getShort(offset) & 0xffff;\n    }\n\n    private static long getUnsignedInt32(ByteBuffer buffer, int offset) {\n        return buffer.getInt(offset) & 0xffffffffL;\n    }\n\n    private static void setUnsignedInt32(ByteBuffer buffer, int offset, long value) {\n        if ((value < 0) || (value > 0xffffffffL)) {\n            throw new IllegalArgumentException(\"uint32 value of out range: \" + value);\n        }\n        buffer.putInt(buffer.position() + offset, (int) value);\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/su/SuCallbackHandler.kt",
    "content": "package com.topjohnwu.magisk.core.su\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.widget.Toast\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.ktx.getPackageInfo\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.core.model.su.createSuLog\nimport com.topjohnwu.magisk.view.Notifications\nimport kotlinx.coroutines.runBlocking\nimport timber.log.Timber\n\nobject SuCallbackHandler {\n\n    const val REQUEST = \"request\"\n    const val LOG = \"log\"\n    const val NOTIFY = \"notify\"\n\n    fun run(context: Context, action: String?, data: Bundle?) {\n        data ?: return\n\n        // Debug messages\n        if (BuildConfig.DEBUG) {\n            Timber.d(action)\n            data.let { bundle ->\n                bundle.keySet().forEach {\n                    Timber.d(\"[%s]=[%s]\", it, bundle[it])\n                }\n            }\n        }\n\n        when (action) {\n            LOG -> handleLogging(context, data)\n            NOTIFY -> handleNotify(context, data)\n        }\n    }\n\n    // https://android.googlesource.com/platform/frameworks/base/+/547bf5487d52b93c9fe183aa6d56459c170b17a4\n    private fun Bundle.getIntComp(key: String, defaultValue: Int): Int {\n        val value = get(key) ?: return defaultValue\n        return when (value) {\n            is Int -> value\n            is Long -> value.toInt()\n            else -> defaultValue\n        }\n    }\n\n    private fun handleLogging(context: Context, data: Bundle) {\n        val fromUid = data.getIntComp(\"from.uid\", -1)\n        val notify = data.getBoolean(\"notify\", true)\n        val policy = data.getIntComp(\"policy\", SuPolicy.ALLOW)\n        val toUid = data.getIntComp(\"to.uid\", -1)\n        val pid = data.getIntComp(\"pid\", -1)\n        val command = data.getString(\"command\", \"\")\n        val target = data.getIntComp(\"target\", -1)\n        val seContext = data.getString(\"context\", \"\")\n        val gids = data.getString(\"gids\", \"\")\n\n        val pm = context.packageManager\n\n        val log = runCatching {\n            pm.getPackageInfo(fromUid, pid)?.applicationInfo?.let {\n                pm.createSuLog(it, toUid, pid, command, policy, target, seContext, gids)\n            }\n        }.getOrNull() ?: createSuLog(fromUid, toUid, pid, command, policy, target, seContext, gids)\n\n        runBlocking { ServiceLocator.logRepo.insert(log) }\n\n        if (notify || Config.suNotification == Config.Value.NOTIFICATION_STATUS_BAR)\n            notify(context, log.action >= SuPolicy.ALLOW, log.appName)\n        SuEvents.notifyLogUpdated()\n        SuEvents.notifyPolicyChanged()\n    }\n\n    private fun handleNotify(context: Context, data: Bundle) {\n        val uid = data.getIntComp(\"from.uid\", -1)\n        val pid = data.getIntComp(\"pid\", -1)\n        val policy = data.getIntComp(\"policy\", SuPolicy.ALLOW)\n\n        val pm = context.packageManager\n\n        val appName = runCatching {\n            pm.getPackageInfo(uid, pid)?.applicationInfo?.getLabel(pm)\n        }.getOrNull() ?: \"[UID] $uid\"\n\n        notify(context, policy >= SuPolicy.ALLOW, appName)\n        SuEvents.notifyPolicyChanged()\n    }\n\n    fun notify(granted: Boolean, appName: String) {\n        when (Config.suNotification) {\n            Config.Value.NOTIFICATION_TOAST -> {\n                val resId = if (granted) R.string.su_allow_toast else R.string.su_deny_toast\n                AppContext.toast(AppContext.getString(resId, appName), Toast.LENGTH_SHORT)\n            }\n            Config.Value.NOTIFICATION_STATUS_BAR -> {\n                Notifications.suNotification(granted, appName)\n            }\n        }\n    }\n\n    private fun notify(context: Context, granted: Boolean, appName: String) {\n        when (Config.suNotification) {\n            Config.Value.NOTIFICATION_TOAST -> {\n                val resId = if (granted) R.string.su_allow_toast else R.string.su_deny_toast\n                context.toast(context.getString(resId, appName), Toast.LENGTH_SHORT)\n            }\n            Config.Value.NOTIFICATION_STATUS_BAR -> {\n                Notifications.suNotification(granted, appName)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/su/SuEvents.kt",
    "content": "package com.topjohnwu.magisk.core.su\n\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.asSharedFlow\n\nobject SuEvents {\n    private val _policyChanged = MutableSharedFlow<Unit>(extraBufferCapacity = 64)\n    val policyChanged = _policyChanged.asSharedFlow()\n\n    private val _logUpdated = MutableSharedFlow<Unit>(extraBufferCapacity = 64)\n    val logUpdated = _logUpdated.asSharedFlow()\n\n    fun notifyPolicyChanged() {\n        _policyChanged.tryEmit(Unit)\n    }\n\n    fun notifyLogUpdated() {\n        _logUpdated.tryEmit(Unit)\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/su/SuRequestHandler.kt",
    "content": "package com.topjohnwu.magisk.core.su\n\nimport android.content.Intent\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.data.magiskdb.PolicyDao\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.ktx.getLabel\nimport com.topjohnwu.magisk.core.ktx.getPackageInfo\nimport com.topjohnwu.magisk.core.model.su.SuLog\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport com.topjohnwu.magisk.view.Notifications\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.DataOutputStream\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.util.concurrent.TimeUnit\n\nclass SuRequestHandler(\n    val pm: PackageManager,\n    private val policyDB: PolicyDao\n) {\n\n    private lateinit var output: File\n    private lateinit var policy: SuPolicy\n    private var pid: Int = -1\n    lateinit var pkgInfo: PackageInfo\n        private set\n\n    // Return true to indicate undetermined policy, require user interaction\n    suspend fun start(intent: Intent): Boolean {\n        if (!init(intent))\n            return false\n\n        // Never allow com.topjohnwu.magisk (could be malware)\n        if (pkgInfo.packageName == BuildConfig.APP_PACKAGE_NAME) {\n            Shell.cmd(\"(pm uninstall ${BuildConfig.APP_PACKAGE_NAME} >/dev/null 2>&1)&\").exec()\n            return false\n        }\n\n        when (Config.suAutoResponse) {\n            Config.Value.SU_AUTO_DENY -> {\n                respond(SuPolicy.DENY, 0)\n                return false\n            }\n            Config.Value.SU_AUTO_ALLOW -> {\n                respond(SuPolicy.ALLOW, 0)\n                return false\n            }\n        }\n\n        return true\n    }\n\n    private suspend fun init(intent: Intent): Boolean {\n        val uid = intent.getIntExtra(\"uid\", -1)\n        pid = intent.getIntExtra(\"pid\", -1)\n        val fifo = intent.getStringExtra(\"fifo\")\n        if (uid <= 0 || pid <= 0 || fifo == null) {\n            Timber.e(\"Unexpected extras: uid=[${uid}], pid=[${pid}], fifo=[${fifo}]\")\n            return false\n        }\n        output = File(fifo)\n        policy = policyDB.fetch(uid) ?: SuPolicy(uid)\n        try {\n            pkgInfo = pm.getPackageInfo(uid, pid) ?: PackageInfo().apply {\n                val name = pm.getNameForUid(uid) ?: throw PackageManager.NameNotFoundException()\n                // We only fill in sharedUserId and leave other fields uninitialized\n                sharedUserId = name.split(\":\")[0]\n            }\n        } catch (e: PackageManager.NameNotFoundException) {\n            Timber.e(e)\n            respond(SuPolicy.DENY, -1)\n            return false\n        }\n        if (!output.canWrite()) {\n            Timber.e(\"Cannot write to $output\")\n            return false\n        }\n        return true\n    }\n\n    suspend fun respond(action: Int, time: Long) {\n        if (action == SuPolicy.ALLOW && Config.suRestrict) {\n            policy.policy = SuPolicy.RESTRICT\n        } else {\n            policy.policy = action\n        }\n        if (time >= 0) {\n            policy.remain = TimeUnit.MINUTES.toSeconds(time)\n        } else {\n            policy.remain = time\n        }\n\n        withContext(Dispatchers.IO) {\n            try {\n                DataOutputStream(FileOutputStream(output)).use {\n                    it.writeInt(policy.policy)\n                    it.flush()\n                }\n            } catch (e: IOException) {\n                Timber.e(e)\n            }\n            if (time >= 0) {\n                policyDB.update(policy)\n\n                val appInfo = pkgInfo.applicationInfo\n                val appName = appInfo?.getLabel(pm)\n                    ?: pkgInfo.sharedUserId ?: \"[UID] ${policy.uid}\"\n                val packageName = appInfo?.let { pm.getNameForUid(it.uid) }\n                    ?: pkgInfo.sharedUserId ?: \"[UID] ${policy.uid}\"\n\n                val log = SuLog(\n                    fromUid = policy.uid,\n                    toUid = 0,\n                    fromPid = pid,\n                    packageName = packageName,\n                    appName = appName,\n                    command = \"\",\n                    action = policy.policy,\n                    target = -1,\n                    context = \"\",\n                    gids = \"\",\n                )\n                ServiceLocator.logRepo.insert(log)\n\n                val granted = policy.policy >= SuPolicy.ALLOW\n                SuCallbackHandler.notify(granted, appName)\n\n                SuEvents.notifyPolicyChanged()\n                SuEvents.notifyLogUpdated()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/tasks/AppMigration.kt",
    "content": "package com.topjohnwu.magisk.core.tasks\n\nimport android.app.Activity\nimport android.app.ActivityOptions\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.widget.Toast\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.AppApkPath\nimport com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.ktx.await\nimport com.topjohnwu.magisk.core.ktx.toast\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.signing.JarMap\nimport com.topjohnwu.magisk.core.signing.SignApk\nimport com.topjohnwu.magisk.core.utils.AXML\nimport com.topjohnwu.magisk.core.utils.Keygen\nimport com.topjohnwu.magisk.utils.APKInstall\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.File\nimport java.io.IOException\nimport java.io.OutputStream\nimport java.security.SecureRandom\nimport kotlin.random.asKotlinRandom\n\nobject AppMigration {\n\n    private const val ALPHA = \"abcdefghijklmnopqrstuvwxyz\"\n    private const val ALPHADOTS = \"$ALPHA.....\"\n    private const val ANDROID_MANIFEST = \"AndroidManifest.xml\"\n    private const val TEST_PKG_NAME = \"$APP_PACKAGE_NAME.test\"\n\n    // Some arbitrary limit\n    const val MAX_LABEL_LENGTH = 32\n    const val PLACEHOLDER = \"COMPONENT_PLACEHOLDER\"\n\n    private fun genPackageName(): String {\n        val random = SecureRandom()\n        val len = 5 + random.nextInt(15)\n        val builder = StringBuilder(len)\n        var next: Char\n        var prev = 0.toChar()\n        for (i in 0 until len) {\n            next = if (prev == '.' || i == 0 || i == len - 1) {\n                ALPHA[random.nextInt(ALPHA.length)]\n            } else {\n                ALPHADOTS[random.nextInt(ALPHADOTS.length)]\n            }\n            builder.append(next)\n            prev = next\n        }\n        if (!builder.contains('.')) {\n            // Pick a random index and set it as dot\n            val idx = random.nextInt(len - 2)\n            builder[idx + 1] = '.'\n        }\n        return builder.toString()\n    }\n\n    private fun classNameGenerator() = sequence {\n        val c1 = mutableListOf<String>()\n        val c2 = mutableListOf<String>()\n        val c3 = mutableListOf<String>()\n        val random = SecureRandom()\n        val kRandom = random.asKotlinRandom()\n\n        fun <T> chain(vararg iters: Iterable<T>) = sequence {\n            iters.forEach { it.forEach { v -> yield(v) } }\n        }\n\n        for (a in chain('a'..'z', 'A'..'Z')) {\n            if (a != 'a' && a != 'A') {\n                c1.add(\"$a\")\n            }\n            for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) {\n                c2.add(\"$a$b\")\n                for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) {\n                    c3.add(\"$a$b$c\")\n                }\n            }\n        }\n\n        c1.shuffle(random)\n        c2.shuffle(random)\n        c3.shuffle(random)\n\n        fun notJavaKeyword(name: String) = when (name) {\n            \"do\", \"if\", \"for\", \"int\", \"new\", \"try\" -> false\n            else -> true\n        }\n\n        fun List<String>.process() = asSequence().filter(::notJavaKeyword)\n\n        val names = mutableListOf<String>()\n        names.addAll(c1)\n        names.addAll(c2.process().take(30))\n        names.addAll(c3.process().take(30))\n\n        while (true) {\n            val seg = 2 + random.nextInt(4)\n            val cls = StringBuilder()\n            for (i in 0 until seg) {\n                cls.append(names.random(kRandom))\n                if (i != seg - 1)\n                    cls.append('.')\n            }\n            // Old Android does not support capitalized package names\n            // Check Android 7.0.0 PackageParser#buildClassName\n            cls[0] = cls[0].lowercaseChar()\n            yield(cls.toString())\n        }\n    }.distinct().iterator()\n\n    private fun patch(\n        context: Context,\n        apk: File, out: OutputStream,\n        pkg: String, label: CharSequence\n    ): Boolean {\n        val pm = context.packageManager\n        val info = pm.getPackageArchiveInfo(apk.path, 0)?.applicationInfo ?: return false\n        val origLabel = info.nonLocalizedLabel.toString()\n        try {\n            JarMap.open(apk, true).use { jar ->\n                val je = jar.getJarEntry(ANDROID_MANIFEST)\n                val xml = AXML(jar.getRawData(je))\n                val generator = classNameGenerator()\n                val p = xml.patchStrings {\n                    when {\n                        it.contains(APP_PACKAGE_NAME) -> it.replace(APP_PACKAGE_NAME, pkg)\n                        it.contains(PLACEHOLDER) -> generator.next()\n                        it == origLabel -> label.toString()\n                        else -> it\n                    }\n                }\n                if (!p) return false\n\n                // Write apk changes\n                jar.getOutputStream(je).use { it.write(xml.bytes) }\n                val keys = Keygen()\n                SignApk.sign(keys.cert, keys.key, jar, out)\n                return true\n            }\n        } catch (e: Exception) {\n            Timber.e(e)\n            return false\n        }\n    }\n\n    private fun patchTest(apk: File, out: File, pkg: String): Boolean {\n        try {\n            JarMap.open(apk, true).use { jar ->\n                val je = jar.getJarEntry(ANDROID_MANIFEST)\n                val xml = AXML(jar.getRawData(je))\n                val p = xml.patchStrings {\n                    when (it) {\n                        APP_PACKAGE_NAME -> pkg\n                        TEST_PKG_NAME -> \"$pkg.test\"\n                        else -> it\n                    }\n                }\n                if (!p) return false\n\n                // Write apk changes\n                jar.getOutputStream(je).use { it.write(xml.bytes) }\n                val keys = Keygen()\n                out.outputStream().use { SignApk.sign(keys.cert, keys.key, jar, it) }\n                return true\n            }\n        } catch (e: Exception) {\n            Timber.e(e)\n            return false\n        }\n    }\n\n    private fun launchApp(context: Context, pkg: String) {\n        val intent = context.packageManager.getLaunchIntentForPackage(pkg) ?: return\n        intent.putExtra(Const.Key.PREV_CONFIG, Config.toBundle())\n        val options = ActivityOptions.makeBasic()\n        if (Build.VERSION.SDK_INT >= 34) {\n            options.setShareIdentityEnabled(true)\n        }\n        context.startActivity(intent, options.toBundle())\n        if (context is Activity) {\n            context.finish()\n        }\n    }\n\n    suspend fun patchAndHide(context: Context, label: String, pkg: String? = null): Boolean {\n        val stub = File(context.cacheDir, \"stub.apk\")\n        try {\n            context.assets.open(\"stub.apk\").writeTo(stub)\n        } catch (e: IOException) {\n            Timber.e(e)\n            return false\n        }\n\n        // Generate a new random signature and package name if needed\n        val pkg = pkg ?: genPackageName()\n        Config.keyStoreRaw = \"\"\n\n        // Check and patch the test APK\n        try {\n            val info = context.packageManager.getApplicationInfo(TEST_PKG_NAME, 0)\n            val testApk = File(info.sourceDir)\n            val testRepack = File(context.cacheDir, \"test.apk\")\n            if (!patchTest(testApk, testRepack, pkg))\n                return false\n            val cmd = \"adb_pm_install $testRepack $pkg.test\"\n            if (!Shell.cmd(cmd).exec().isSuccess)\n                return false\n        } catch (e: PackageManager.NameNotFoundException) {\n        }\n\n        val repack = File(context.cacheDir, \"patched.apk\")\n        repack.outputStream().use {\n            if (!patch(context, stub, it, pkg, label))\n                return false\n        }\n\n        // Install and auto launch app\n        val cmd = \"adb_pm_install $repack $pkg\"\n        if (Shell.cmd(cmd).exec().isSuccess) {\n            Config.suManager = pkg\n            Shell.cmd(\"touch $AppApkPath\").exec()\n            launchApp(context, pkg)\n            return true\n        } else {\n            return false\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    suspend fun hide(activity: Activity, label: String) {\n        val dialog = android.app.ProgressDialog(activity).apply {\n            setTitle(activity.getString(R.string.hide_app_title))\n            isIndeterminate = true\n            setCancelable(false)\n            show()\n        }\n        val success = withContext(Dispatchers.IO) {\n            patchAndHide(activity, label)\n        }\n        if (!success) {\n            dialog.dismiss()\n            activity.toast(R.string.failure, Toast.LENGTH_LONG)\n        }\n    }\n\n    suspend fun restoreApp(context: Context): Boolean {\n        val apk = StubApk.current(context)\n        val cmd = \"adb_pm_install $apk $APP_PACKAGE_NAME\"\n        if (Shell.cmd(cmd).await().isSuccess) {\n            Config.suManager = \"\"\n            Shell.cmd(\"touch $AppApkPath\").exec()\n            launchApp(context, APP_PACKAGE_NAME)\n            return true\n        }\n        return false\n    }\n\n    @Suppress(\"DEPRECATION\")\n    suspend fun restore(activity: Activity) {\n        val dialog = android.app.ProgressDialog(activity).apply {\n            setTitle(activity.getString(R.string.restore_img_msg))\n            isIndeterminate = true\n            setCancelable(false)\n            show()\n        }\n        if (!restoreApp(activity)) {\n            activity.toast(R.string.failure, Toast.LENGTH_LONG)\n        }\n        dialog.dismiss()\n    }\n\n    suspend fun upgradeStub(context: Context, apk: File): Intent? {\n        val label = context.applicationInfo.nonLocalizedLabel\n        val pkg = context.packageName\n        val session = APKInstall.startSession(context)\n        return withContext(Dispatchers.IO) {\n            session.openStream(context).use {\n                if (!patch(context, apk, it, pkg, label)) {\n                    return@withContext null\n                }\n            }\n            session.waitIntent()\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/tasks/FlashZip.kt",
    "content": "package com.topjohnwu.magisk.core.tasks\n\nimport android.net.Uri\nimport androidx.core.net.toFile\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.displayName\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.IOException\n\nopen class FlashZip(\n    private val mUri: Uri,\n    private val console: MutableList<String>,\n    private val logs: MutableList<String>\n) {\n\n    private val installDir = File(AppContext.cacheDir, \"flash\")\n    private lateinit var zipFile: File\n\n    @Throws(IOException::class)\n    private suspend fun flash(): Boolean {\n        installDir.deleteRecursively()\n        installDir.mkdirs()\n\n        zipFile = if (mUri.scheme == \"file\") {\n            mUri.toFile()\n        } else {\n            File(installDir, \"install.zip\").also {\n                console.add(\"- Copying zip to temp directory\")\n                try {\n                    mUri.inputStream().writeTo(it)\n                } catch (e: IOException) {\n                    when (e) {\n                        is FileNotFoundException -> console.add(\"! Invalid Uri\")\n                        else -> console.add(\"! Cannot copy to cache\")\n                    }\n                    throw e\n                }\n            }\n        }\n\n        try {\n            val binary = File(installDir, \"update-binary\")\n            AppContext.assets.open(\"module_installer.sh\").use { it.writeTo(binary) }\n        } catch (e: IOException) {\n            console.add(\"! Unzip error\")\n            throw e\n        }\n\n        console.add(\"- Installing ${mUri.displayName}\")\n\n        return Shell.cmd(\"sh $installDir/update-binary dummy 1 \\'$zipFile\\'\")\n            .to(console, logs).exec().isSuccess\n    }\n\n    open suspend fun exec() = withContext(Dispatchers.IO) {\n        try {\n            if (!flash()) {\n                console.add(\"! Installation failed\")\n                false\n            } else {\n                true\n            }\n        } catch (e: IOException) {\n            Timber.e(e)\n            false\n        } finally {\n            Shell.cmd(\"cd /\", \"rm -rf $installDir ${Const.TMPDIR}\").submit()\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/tasks/MagiskInstaller.kt",
    "content": "package com.topjohnwu.magisk.core.tasks\n\nimport android.net.Uri\nimport android.os.Process\nimport android.system.ErrnoException\nimport android.system.Os\nimport android.system.OsConstants\nimport android.system.OsConstants.O_WRONLY\nimport androidx.annotation.WorkerThread\nimport androidx.core.os.postDelayed\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.AppApkPath\nimport com.topjohnwu.magisk.core.BuildConfig\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.copyAll\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.magisk.core.utils.DummyList\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.inputStream\nimport com.topjohnwu.magisk.core.utils.MediaStoreUtils.outputStream\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.ShellUtils\nimport com.topjohnwu.superuser.internal.UiThreadHandler\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport com.topjohnwu.superuser.nio.FileSystemManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport org.apache.commons.compress.archivers.tar.TarArchiveEntry\nimport org.apache.commons.compress.archivers.tar.TarArchiveInputStream\nimport org.apache.commons.compress.archivers.tar.TarArchiveOutputStream\nimport org.apache.commons.compress.archivers.zip.ZipArchiveEntry\nimport org.apache.commons.compress.archivers.zip.ZipArchiveInputStream\nimport org.apache.commons.compress.archivers.zip.ZipFile\nimport org.apache.commons.compress.compressors.lz4.FramedLZ4CompressorInputStream\nimport timber.log.Timber\nimport java.io.File\nimport java.io.FilterInputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.io.PushbackInputStream\nimport java.nio.ByteBuffer\nimport java.security.SecureRandom\nimport java.util.Locale\nimport java.util.concurrent.atomic.AtomicBoolean\n\nabstract class MagiskInstallImpl protected constructor(\n    protected val console: MutableList<String>,\n    private val logs: MutableList<String>\n) {\n\n    private lateinit var installDir: ExtendedFile\n    private lateinit var srcBoot: ExtendedFile\n\n    private val shell = Shell.getShell()\n    private val useRootDir = shell.isRoot && Info.noDataExec\n    protected val context get() = ServiceLocator.deContext\n\n    private val rootFS get() = RootUtils.fs\n    private val localFS get() = FileSystemManager.getLocal()\n\n    private val destName: String by lazy {\n        if (Config.randName) {\n            val alpha = \"abcdefghijklmnopqrstuvwxyz\"\n            val alphaNum = \"$alpha${alpha.uppercase(Locale.ROOT)}0123456789\"\n            val random = SecureRandom()\n            StringBuilder(\"magisk_patched-${BuildConfig.APP_VERSION_CODE}_\").run {\n                for (i in 1..5) {\n                    append(alphaNum[random.nextInt(alphaNum.length)])\n                }\n                toString()\n            }\n        } else {\n            \"magisk_patched\"\n        }\n    }\n\n    private fun findImage(slot: String): Boolean {\n        val cmd =\n            \"RECOVERYMODE=${Config.recovery} \" +\n            \"VENDORBOOT=${Info.isVendorBoot} \" +\n            \"SLOT=$slot \" +\n            \"find_boot_image; echo \\$BOOTIMAGE\"\n        val bootPath = (\"($cmd)\").fsh()\n        if (bootPath.isEmpty()) {\n            console.add(\"! Unable to detect target image\")\n            return false\n        }\n        srcBoot = rootFS.getFile(bootPath)\n        console.add(\"- Target image: $bootPath\")\n        return true\n    }\n\n    private fun findImage(): Boolean {\n        return findImage(Info.slot)\n    }\n\n    private fun findSecondary(): Boolean {\n        val slot = if (Info.slot == \"_a\") \"_b\" else \"_a\"\n        console.add(\"- Target slot: $slot\")\n        return findImage(slot)\n    }\n\n    private suspend fun extractFiles(): Boolean {\n        console.add(\"- Device platform: ${Const.CPU_ABI}\")\n        console.add(\"- Installing: ${BuildConfig.APP_VERSION_NAME} (${BuildConfig.APP_VERSION_CODE})\")\n\n        installDir = localFS.getFile(context.filesDir.parent, \"install\")\n        installDir.deleteRecursively()\n        installDir.mkdirs()\n\n        try {\n            // Extract binaries\n            if (isRunningAsStub) {\n                ZipFile.builder().setFile(StubApk.current(context)).get().use { zf ->\n                    zf.entries.asSequence().filter {\n                        !it.isDirectory && it.name.startsWith(\"lib/${Const.CPU_ABI}/\")\n                    }.forEach {\n                        val n = it.name.substring(it.name.lastIndexOf('/') + 1)\n                        val name = n.substring(3, n.length - 3)\n                        val dest = File(installDir, name)\n                        zf.getInputStream(it).writeTo(dest)\n                        dest.setExecutable(true)\n                    }\n\n                    val abi32 = Const.CPU_ABI_32\n                    if (Process.is64Bit() && abi32 != null) {\n                        val entry = zf.getEntry(\"lib/$abi32/libmagisk.so\")\n                        if (entry != null) {\n                            val magisk32 = File(installDir, \"magisk32\")\n                            zf.getInputStream(entry).writeTo(magisk32)\n                        }\n                    }\n                }\n            } else {\n                val info = context.applicationInfo\n                val libs = File(info.nativeLibraryDir).listFiles { _, name ->\n                    name.startsWith(\"lib\") && name.endsWith(\".so\")\n                } ?: emptyArray()\n\n                for (lib in libs) {\n                    val name = lib.name.substring(3, lib.name.length - 3)\n                    Os.symlink(lib.path, \"$installDir/$name\")\n                }\n\n                // Also extract magisk32 on 64-bit devices that supports 32-bit\n                val abi32 = Const.CPU_ABI_32\n                if (Process.is64Bit() && abi32 != null) {\n                    val name = \"lib/$abi32/libmagisk.so\"\n                    val entry = javaClass.classLoader!!.getResourceAsStream(name)\n                    if (entry != null) {\n                        val magisk32 = File(installDir, \"magisk32\")\n                        entry.writeTo(magisk32)\n                    }\n                }\n            }\n\n            // Extract scripts\n            for (script in listOf(\"util_functions.sh\", \"boot_patch.sh\", \"addon.d.sh\", \"stub.apk\")) {\n                val dest = File(installDir, script)\n                context.assets.open(script).writeTo(dest)\n            }\n            // Extract chromeos tools\n            File(installDir, \"chromeos\").mkdir()\n            for (file in listOf(\"futility\", \"kernel_data_key.vbprivk\", \"kernel.keyblock\")) {\n                val name = \"chromeos/$file\"\n                val dest = File(installDir, name)\n                context.assets.open(name).writeTo(dest)\n            }\n        } catch (e: Exception) {\n            console.add(\"! Unable to extract files\")\n            Timber.e(e)\n            return false\n        }\n\n        if (useRootDir) {\n            // Move everything to tmpfs to workaround Samsung bullshit\n            rootFS.getFile(Const.TMPDIR).also {\n                arrayOf(\n                    \"rm -rf $it\",\n                    \"mkdir -p $it\",\n                    \"cp_readlink $installDir $it\",\n                    \"rm -rf $installDir\"\n                ).sh()\n                installDir = it\n            }\n        }\n\n        return true\n    }\n\n    private suspend fun InputStream.copyAndCloseOut(out: OutputStream) =\n        out.use { copyAll(it, 1024 * 1024) }\n\n    private class NoAvailableStream(s: InputStream) : FilterInputStream(s) {\n        // Make sure available is never called on the actual stream and always return 0\n        // to reduce max buffer size and avoid OOM\n        override fun available() = 0\n    }\n\n    private class NoBootException : IOException()\n\n    inner class BootItem(private val entry: TarArchiveEntry) {\n        val name = entry.name.replace(\".lz4\", \"\")\n        var file = installDir.getChildFile(name)\n\n        suspend fun copyTo(tarOut: TarArchiveOutputStream) {\n            entry.name = name\n            entry.size = file.length()\n            file.newInputStream().use {\n                console.add(\"-- Writing   : $name\")\n                tarOut.putArchiveEntry(entry)\n                it.copyAll(tarOut)\n                tarOut.closeArchiveEntry()\n            }\n        }\n    }\n\n    @Throws(IOException::class)\n    private suspend fun processTar(\n        tarIn: TarArchiveInputStream,\n        tarOut: TarArchiveOutputStream\n    ): BootItem {\n        console.add(\"- Processing tar file\")\n        var entry: TarArchiveEntry? = tarIn.nextEntry\n\n        fun decompressedStream(): InputStream {\n            val stream = if (tarIn.currentEntry.name.endsWith(\".lz4\"))\n                FramedLZ4CompressorInputStream(tarIn, true) else tarIn\n            return NoAvailableStream(stream)\n        }\n\n        var boot: BootItem? = null\n        var initBoot: BootItem? = null\n        var recovery: BootItem? = null\n\n        while (entry != null) {\n            val bootItem: BootItem?\n            if (entry.name.startsWith(\"boot.img\")) {\n                bootItem = BootItem(entry)\n                boot = bootItem\n            } else if (entry.name.startsWith(\"init_boot.img\")) {\n                bootItem = BootItem(entry)\n                initBoot = bootItem\n            } else if (Config.recovery && entry.name.contains(\"recovery.img\")) {\n                bootItem = BootItem(entry)\n                recovery = bootItem\n            } else {\n                bootItem = null\n            }\n\n            if (bootItem != null) {\n                console.add(\"-- Extracting: ${bootItem.name}\")\n                decompressedStream().copyAndCloseOut(bootItem.file.newOutputStream())\n            } else if (entry.name.contains(\"vbmeta.img\")) {\n                val rawData = decompressedStream().readBytes()\n                // Valid vbmeta.img should be at least 256 bytes\n                if (rawData.size < 256)\n                    continue\n\n                // vbmeta partition exist, disable boot vbmeta patch\n                Info.patchBootVbmeta = false\n\n                val name = entry.name.replace(\".lz4\", \"\")\n                console.add(\"-- Patching  : $name\")\n\n                // Patch flags to AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED |\n                // AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED\n                ByteBuffer.wrap(rawData).putInt(120, 3)\n\n                // Fetch the next entry first before modifying current entry\n                val vbmeta = entry\n                entry = tarIn.nextEntry\n\n                // Update entry with new information\n                vbmeta.name = name\n                vbmeta.size = rawData.size.toLong()\n\n                // Write output\n                tarOut.putArchiveEntry(vbmeta)\n                tarOut.write(rawData)\n                tarOut.closeArchiveEntry()\n                continue\n            } else if (entry.name.contains(\"userdata.img\")) {\n                console.add(\"-- Skipping  : ${entry.name}\")\n            } else {\n                console.add(\"-- Copying   : ${entry.name}\")\n                tarOut.putArchiveEntry(entry)\n                tarIn.copyAll(tarOut)\n                tarOut.closeArchiveEntry()\n            }\n            entry = tarIn.nextEntry ?: break\n        }\n\n        // Patch priority: recovery > init_boot > boot\n        return when {\n            recovery != null -> {\n                if (boot != null) {\n                    // Repack boot image to prevent auto restore\n                    arrayOf(\n                        \"cd $installDir\",\n                        \"chmod -R 755 .\",\n                        \"./magiskboot unpack boot.img\",\n                        \"./magiskboot repack boot.img\",\n                        \"cat new-boot.img > boot.img\",\n                        \"./magiskboot cleanup\",\n                        \"rm -f new-boot.img\",\n                        \"cd /\").sh()\n                    boot.copyTo(tarOut)\n                }\n                recovery\n            }\n            initBoot != null -> {\n                boot?.copyTo(tarOut)\n                initBoot\n            }\n            boot != null -> boot\n            else -> throw NoBootException()\n        }\n    }\n\n    @Throws(IOException::class)\n    private suspend fun processZip(zipIn: ZipArchiveInputStream): ExtendedFile {\n        console.add(\"- Processing zip file\")\n        val boot = installDir.getChildFile(\"boot.img\")\n        val initBoot = installDir.getChildFile(\"init_boot.img\")\n        var entry: ZipArchiveEntry\n        while (true) {\n            entry = zipIn.nextEntry ?: break\n            if (entry.isDirectory) continue\n            when (entry.name.substringAfterLast('/')) {\n                \"payload.bin\" -> {\n                    try {\n                        return processPayload(zipIn)\n                    } catch (e: IOException) {\n                        // No boot image in payload.bin, continue to find boot images\n                    }\n                }\n                \"init_boot.img\" -> {\n                    console.add(\"- Extracting init_boot.img\")\n                    zipIn.copyAndCloseOut(initBoot.newOutputStream())\n                    return initBoot\n                }\n                \"boot.img\" -> {\n                    console.add(\"- Extracting boot.img\")\n                    zipIn.copyAndCloseOut(boot.newOutputStream())\n                    // Don't return here since there might be an init_boot.img\n                }\n            }\n        }\n        if (boot.exists()) {\n            return boot\n        } else {\n            throw NoBootException()\n        }\n    }\n\n    @Throws(IOException::class)\n    private fun processPayload(input: InputStream): ExtendedFile {\n        var fifo: File? = null\n        try {\n            console.add(\"- Processing payload.bin\")\n            fifo = File.createTempFile(\"payload-fifo-\", null, installDir)\n            fifo.delete()\n            Os.mkfifo(fifo.path, 420 /* 0644 */)\n\n            // Enqueue the shell command first, or the subsequent FIFO open will block\n            val future = arrayOf(\n                \"cd $installDir\",\n                \"./magiskboot extract $fifo\",\n                \"cd /\"\n            ).eq()\n\n            val fd = Os.open(fifo.path, O_WRONLY, 0)\n            try {\n                val bufSize = 1024 * 1024\n                val buf = ByteBuffer.allocate(bufSize)\n                buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()\n                while (buf.hasRemaining()) {\n                    try {\n                        Os.write(fd, buf)\n                    } catch (e: ErrnoException) {\n                        if (e.errno != OsConstants.EPIPE)\n                            throw e\n                        // If SIGPIPE, then the other side is closed, we're done\n                        break\n                    }\n                    if (!buf.hasRemaining()) {\n                        buf.limit(bufSize)\n                        buf.position(input.read(buf.array()).coerceAtLeast(0)).flip()\n                    }\n                }\n            } finally {\n                Os.close(fd)\n            }\n\n            val success = try { future.get().isSuccess } catch (e: Exception) { false }\n            if (!success) {\n                console.add(\"! Error while extracting payload.bin\")\n                throw IOException()\n            }\n            val boot = installDir.getChildFile(\"boot.img\")\n            val initBoot = installDir.getChildFile(\"init_boot.img\")\n            return when {\n                initBoot.exists() -> {\n                    console.add(\"-- Extract init_boot.img\")\n                    initBoot\n                }\n                boot.exists() -> {\n                    console.add(\"-- Extract boot.img\")\n                    boot\n                }\n                else -> {\n                    throw NoBootException()\n                }\n            }\n        } catch (e: ErrnoException) {\n            throw IOException(e)\n        } finally {\n            fifo?.delete()\n        }\n    }\n\n    private suspend fun processFile(uri: Uri): Boolean {\n        val outStream: OutputStream\n        val outFile: MediaStoreUtils.UriFile\n        var bootItem: BootItem? = null\n\n        // Process input file\n        try {\n            PushbackInputStream(uri.inputStream().buffered(1024 * 1024), 512).use { src ->\n                val head = ByteArray(512)\n                if (src.read(head) != head.size) {\n                    console.add(\"! Invalid input file\")\n                    return false\n                }\n                src.unread(head)\n\n                val magic = head.copyOf(4)\n                val tarMagic = head.copyOfRange(257, 262)\n\n                srcBoot = if (tarMagic.contentEquals(\"ustar\".toByteArray())) {\n                    // tar file\n                    outFile = MediaStoreUtils.getFile(\"$destName.tar\")\n                    val os = outFile.uri.outputStream().buffered(1024 * 1024)\n                    outStream = TarArchiveOutputStream(os).also {\n                        it.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)\n                        it.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)\n                    }\n\n                    try {\n                        bootItem = processTar(TarArchiveInputStream(src), outStream)\n                        bootItem.file\n                    } catch (e: IOException) {\n                        outStream.close()\n                        outFile.delete()\n                        throw e\n                    }\n                } else {\n                    // raw image\n                    outFile = MediaStoreUtils.getFile(\"$destName.img\")\n                    outStream = outFile.uri.outputStream()\n\n                    try {\n                        if (magic.contentEquals(\"CrAU\".toByteArray())) {\n                            processPayload(src)\n                        } else if (magic.contentEquals(\"PK\\u0003\\u0004\".toByteArray())) {\n                            processZip(ZipArchiveInputStream(src))\n                        } else {\n                            console.add(\"- Copying image to cache\")\n                            installDir.getChildFile(\"boot.img\").also {\n                                src.copyAndCloseOut(it.newOutputStream())\n                            }\n                        }\n                    } catch (e: IOException) {\n                        outStream.close()\n                        outFile.delete()\n                        throw e\n                    }\n                }\n            }\n        } catch (e: IOException) {\n            if (e is NoBootException)\n                console.add(\"! No boot image found\")\n            console.add(\"! Process error\")\n            Timber.e(e)\n            return false\n        }\n\n        // Patch file\n        if (!patchBoot()) {\n            outFile.delete()\n            return false\n        }\n\n        // Output file\n        try {\n            val newBoot = installDir.getChildFile(\"new-boot.img\")\n            if (bootItem != null) {\n                bootItem.file = newBoot\n                bootItem.copyTo(outStream as TarArchiveOutputStream)\n            } else {\n                newBoot.newInputStream().use { it.copyAll(outStream, 1024 * 1024) }\n            }\n            newBoot.delete()\n\n            console.add(\"\")\n            console.add(\"****************************\")\n            console.add(\" Output file is written to \")\n            console.add(\" $outFile \")\n            console.add(\"****************************\")\n        } catch (e: IOException) {\n            console.add(\"! Failed to output to $outFile\")\n            outFile.delete()\n            Timber.e(e)\n            return false\n        } finally {\n            outStream.close()\n        }\n\n        // Fix up binaries\n        srcBoot.delete()\n        \"cp_readlink $installDir\".sh()\n\n        return true\n    }\n\n    private fun patchBoot(): Boolean {\n        val newBoot = installDir.getChildFile(\"new-boot.img\")\n        if (!useRootDir) {\n            // Create output files before hand\n            newBoot.createNewFile()\n            File(installDir, \"stock_boot.img\").createNewFile()\n        }\n\n        val cmds = arrayOf(\n            \"cd $installDir\",\n            \"KEEPFORCEENCRYPT=${Config.keepEnc} \" +\n            \"KEEPVERITY=${Config.keepVerity} \" +\n            \"PATCHVBMETAFLAG=${Info.patchBootVbmeta} \" +\n            \"RECOVERYMODE=${Config.recovery} \" +\n            \"LEGACYSAR=${Info.legacySAR} \" +\n            \"sh boot_patch.sh $srcBoot\")\n        val isSuccess = cmds.sh().isSuccess\n\n        shell.newJob().add(\"./magiskboot cleanup\", \"cd /\").exec()\n\n        return isSuccess\n    }\n\n    private fun flashBoot() = \"direct_install $installDir $srcBoot\".sh().isSuccess\n\n    private suspend fun postOTA(): Boolean {\n        try {\n            val bootctl = File.createTempFile(\"bootctl\", null, context.cacheDir)\n            context.assets.open(\"bootctl\").writeTo(bootctl)\n            \"post_ota $bootctl\".sh()\n        } catch (e: IOException) {\n            console.add(\"! Unable to download bootctl\")\n            Timber.e(e)\n            return false\n        }\n\n        console.add(\"*************************************************************\")\n        console.add(\" Next reboot will boot to second slot!\")\n        console.add(\" Go back to System Updates and press Restart to complete OTA\")\n        console.add(\"*************************************************************\")\n        return true\n    }\n\n    private fun Array<String>.eq() = shell.newJob().add(*this).to(console, logs).enqueue()\n    private fun String.sh() = shell.newJob().add(this).to(console, logs).exec()\n    private fun Array<String>.sh() = shell.newJob().add(*this).to(console, logs).exec()\n    private fun String.fsh() = ShellUtils.fastCmd(shell, this)\n    private fun Array<String>.fsh() = ShellUtils.fastCmd(shell, *this)\n\n    protected suspend fun patchFile(file: Uri) = extractFiles() && processFile(file)\n\n    protected suspend fun direct() = findImage() && extractFiles() && patchBoot() && flashBoot()\n\n    protected suspend fun secondSlot() =\n        findSecondary() && extractFiles() && patchBoot() && flashBoot() && postOTA()\n\n    protected suspend fun fixEnv() = extractFiles() && \"fix_env $installDir\".sh().isSuccess\n\n    protected fun restore() = findImage() && \"restore_imgs $srcBoot\".sh().isSuccess\n\n    protected fun uninstall() = \"run_uninstaller $AppApkPath\".sh().isSuccess\n\n    @WorkerThread\n    protected abstract suspend fun operations(): Boolean\n\n    open suspend fun exec(): Boolean {\n        if (haveActiveSession.getAndSet(true))\n            return false\n\n        val result = withContext(Dispatchers.IO) { operations() }\n        haveActiveSession.set(false)\n        if (result)\n            return true\n\n        // Not every operation initializes installDir\n        if (::installDir.isInitialized)\n            Shell.cmd(\"rm -rf $installDir\").submit()\n        return false\n    }\n\n    companion object {\n        private var haveActiveSession = AtomicBoolean(false)\n    }\n}\n\nabstract class ConsoleInstaller(\n    console: MutableList<String>,\n    logs: MutableList<String>\n) : MagiskInstallImpl(console, logs) {\n    override suspend fun exec(): Boolean {\n        val success = super.exec()\n        if (success) {\n            console.add(\"- All done!\")\n        } else {\n            console.add(\"! Installation failed\")\n        }\n        return success\n    }\n}\n\nabstract class CallBackInstaller : MagiskInstallImpl(DummyList, DummyList) {\n    suspend fun exec(callback: (Boolean) -> Unit): Boolean {\n        val success = exec()\n        callback(success)\n        return success\n    }\n}\n\nclass MagiskInstaller {\n\n    class Patch(\n        private val uri: Uri,\n        console: MutableList<String>,\n        logs: MutableList<String>\n    ) : ConsoleInstaller(console, logs) {\n        override suspend fun operations() = patchFile(uri)\n    }\n\n    class SecondSlot(\n        console: MutableList<String>,\n        logs: MutableList<String>\n    ) : ConsoleInstaller(console, logs) {\n        override suspend fun operations() = secondSlot()\n    }\n\n    class Direct(\n        console: MutableList<String>,\n        logs: MutableList<String>\n    ) : ConsoleInstaller(console, logs) {\n        override suspend fun operations() = direct()\n    }\n\n    class Emulator(\n        console: MutableList<String>,\n        logs: MutableList<String>\n    ) : ConsoleInstaller(console, logs) {\n        override suspend fun operations() = fixEnv()\n    }\n\n    class Uninstall(\n        console: MutableList<String>,\n        logs: MutableList<String>\n    ) : ConsoleInstaller(console, logs) {\n        override suspend fun operations() = uninstall()\n\n        override suspend fun exec(): Boolean {\n            val success = super.exec()\n            if (success) {\n                UiThreadHandler.handler.postDelayed(3000) {\n                    Shell.cmd(\"pm uninstall ${context.packageName}\").exec()\n                }\n            }\n            return success\n        }\n    }\n\n    class Restore : CallBackInstaller() {\n        override suspend fun operations() = restore()\n    }\n\n    class FixEnv : CallBackInstaller() {\n        override suspend fun operations() = fixEnv()\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/AXML.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport java.io.ByteArrayOutputStream\nimport java.nio.ByteBuffer\nimport java.nio.ByteOrder.LITTLE_ENDIAN\nimport java.nio.charset.Charset\n\nclass AXML(b: ByteArray) {\n\n    var bytes = b\n        private set\n\n    companion object {\n        private const val CHUNK_SIZE_OFF = 4\n        private const val STRING_INDICES_OFF = 7 * 4\n        private val UTF_16LE = Charset.forName(\"UTF-16LE\")\n    }\n\n    /**\n     * String pool header:\n     * 0:  0x1C0001\n     * 1:  chunk size\n     * 2:  number of strings\n     * 3:  number of styles (assert as 0)\n     * 4:  flags\n     * 5:  offset to string data\n     * 6:  offset to style data (assert as 0)\n     *\n     * Followed by an array of uint32_t with size = number of strings\n     * Each entry points to an offset into the string data\n     */\n    fun patchStrings(mapFn: (String) -> String): Boolean {\n        val buffer = ByteBuffer.wrap(bytes).order(LITTLE_ENDIAN)\n\n        fun findStringPool(): Int {\n            var offset = 8\n            while (offset < bytes.size) {\n                if (buffer.getInt(offset) == 0x1C0001)\n                    return offset\n                offset += buffer.getInt(offset + CHUNK_SIZE_OFF)\n            }\n            return -1\n        }\n\n        val start = findStringPool()\n        if (start < 0)\n            return false\n\n        // Read header\n        buffer.position(start + 4)\n        val intBuf = buffer.asIntBuffer()\n        val size = intBuf.get()\n        val count = intBuf.get()\n        intBuf.get()\n        intBuf.get()\n        val dataOff = start + intBuf.get()\n        intBuf.get()\n\n        val strList = ArrayList<String>(count)\n        // Collect all strings in the pool\n        for (i in 0 until count) {\n            val off = dataOff + intBuf.get()\n            val len = buffer.getShort(off)\n            strList.add(String(bytes, off + 2, len * 2, UTF_16LE))\n        }\n\n        val strArr = strList.toTypedArray()\n        for (i in strArr.indices) {\n            strArr[i] = mapFn(strArr[i])\n        }\n\n        // Write everything before string data, will patch values later\n        val baos = RawByteStream()\n        baos.write(bytes, 0, dataOff)\n\n        // Write string data\n        val offList = IntArray(count)\n        for (i in 0 until count) {\n            offList[i] = baos.size() - dataOff\n            val str = strArr[i]\n            baos.write(str.length.toShortBytes())\n            baos.write(str.toByteArray(UTF_16LE))\n            // Null terminate\n            baos.write(0)\n            baos.write(0)\n        }\n        baos.align()\n\n        val sizeDiff = baos.size() - start - size\n        val newBuffer = ByteBuffer.wrap(baos.buffer).order(LITTLE_ENDIAN)\n\n        // Patch XML size\n        newBuffer.putInt(CHUNK_SIZE_OFF, buffer.getInt(CHUNK_SIZE_OFF) + sizeDiff)\n        // Patch string pool size\n        newBuffer.putInt(start + CHUNK_SIZE_OFF, size + sizeDiff)\n        // Patch index table\n        newBuffer.position(start + STRING_INDICES_OFF)\n        val newIntBuf = newBuffer.asIntBuffer()\n        offList.forEach { newIntBuf.put(it) }\n\n        // Write the rest of the chunks\n        val nextOff = start + size\n        baos.write(bytes, nextOff, bytes.size - nextOff)\n\n        bytes = baos.toByteArray()\n        return true\n    }\n\n    private fun Int.toShortBytes(): ByteArray {\n        val b = ByteBuffer.allocate(2).order(LITTLE_ENDIAN)\n        b.putShort(this.toShort())\n        return b.array()\n    }\n\n    private class RawByteStream : ByteArrayOutputStream() {\n        val buffer: ByteArray get() = buf\n\n        fun align(alignment: Int = 4) {\n            val newCount = (count + alignment - 1) / alignment * alignment\n            for (i in 0 until (newCount - count))\n                write(0)\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/Desugar.java",
    "content": "package com.topjohnwu.magisk.core.utils;\n\nimport android.os.Build;\n\nimport org.apache.commons.compress.archivers.zip.ZipArchiveEntry;\nimport org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;\nimport org.apache.commons.compress.archivers.zip.ZipUtil;\n\nimport java.nio.file.attribute.FileTime;\nimport java.util.zip.ZipEntry;\n\npublic class Desugar {\n    public static FileTime getLastModifiedTime(ZipEntry entry) {\n        if (Build.VERSION.SDK_INT >= 26) {\n            return entry.getLastModifiedTime();\n        } else {\n            return FileTime.fromMillis(entry.getTime());\n        }\n    }\n\n    public static FileTime getLastAccessTime(ZipEntry entry) {\n        if (Build.VERSION.SDK_INT >= 26) {\n            return entry.getLastAccessTime();\n        } else {\n            return null;\n        }\n    }\n\n    public static FileTime getCreationTime(ZipEntry entry) {\n        if (Build.VERSION.SDK_INT >= 26) {\n            return entry.getCreationTime();\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Within {@link ZipArchiveOutputStream#copyFromZipInputStream}, we redirect the method call\n     * {@link ZipUtil#checkRequestedFeatures} to this method. This is safe because the only usage\n     * of copyFromZipInputStream is in {@link ZipArchiveOutputStream#addRawArchiveEntry},\n     * which does not need to actually understand the content of the zip entry. By removing\n     * this feature check, we can modify zip files using unsupported compression methods.\n     */\n    public static void checkRequestedFeatures(final ZipArchiveEntry ze) {\n        // No-op\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/DummyList.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nobject DummyList : java.util.AbstractList<String>() {\n\n    override val size: Int get() = 0\n\n    override fun get(index: Int): String {\n        throw IndexOutOfBoundsException()\n    }\n\n    override fun add(element: String): Boolean = false\n\n    override fun add(index: Int, element: String) {}\n\n    override fun clear() {}\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/Keygen.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.util.Base64\nimport android.util.Base64OutputStream\nimport com.topjohnwu.magisk.core.Config\nimport org.bouncycastle.asn1.x500.X500Name\nimport org.bouncycastle.asn1.x509.SubjectPublicKeyInfo\nimport org.bouncycastle.cert.X509v3CertificateBuilder\nimport org.bouncycastle.cert.jcajce.JcaX509CertificateConverter\nimport org.bouncycastle.operator.jcajce.JcaContentSignerBuilder\nimport java.io.ByteArrayOutputStream\nimport java.math.BigInteger\nimport java.security.KeyPairGenerator\nimport java.security.KeyStore\nimport java.security.PrivateKey\nimport java.security.cert.X509Certificate\nimport java.util.Calendar\nimport java.util.Locale\nimport java.util.Random\nimport java.util.zip.GZIPInputStream\nimport java.util.zip.GZIPOutputStream\n\nprivate interface CertKeyProvider {\n    val cert: X509Certificate\n    val key: PrivateKey\n}\n\nclass Keygen : CertKeyProvider {\n\n    companion object {\n        private const val ALIAS = \"magisk\"\n        private val PASSWORD get() = \"magisk\".toCharArray()\n        private const val DNAME = \"C=US,ST=California,L=Mountain View,O=Google Inc.,OU=Android,CN=Android\"\n        private const val BASE64_FLAG = Base64.NO_PADDING or Base64.NO_WRAP\n    }\n\n    private val start = Calendar.getInstance().apply { add(Calendar.MONTH, -3) }\n    private val end = (start.clone() as Calendar).apply { add(Calendar.YEAR, 30) }\n\n    private val ks = init()\n    override val cert = ks.getCertificate(ALIAS) as X509Certificate\n    override val key = ks.getKey(ALIAS, PASSWORD) as PrivateKey\n\n    private fun init(): KeyStore {\n        val raw = Config.keyStoreRaw\n        val ks = KeyStore.getInstance(\"PKCS12\")\n        if (raw.isEmpty()) {\n            ks.load(null)\n        } else {\n            GZIPInputStream(Base64.decode(raw, BASE64_FLAG).inputStream()).use {\n                ks.load(it, PASSWORD)\n            }\n        }\n\n        // Keys already exist\n        if (ks.containsAlias(ALIAS))\n            return ks\n\n        // Generate new private key and certificate\n        val kp = KeyPairGenerator.getInstance(\"RSA\").apply { initialize(4096) }.genKeyPair()\n        val dname = X500Name(DNAME)\n        val builder = X509v3CertificateBuilder(\n            dname, BigInteger(160, Random()),\n            start.time, end.time, Locale.ROOT, dname,\n            SubjectPublicKeyInfo.getInstance(kp.public.encoded)\n        )\n        val signer = JcaContentSignerBuilder(\"SHA1WithRSA\").build(kp.private)\n        val cert = JcaX509CertificateConverter().getCertificate(builder.build(signer))\n\n        // Store them into keystore\n        ks.setKeyEntry(ALIAS, kp.private, PASSWORD, arrayOf(cert))\n        val bytes = ByteArrayOutputStream()\n        GZIPOutputStream(Base64OutputStream(bytes, BASE64_FLAG)).use {\n            ks.store(it, PASSWORD)\n        }\n        Config.keyStoreRaw = bytes.toString(\"UTF-8\")\n\n        return ks\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/LocaleSetting.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.annotation.SuppressLint\nimport android.app.LocaleConfig\nimport android.app.LocaleManager\nimport android.content.ContextWrapper\nimport android.content.Intent\nimport android.content.res.Resources\nimport android.net.Uri\nimport android.os.Build\nimport android.os.LocaleList\nimport android.provider.Settings\nimport androidx.annotation.RequiresApi\nimport com.topjohnwu.magisk.core.AppApkPath\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.base.relaunch\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport org.xmlpull.v1.XmlPullParser\nimport java.util.Locale\n\ninterface LocaleSetting {\n    // The locale that is manually overridden, null if system default\n    val appLocale: Locale?\n    // The current active locale used in the application\n    val currentLocale: Locale\n\n    fun setLocale(tag: String)\n    fun updateResource(res: Resources)\n\n    private class Api23Impl : LocaleSetting {\n\n        private val systemLocale: Locale = Locale.getDefault()\n\n        override var appLocale: Locale? = null\n        override var currentLocale: Locale = systemLocale\n\n        init {\n            setLocale(Config.locale)\n        }\n\n        override fun setLocale(tag: String) {\n            val locale = when {\n                tag.isEmpty() -> null\n                else -> Locale.forLanguageTag(tag)\n            }\n            currentLocale = locale ?: systemLocale\n            appLocale = locale\n            Locale.setDefault(currentLocale)\n            updateResource(AppContext.resources)\n            AppContext.foregroundActivity?.relaunch()\n        }\n\n        @Suppress(\"DEPRECATION\")\n        override fun updateResource(res: Resources) {\n            val config = res.configuration\n            config.setLocale(currentLocale)\n            res.updateConfiguration(config, null)\n        }\n    }\n\n    @RequiresApi(24)\n    private class Api24Impl : LocaleSetting {\n\n        private val systemLocaleList = LocaleList.getDefault()\n        private var currentLocaleList: LocaleList = systemLocaleList\n\n        override var appLocale: Locale? = null\n        override val currentLocale: Locale get() = currentLocaleList[0]\n\n        init {\n            setLocale(Config.locale)\n        }\n\n        override fun setLocale(tag: String) {\n            val localeList = when {\n                tag.isEmpty() -> null\n                else -> LocaleList.forLanguageTags(tag)\n            }\n            currentLocaleList = localeList ?: systemLocaleList\n            appLocale = localeList?.get(0)\n            LocaleList.setDefault(currentLocaleList)\n            updateResource(AppContext.resources)\n            AppContext.foregroundActivity?.relaunch()\n        }\n\n        @Suppress(\"DEPRECATION\")\n        override fun updateResource(res: Resources) {\n            val config = res.configuration\n            config.setLocales(currentLocaleList)\n            res.updateConfiguration(config, null)\n        }\n    }\n\n    @RequiresApi(33)\n    private class Api33Impl : LocaleSetting {\n\n        private val lm: LocaleManager = AppContext.getSystemService(LocaleManager::class.java)\n\n        override val appLocale: Locale?\n            get() = lm.applicationLocales.let { if (it.isEmpty) null else it[0] }\n\n        override val currentLocale: Locale\n            get() = appLocale ?: lm.systemLocales[0]\n\n        // These following methods should not be used\n        override fun setLocale(tag: String) {}\n        override fun updateResource(res: Resources) {}\n    }\n\n    class AppLocaleList(\n        val names: Array<String>,\n        val tags: Array<String>\n    )\n\n    @SuppressLint(\"NewApi\")\n    companion object {\n        val available: AppLocaleList by lazy {\n            val names = ArrayList<String>()\n            val tags = ArrayList<String>()\n\n            names.add(AppContext.getString(R.string.system_default))\n            tags.add(\"\")\n\n            if ((Build.VERSION.SDK_INT == 34 && !isRunningAsStub) || Build.VERSION.SDK_INT >= 35) {\n                // Use platform LocaleConfig parser\n                val config = localeConfig\n                val list = config.supportedLocales ?: LocaleList.getEmptyLocaleList()\n                names.ensureCapacity(list.size() + 1)\n                tags.ensureCapacity(list.size() + 1)\n                for (i in 0 until list.size()) {\n                    val locale = list[i]\n                    names.add(locale.getDisplayName(locale))\n                    tags.add(locale.toLanguageTag())\n                }\n            } else {\n                // Manually parse locale_config.xml\n                val parser = AppContext.resources.getXml(R.xml.locale_config)\n                while (true) {\n                    when (parser.next()) {\n                        XmlPullParser.START_TAG -> {\n                            if (parser.name == \"locale\") {\n                                val tag = parser.getAttributeValue(0)\n                                val locale = Locale.forLanguageTag(tag)\n                                names.add(locale.getDisplayName(locale))\n                                tags.add(tag)\n                            }\n                        }\n                        XmlPullParser.END_DOCUMENT -> break\n                    }\n                }\n            }\n            AppLocaleList(names.toTypedArray(), tags.toTypedArray())\n        }\n\n        @get:RequiresApi(34)\n        val localeConfig: LocaleConfig by lazy {\n            val context = if (isRunningAsStub) {\n                val pkgInfo = AppContext.packageManager.getPackageArchiveInfo(AppApkPath, 0)!!\n                object : ContextWrapper(AppContext) {\n                    override fun getApplicationInfo() = pkgInfo.applicationInfo\n                }\n            } else {\n                AppContext\n            }\n            LocaleConfig.fromContextIgnoringOverride(context)\n        }\n\n        private val localeManagerUsable get() =\n            if (isRunningAsStub) Build.VERSION.SDK_INT >= 35 else Build.VERSION.SDK_INT >= 33\n\n        val useLocaleManager by lazy {\n            localeManagerUsable &&\n                    localeSettingsIntent.resolveActivity(AppContext.packageManager) != null\n        }\n\n        val localeSettingsIntent get() = Intent(\n            Settings.ACTION_APP_LOCALE_SETTINGS,\n            Uri.fromParts(\"package\", AppContext.packageName, null),\n        )\n\n        val instance: LocaleSetting by lazy {\n            // Initialize available locale list\n            available\n            if (useLocaleManager) {\n                Api33Impl()\n            } else if (Build.VERSION.SDK_INT <= 23) {\n                Api23Impl()\n            } else {\n                Api24Impl()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/MediaStoreUtils.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.content.ContentUris\nimport android.content.ContentValues\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.MediaStore\nimport android.provider.OpenableColumns\nimport androidx.annotation.RequiresApi\nimport androidx.core.net.toFile\nimport androidx.core.net.toUri\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.Config\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.IOException\n\n@Suppress(\"DEPRECATION\")\nobject MediaStoreUtils {\n\n    private val cr get() = AppContext.contentResolver\n\n    private fun relativePath(name: String) =\n        if (name.isEmpty()) Environment.DIRECTORY_DOWNLOADS\n        else Environment.DIRECTORY_DOWNLOADS + File.separator + name\n\n    fun fullPath(name: String): String =\n        File(Environment.getExternalStorageDirectory(), relativePath(name)).canonicalPath\n\n    private val downloadPath get() = relativePath(Config.downloadDir)\n\n    @RequiresApi(api = 30)\n    @Throws(IOException::class)\n    private fun insertFile(displayName: String): MediaStoreFile {\n        val values = ContentValues()\n        values.put(MediaStore.MediaColumns.RELATIVE_PATH, downloadPath)\n        values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)\n\n        // When a file with the same name exists and was not created by us:\n        // - Before Android 11, insert will return null\n        // - On Android 11+, the system will automatically create a new name\n        // Thus the reason to restrict this method call to API 30+\n        val fileUri = cr.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)\n            ?: throw IOException(\"Can't insert $displayName.\")\n\n        val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)\n        cr.query(fileUri, projection, null, null, null)?.use { cursor ->\n            val idIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)\n            val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)\n            if (cursor.moveToFirst()) {\n                val id = cursor.getLong(idIndex)\n                val data = cursor.getString(dataColumn)\n                return MediaStoreFile(id, data)\n            }\n        }\n\n        throw IOException(\"Can't insert $displayName.\")\n    }\n\n    @RequiresApi(api = 29)\n    private fun queryFile(displayName: String): UriFile? {\n        val projection = arrayOf(MediaStore.MediaColumns._ID, MediaStore.MediaColumns.DATA)\n        // Before Android 10, we wrote the DISPLAY_NAME field when insert, so it can be used.\n        val selection = \"${MediaStore.MediaColumns.DISPLAY_NAME} == ?\"\n        val selectionArgs = arrayOf(displayName)\n        val sortOrder = \"${MediaStore.MediaColumns.DATE_ADDED} DESC\"\n        val query = cr.query(\n            MediaStore.Downloads.EXTERNAL_CONTENT_URI,\n            projection, selection, selectionArgs, sortOrder)\n        query?.use { cursor ->\n            val idColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)\n            val dataColumn = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA)\n            while (cursor.moveToNext()) {\n                val id = cursor.getLong(idColumn)\n                val data = cursor.getString(dataColumn)\n                if (data.endsWith(downloadPath + File.separator + displayName)) {\n                    return MediaStoreFile(id, data)\n                }\n            }\n        }\n        return null\n    }\n\n    @Throws(IOException::class)\n    fun getFile(displayName: String): UriFile {\n        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {\n            // Fallback to file based I/O pre Android 11\n            val parent = File(Environment.getExternalStorageDirectory(), downloadPath)\n            parent.mkdirs()\n            LegacyUriFile(File(parent, displayName))\n        } else {\n            queryFile(displayName) ?: insertFile(displayName)\n        }\n    }\n\n    fun Uri.inputStream() = cr.openInputStream(this) ?: throw FileNotFoundException()\n\n    fun Uri.outputStream() = cr.openOutputStream(this, \"rwt\") ?: throw FileNotFoundException()\n\n    val Uri.displayName: String get() {\n        if (scheme == \"file\") {\n            // Simple uri wrapper over file, directly get file name\n            return toFile().name\n        }\n        require(scheme == \"content\") { \"Uri lacks 'content' scheme: $this\" }\n        val projection = arrayOf(OpenableColumns.DISPLAY_NAME)\n        cr.query(this, projection, null, null, null)?.use { cursor ->\n            val displayNameColumn = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)\n            if (cursor.moveToFirst()) {\n                return cursor.getString(displayNameColumn)\n            }\n        }\n        return this.toString()\n    }\n\n    interface UriFile {\n        val uri: Uri\n        fun delete(): Boolean\n    }\n\n    private class LegacyUriFile(private val file: File) : UriFile {\n        override val uri = file.toUri()\n        override fun delete() = file.delete()\n        override fun toString() = file.toString()\n    }\n\n    @RequiresApi(api = 29)\n    private class MediaStoreFile(private val id: Long, private val data: String) : UriFile {\n        override val uri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id)\n        override fun toString() = data\n        override fun delete(): Boolean {\n            val selection = \"${MediaStore.MediaColumns._ID} == ?\"\n            val selectionArgs = arrayOf(id.toString())\n            return cr.delete(uri, selection, selectionArgs) == 1\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/NetworkObserver.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.net.NetworkRequest\nimport android.os.PowerManager\nimport androidx.collection.ArraySet\nimport androidx.core.content.getSystemService\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.ktx.registerRuntimeReceiver\n\nclass NetworkObserver(context: Context) {\n    private val manager = context.getSystemService<ConnectivityManager>()!!\n\n    private val networkCallback = object : ConnectivityManager.NetworkCallback() {\n        private val activeList = ArraySet<Network>()\n\n        override fun onAvailable(network: Network) {\n            activeList.add(network)\n            postValue(true)\n        }\n        override fun onLost(network: Network) {\n            activeList.remove(network)\n            postValue(!activeList.isEmpty())\n        }\n    }\n\n    private val receiver = object : BroadcastReceiver() {\n        private fun Context.isIdleMode(): Boolean {\n            val pwm = getSystemService<PowerManager>() ?: return true\n            val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)\n            return pwm.isDeviceIdleMode && !isIgnoringOptimizations\n        }\n        override fun onReceive(context: Context, intent: Intent) {\n            if (context.isIdleMode()) {\n                postValue(false)\n            } else {\n                postCurrentState()\n            }\n        }\n    }\n\n    init {\n        val request = NetworkRequest.Builder()\n            .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)\n            .build()\n        manager.registerNetworkCallback(request, networkCallback)\n        val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)\n        context.applicationContext.registerRuntimeReceiver(receiver, filter)\n    }\n\n    fun postCurrentState() {\n        postValue(\n            manager.getNetworkCapabilities(manager.activeNetwork)\n                ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) == true\n        )\n    }\n\n    private fun postValue(b: Boolean) {\n        Info.resetUpdate()\n        Info.isConnected.postValue(b)\n    }\n\n    companion object {\n        fun init(context: Context): NetworkObserver {\n            return NetworkObserver(context).apply { postCurrentState() }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/ProgressInputStream.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport java.io.FilterInputStream\nimport java.io.InputStream\n\nclass ProgressInputStream(\n    base: InputStream,\n    val progressEmitter: (Long) -> Unit\n) : FilterInputStream(base) {\n\n    private var bytesRead = 0L\n    private var lastUpdate = 0L\n\n    private fun emitProgress() {\n        val cur = System.currentTimeMillis()\n        if (cur - lastUpdate > 1000) {\n            lastUpdate = cur\n            progressEmitter(bytesRead)\n        }\n    }\n\n    override fun read(): Int {\n        val b = read()\n        if (b >= 0) {\n            bytesRead++\n            emitProgress()\n        }\n        return b\n    }\n\n    override fun read(b: ByteArray): Int {\n        return read(b, 0, b.size)\n    }\n\n    override fun read(b: ByteArray, off: Int, len: Int): Int {\n        val sz = super.read(b, off, len)\n        if (sz > 0) {\n            bytesRead += sz\n            emitProgress()\n        }\n        return sz\n    }\n\n    override fun close() {\n        super.close()\n        progressEmitter(bytesRead)\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/RequestAuthentication.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.app.Activity\nimport android.app.KeyguardManager\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.result.contract.ActivityResultContract\n\nclass RequestAuthentication: ActivityResultContract<Unit, Boolean>() {\n\n    override fun createIntent(context: Context, input: Unit) =\n        context.getSystemService(KeyguardManager::class.java)\n            .createConfirmDeviceCredentialIntent(null, null)\n\n    override fun parseResult(resultCode: Int, intent: Intent?) =\n        resultCode == Activity.RESULT_OK\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/RequestInstall.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.annotation.TargetApi\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.provider.Settings\nimport androidx.activity.result.contract.ActivityResultContract\n\nclass RequestInstall : ActivityResultContract<Unit, Boolean>() {\n\n    @TargetApi(26)\n    override fun createIntent(context: Context, input: Unit): Intent {\n        // This will only be called on API 26+\n        return Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES)\n            .setData(Uri.parse(\"package:${context.packageName}\"))\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?) =\n        resultCode == Activity.RESULT_OK\n\n    override fun getSynchronousResult(\n        context: Context,\n        input: Unit\n    ): SynchronousResult<Boolean>? {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)\n            return SynchronousResult(true)\n        if (context.packageManager.canRequestPackageInstalls())\n            return SynchronousResult(true)\n        return null\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/RootUtils.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.app.ActivityManager\nimport android.content.ComponentName\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.os.IBinder\nimport android.system.Os\nimport androidx.core.content.getSystemService\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.ShellUtils\nimport com.topjohnwu.superuser.ipc.RootService\nimport com.topjohnwu.superuser.nio.FileSystemManager\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport timber.log.Timber\nimport java.io.File\nimport java.util.concurrent.locks.AbstractQueuedSynchronizer\n\nclass RootUtils(stub: Any?) : RootService() {\n\n    private val className: String = stub?.javaClass?.name ?: javaClass.name\n    private lateinit var am: ActivityManager\n\n    constructor() : this(null)\n\n    init {\n        Timber.plant(object : Timber.DebugTree() {\n            override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {\n                super.log(priority, \"Magisk\", message, t)\n            }\n        })\n    }\n\n    override fun onCreate() {\n        am = getSystemService()!!\n    }\n\n    override fun getComponentName(): ComponentName {\n        return ComponentName(packageName, className)\n    }\n\n    override fun onBind(intent: Intent): IBinder {\n        return object : IRootUtils.Stub() {\n            override fun getAppProcess(pid: Int) = safe(null) { getAppProcessImpl(pid) }\n            override fun getFileSystem(): IBinder = FileSystemManager.getService()\n            override fun addSystemlessHosts() = safe(false) { addSystemlessHostsImpl() }\n        }\n    }\n\n    private fun getAppProcessImpl(_pid: Int): ActivityManager.RunningAppProcessInfo? {\n        val procList = am.runningAppProcesses\n        var pid = _pid\n        while (pid > 1) {\n            val proc = procList.find { it.pid == pid }\n            if (proc != null)\n                return proc\n\n            // Stop find when root process\n            if (Os.stat(\"/proc/$pid\").st_uid == 0) {\n                return null\n            }\n\n            // Find PPID\n            File(\"/proc/$pid/status\").useLines {\n                val line = it.find { l -> l.startsWith(\"PPid:\") } ?: return null\n                pid = line.substring(5).trim().toInt()\n            }\n        }\n        return null\n    }\n\n    private fun addSystemlessHostsImpl(): Boolean {\n        val module = File(Const.MODULE_PATH, \"hosts\")\n        if (module.exists()) return true\n        val hosts = File(module, \"system/etc/hosts\")\n        if (!hosts.parentFile.mkdirs()) return false\n        File(module, \"module.prop\").outputStream().writer().use {\n            it.write(\"\"\"\n                id=hosts\n                name=Systemless Hosts\n                version=1.0\n                versionCode=1\n                author=Magisk\n                description=Magisk app built-in systemless hosts module\n            \"\"\".trimIndent())\n        }\n        File(\"/system/etc/hosts\").copyTo(hosts)\n        File(module, \"update\").createNewFile()\n        return true\n    }\n\n    object Connection : AbstractQueuedSynchronizer(), ServiceConnection {\n        init {\n            state = 1\n        }\n\n        override fun onServiceConnected(name: ComponentName, service: IBinder) {\n            Timber.d(\"onServiceConnected\")\n            IRootUtils.Stub.asInterface(service).let {\n                obj = it\n                fs = FileSystemManager.getRemote(it.fileSystem)\n            }\n            releaseShared(1)\n        }\n\n        override fun onServiceDisconnected(name: ComponentName) {\n            state = 1\n            obj = null\n            bind(Intent().setComponent(name), this)\n        }\n\n        override fun tryAcquireShared(acquires: Int) = if (state == 0) 1 else -1\n\n        override fun tryReleaseShared(releases: Int): Boolean {\n            // Decrement count; signal when transition to zero\n            while (true) {\n                val c = state\n                if (c == 0)\n                    return false\n                val n = c - 1\n                if (compareAndSetState(c, n))\n                    return n == 0\n            }\n        }\n\n        fun await() {\n            if (!Info.isRooted)\n                return\n            if (!ShellUtils.onMainThread()) {\n                acquireSharedInterruptibly(1)\n            } else if (state != 0) {\n                throw IllegalStateException(\"Cannot await on the main thread\")\n            }\n        }\n    }\n\n    companion object {\n        var bindTask: Shell.Task? = null\n        var fs: FileSystemManager = FileSystemManager.getLocal()\n            get() {\n                Connection.await()\n                return field\n            }\n            private set\n        private var obj: IRootUtils? = null\n            get() {\n                Connection.await()\n                return field\n            }\n\n        fun getAppProcess(pid: Int) = safe(null) { obj?.getAppProcess(pid) }\n\n        suspend fun addSystemlessHosts() =\n            withContext(Dispatchers.IO) { safe(false) { obj?.addSystemlessHosts() ?: false } }\n\n        private inline fun <T> safe(default: T, block: () -> T): T {\n            return try {\n                block()\n            } catch (e: Throwable) {\n                // The process died unexpectedly\n                Timber.e(e)\n                default\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/utils/ShellInit.kt",
    "content": "package com.topjohnwu.magisk.core.utils\n\nimport android.content.Context\nimport com.topjohnwu.magisk.StubApk\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.cachedFile\nimport com.topjohnwu.magisk.core.ktx.deviceProtectedContext\nimport com.topjohnwu.magisk.core.ktx.writeTo\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.runBlocking\nimport java.io.File\nimport java.util.jar.JarFile\n\nclass ShellInit : Shell.Initializer() {\n    override fun onInit(context: Context, shell: Shell): Boolean {\n        if (shell.isRoot) {\n            Info.isRooted = true\n            RootUtils.bindTask?.let { shell.execTask(it) }\n            RootUtils.bindTask = null\n        }\n        shell.newJob().apply {\n            add(\"export ASH_STANDALONE=1\")\n\n            val localBB: File\n            if (isRunningAsStub) {\n                if (!shell.isRoot)\n                    return true\n                val jar = JarFile(StubApk.current(context))\n                val bb = jar.getJarEntry(\"lib/${Const.CPU_ABI}/libbusybox.so\")\n                localBB = context.deviceProtectedContext.cachedFile(\"busybox\")\n                localBB.delete()\n                runBlocking {\n                    jar.getInputStream(bb).writeTo(localBB, dispatcher = Dispatchers.Unconfined)\n                }\n                localBB.setExecutable(true)\n            } else {\n                localBB = File(context.applicationInfo.nativeLibraryDir, \"libbusybox.so\")\n            }\n\n            if (shell.isRoot) {\n                add(\"export MAGISKTMP=\\$(magisk --path)\")\n                // Test if we can properly execute stuff in /data\n                Info.noDataExec = !shell.newJob()\n                    .add(\"$localBB sh -c '$localBB true'\").exec().isSuccess\n            }\n\n            if (Info.noDataExec) {\n                // Copy it out of /data to workaround Samsung bullshit\n                add(\n                    \"if [ -x \\$MAGISKTMP/.magisk/busybox/busybox ]; then\",\n                    \"  cp -af $localBB \\$MAGISKTMP/.magisk/busybox/busybox\",\n                    \"  exec \\$MAGISKTMP/.magisk/busybox/busybox sh\",\n                    \"else\",\n                    \"  cp -af $localBB /dev/busybox\",\n                    \"  exec /dev/busybox sh\",\n                    \"fi\"\n                )\n            } else {\n                // Directly execute the file\n                add(\"exec $localBB sh\")\n            }\n\n            add(context.assets.open(\"app_functions.sh\"))\n            if (shell.isRoot) {\n                add(context.assets.open(\"util_functions.sh\"))\n            }\n        }.exec()\n\n        Info.init(shell)\n        return true\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/view/Notifications.kt",
    "content": "package com.topjohnwu.magisk.view\n\nimport android.annotation.SuppressLint\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.app.PendingIntent\nimport android.os.Build\nimport android.os.Build.VERSION.SDK_INT\nimport androidx.core.content.getSystemService\nimport androidx.core.graphics.drawable.toIcon\nimport com.topjohnwu.magisk.core.AppContext\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.download.DownloadEngine\nimport com.topjohnwu.magisk.core.download.Subject\nimport com.topjohnwu.magisk.core.ktx.getBitmap\nimport com.topjohnwu.magisk.core.ktx.selfLaunchIntent\nimport java.util.concurrent.atomic.AtomicInteger\n\n@Suppress(\"DEPRECATION\")\nobject Notifications {\n\n    val mgr by lazy { AppContext.getSystemService<NotificationManager>()!! }\n\n    private const val APP_UPDATED_ID = 4\n    private const val APP_UPDATE_AVAILABLE_ID = 5\n\n    private const val UPDATE_CHANNEL = \"update\"\n    private const val PROGRESS_CHANNEL = \"progress\"\n    private const val UPDATED_CHANNEL = \"updated\"\n    private const val SU_CHANNEL = \"su_notification\"\n\n    private val nextId = AtomicInteger(APP_UPDATE_AVAILABLE_ID)\n\n    fun setup() {\n        AppContext.apply {\n            if (SDK_INT >= Build.VERSION_CODES.O) {\n                val channel = NotificationChannel(UPDATE_CHANNEL,\n                    getString(R.string.update_channel), NotificationManager.IMPORTANCE_DEFAULT)\n                val channel2 = NotificationChannel(PROGRESS_CHANNEL,\n                    getString(R.string.progress_channel), NotificationManager.IMPORTANCE_LOW)\n                val channel3 = NotificationChannel(UPDATED_CHANNEL,\n                    getString(R.string.updated_channel), NotificationManager.IMPORTANCE_HIGH)\n                val channel4 = NotificationChannel(SU_CHANNEL,\n                    getString(R.string.su_notification_channel), NotificationManager.IMPORTANCE_HIGH)\n                mgr.createNotificationChannels(listOf(channel, channel2, channel3, channel4))\n            }\n        }\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    fun updateDone() {\n        AppContext.apply {\n            val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n            val pending = PendingIntent.getActivity(this, 0, selfLaunchIntent(), flag)\n            val builder = if (SDK_INT >= Build.VERSION_CODES.O) {\n                Notification.Builder(this, UPDATED_CHANNEL)\n                    .setSmallIcon(getBitmap(R.drawable.ic_magisk_outline).toIcon())\n            } else {\n                Notification.Builder(this).setPriority(Notification.PRIORITY_HIGH)\n                    .setSmallIcon(R.drawable.ic_magisk_outline)\n            }\n                .setContentIntent(pending)\n                .setContentTitle(getText(R.string.updated_title))\n                .setContentText(getText(R.string.updated_text))\n                .setAutoCancel(true)\n            mgr.notify(APP_UPDATED_ID, builder.build())\n        }\n    }\n\n    fun updateAvailable() {\n        AppContext.apply {\n            val intent = DownloadEngine.getPendingIntent(this, Subject.App())\n            val bitmap = getBitmap(R.drawable.ic_magisk_outline)\n            val builder = if (SDK_INT >= Build.VERSION_CODES.O) {\n                Notification.Builder(this, UPDATE_CHANNEL)\n                    .setSmallIcon(bitmap.toIcon())\n            } else {\n                Notification.Builder(this)\n                    .setSmallIcon(R.drawable.ic_magisk_outline)\n            }\n                .setLargeIcon(bitmap)\n                .setContentTitle(getString(R.string.magisk_update_title))\n                .setContentText(getString(R.string.manager_download_install))\n                .setAutoCancel(true)\n                .setContentIntent(intent)\n\n            mgr.notify(APP_UPDATE_AVAILABLE_ID, builder.build())\n        }\n    }\n\n    fun startProgress(title: CharSequence): Notification.Builder {\n        val builder = if (SDK_INT >= Build.VERSION_CODES.O) {\n            Notification.Builder(AppContext, PROGRESS_CHANNEL)\n        } else {\n            Notification.Builder(AppContext).setPriority(Notification.PRIORITY_LOW)\n        }\n            .setSmallIcon(android.R.drawable.stat_sys_download)\n            .setContentTitle(title)\n            .setProgress(0, 0, true)\n            .setOngoing(true)\n        if (SDK_INT >= Build.VERSION_CODES.S)\n            builder.setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE)\n        return builder\n    }\n\n    private const val SU_NOTIFICATION_TIMEOUT_MS = 3_000L\n\n    @SuppressLint(\"InlinedApi\")\n    fun suNotification(granted: Boolean, appName: String) {\n        AppContext.apply {\n            val flag = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT\n            val pending = PendingIntent.getActivity(this, 0, selfLaunchIntent(), flag)\n            val title = getString(\n                if (granted) R.string.su_notification_granted_title\n                else R.string.su_notification_denied_title\n            )\n            val text = getString(\n                if (granted) R.string.su_allow_toast\n                else R.string.su_deny_toast,\n                appName\n            )\n            val builder = if (SDK_INT >= Build.VERSION_CODES.O) {\n                Notification.Builder(this, SU_CHANNEL)\n                    .setSmallIcon(getBitmap(R.drawable.ic_magisk_outline).toIcon())\n            } else {\n                Notification.Builder(this).setPriority(Notification.PRIORITY_HIGH)\n                    .setSmallIcon(R.drawable.ic_magisk_outline)\n            }\n                .setContentIntent(pending)\n                .setContentTitle(title)\n                .setContentText(text)\n                .setAutoCancel(true)\n                .setTimeoutAfter(SU_NOTIFICATION_TIMEOUT_MS)\n            mgr.notify(nextId(), builder.build())\n        }\n    }\n\n    fun nextId() = nextId.incrementAndGet()\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/core/view/Shortcuts.kt",
    "content": "package com.topjohnwu.magisk.view\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ShortcutInfo\nimport android.content.pm.ShortcutManager\nimport android.graphics.drawable.Icon\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport androidx.core.content.getSystemService\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.R\nimport com.topjohnwu.magisk.core.isRunningAsStub\nimport com.topjohnwu.magisk.core.ktx.getBitmap\n\nobject Shortcuts {\n\n    fun setupDynamic(context: Context) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {\n            val manager = context.getSystemService<ShortcutManager>() ?: return\n            manager.dynamicShortcuts = getShortCuts(context)\n        }\n    }\n\n    fun addHomeIcon(context: Context) {\n        val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return\n        val info = ShortcutInfoCompat.Builder(context, Const.Nav.HOME)\n            .setShortLabel(context.getString(R.string.magisk))\n            .setIntent(intent)\n            .setIcon(context.getIconCompat(R.drawable.ic_launcher))\n            .build()\n        ShortcutManagerCompat.requestPinShortcut(context, info, null)\n    }\n\n    private fun Context.getIcon(id: Int): Icon {\n        return if (isRunningAsStub) {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)\n                Icon.createWithAdaptiveBitmap(getBitmap(id))\n            else\n                Icon.createWithBitmap(getBitmap(id))\n        } else {\n            Icon.createWithResource(this, id)\n        }\n    }\n\n    private fun Context.getIconCompat(id: Int): IconCompat {\n        return if (isRunningAsStub) {\n            val bitmap = getBitmap(id)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)\n                IconCompat.createWithAdaptiveBitmap(bitmap)\n            else\n                IconCompat.createWithBitmap(bitmap)\n        } else {\n            IconCompat.createWithResource(this, id)\n        }\n    }\n\n    @RequiresApi(api = 25)\n    private fun getShortCuts(context: Context): List<ShortcutInfo> {\n        val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)\n            ?: return emptyList()\n\n        val shortCuts = mutableListOf<ShortcutInfo>()\n\n        if (Info.showSuperUser) {\n            shortCuts.add(\n                ShortcutInfo.Builder(context, Const.Nav.SUPERUSER)\n                    .setShortLabel(context.getString(R.string.superuser))\n                    .setIntent(\n                        Intent(intent).putExtra(Const.Key.OPEN_SECTION, Const.Nav.SUPERUSER)\n                    )\n                    .setIcon(context.getIcon(R.drawable.sc_superuser))\n                    .setRank(0)\n                    .build()\n            )\n        }\n        if (Info.env.isActive) {\n            shortCuts.add(\n                ShortcutInfo.Builder(context, Const.Nav.MODULES)\n                    .setShortLabel(context.getString(R.string.modules))\n                    .setIntent(\n                        Intent(intent).putExtra(Const.Key.OPEN_SECTION, Const.Nav.MODULES)\n                    )\n                    .setIcon(context.getIcon(R.drawable.sc_extension))\n                    .setRank(1)\n                    .build()\n            )\n        }\n        return shortCuts\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.os.ParcelFileDescriptor.AutoCloseInputStream\nimport androidx.annotation.Keep\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.uiautomator.By\nimport androidx.test.uiautomator.Until\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.magisk.test.Environment.Companion.EMPTY_ZYGISK\nimport com.topjohnwu.magisk.test.Environment.Companion.INVALID_ZYGISK\nimport com.topjohnwu.magisk.test.Environment.Companion.MOUNT_TEST\nimport com.topjohnwu.magisk.test.Environment.Companion.REMOVE_TEST\nimport com.topjohnwu.magisk.test.Environment.Companion.SEPOLICY_RULE\nimport com.topjohnwu.magisk.test.Environment.Companion.UPGRADE_TEST\nimport com.topjohnwu.superuser.Shell\nimport kotlinx.coroutines.runBlocking\nimport org.junit.After\nimport org.junit.Assert.assertArrayEquals\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertFalse\nimport org.junit.Assert.assertNotNull\nimport org.junit.Assert.assertNull\nimport org.junit.Assert.assertTrue\nimport org.junit.Assume.assumeTrue\nimport org.junit.BeforeClass\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport java.util.concurrent.TimeUnit\nimport java.util.regex.Pattern\n\n@Keep\n@RunWith(AndroidJUnit4::class)\nclass AdditionalTest : BaseTest {\n\n    companion object {\n        private const val SHELL_PKG = \"com.android.shell\"\n        private const val LSPOSED_CATEGORY = \"org.lsposed.manager.LAUNCH_MANAGER\"\n        private const val LSPOSED_PKG = \"org.lsposed.manager\"\n\n        private lateinit var modules: List<LocalModule>\n\n        @BeforeClass\n        @JvmStatic\n        fun before() {\n            BaseTest.prerequisite()\n            runBlocking {\n                modules = LocalModule.installed()\n            }\n        }\n    }\n\n    @After\n    fun teardown() {\n        device.pressHome()\n    }\n\n    @Test\n    fun testModuleCount() {\n        var expected = 4\n        if (Environment.mount()) expected++\n        if (Environment.preinit()) expected++\n        if (Environment.lsposed()) expected++\n        if (Environment.shamiko()) expected++\n        assertEquals(\"Module count incorrect\", expected, modules.size)\n    }\n\n    @Test\n    fun testLsposed() {\n        assumeTrue(Environment.lsposed())\n\n        val module = modules.find { it.id == \"zygisk_lsposed\" }\n        assertNotNull(\"zygisk_lsposed is not installed\", module)\n        module!!\n        assertFalse(\"zygisk_lsposed is not enabled\", module.zygiskUnloaded)\n\n        // Launch lsposed manager to ensure the module is active\n        uiAutomation.executeShellCommand(\n            \"am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity\"\n        ).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } }\n\n        val pattern = Pattern.compile(\"$LSPOSED_PKG:id/.*\")\n        assertNotNull(\n            \"LSPosed manager launch failed\",\n            device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10))\n        )\n    }\n\n    @Test\n    fun testModuleMount() {\n        assumeTrue(Environment.mount())\n\n        assertNotNull(\"$MOUNT_TEST is not installed\", modules.find { it.id == MOUNT_TEST })\n        assertTrue(\n            \"/system/fonts/newfile should exist\",\n            RootUtils.fs.getFile(\"/system/fonts/newfile\").exists()\n        )\n        assertFalse(\n            \"/system/bin/screenrecord should not exist\",\n            RootUtils.fs.getFile(\"/system/bin/screenrecord\").exists()\n        )\n        val egg = RootUtils.fs.getFile(\"/system/app/EasterEgg\").list() ?: arrayOf()\n        assertArrayEquals(\n            \"/system/app/EasterEgg should be replaced\",\n            egg,\n            arrayOf(\"newfile\")\n        )\n    }\n\n    @Test\n    fun testSepolicyRule() {\n        assumeTrue(Environment.preinit())\n\n        assertNotNull(\"$SEPOLICY_RULE is not installed\", modules.find { it.id == SEPOLICY_RULE })\n        assertTrue(\n            \"Module sepolicy.rule is not applied\",\n            Shell.cmd(\"magiskpolicy --print-rules | grep -q magisk_test\").exec().isSuccess\n        )\n    }\n\n    @Test\n    fun testEmptyZygiskModule() {\n        val module = modules.find { it.id == EMPTY_ZYGISK }\n        assertNotNull(\"$EMPTY_ZYGISK is not installed\", module)\n        module!!\n        assertTrue(\"$EMPTY_ZYGISK should be zygisk unloaded\", module.zygiskUnloaded)\n    }\n\n    @Test\n    fun testInvalidZygiskModule() {\n        val module = modules.find { it.id == INVALID_ZYGISK }\n        assertNotNull(\"$INVALID_ZYGISK is not installed\", module)\n        module!!\n        assertTrue(\"$INVALID_ZYGISK should be zygisk unloaded\", module.zygiskUnloaded)\n    }\n\n    @Test\n    fun testRemoveModule() {\n        assertNull(\"$REMOVE_TEST should be removed\", modules.find { it.id == REMOVE_TEST })\n        assertTrue(\n            \"Uninstaller of $REMOVE_TEST should be run\",\n            RootUtils.fs.getFile(Environment.REMOVE_TEST_MARKER).exists()\n        )\n    }\n\n    @Test\n    fun testModuleUpgrade() {\n        val module = modules.find { it.id == UPGRADE_TEST }\n        assertNotNull(\"$UPGRADE_TEST is not installed\", module)\n        module!!\n        assertFalse(\"$UPGRADE_TEST should be disabled\", module.enable)\n        assertTrue(\n            \"$UPGRADE_TEST should be updated\",\n            module.base.getChildFile(\"post-fs-data.sh\").exists()\n        )\n        assertFalse(\n            \"$UPGRADE_TEST should be updated\",\n            module.base.getChildFile(\"service.sh\").exists()\n        )\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/test/BaseTest.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.app.Instrumentation\nimport android.app.UiAutomation\nimport android.content.Context\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.uiautomator.UiDevice\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.superuser.Shell\nimport org.junit.Assert.assertTrue\n\ninterface BaseTest {\n    val instrumentation: Instrumentation\n        get() = InstrumentationRegistry.getInstrumentation()\n    val appContext: Context get() = instrumentation.targetContext\n    val testContext: Context get() = instrumentation.context\n    val uiAutomation: UiAutomation get() = instrumentation.uiAutomation\n    val device: UiDevice get() = UiDevice.getInstance(instrumentation)\n\n    companion object {\n        fun prerequisite() {\n            assertTrue(\"Should have root access\", Shell.getShell().isRoot)\n            // Make sure the root service is running\n            RootUtils.Connection.await()\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.app.Notification\nimport android.os.Build\nimport androidx.annotation.Keep\nimport androidx.core.net.toUri\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME\nimport com.topjohnwu.magisk.core.Const\nimport com.topjohnwu.magisk.core.download.DownloadNotifier\nimport com.topjohnwu.magisk.core.download.DownloadProcessor\nimport com.topjohnwu.magisk.core.ktx.cachedFile\nimport com.topjohnwu.magisk.core.model.module.LocalModule\nimport com.topjohnwu.magisk.core.tasks.AppMigration\nimport com.topjohnwu.magisk.core.tasks.FlashZip\nimport com.topjohnwu.magisk.core.tasks.MagiskInstaller\nimport com.topjohnwu.magisk.core.utils.RootUtils\nimport com.topjohnwu.superuser.CallbackList\nimport com.topjohnwu.superuser.Shell\nimport com.topjohnwu.superuser.nio.ExtendedFile\nimport kotlinx.coroutines.runBlocking\nimport org.apache.commons.compress.archivers.zip.ZipFile\nimport org.junit.Assert.assertArrayEquals\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertTrue\nimport org.junit.BeforeClass\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport timber.log.Timber\nimport java.io.File\nimport java.io.PrintStream\n\n@Keep\n@RunWith(AndroidJUnit4::class)\nclass Environment : BaseTest {\n\n    companion object {\n        @BeforeClass\n        @JvmStatic\n        fun before() = BaseTest.prerequisite()\n\n        // The kernel running on emulators < API 26 does not play well with\n        // magic mount. Skip mount_test on those legacy platforms.\n        fun mount(): Boolean {\n            return Build.VERSION.SDK_INT >= 26\n        }\n\n        // It is possible that there are no suitable preinit partition to use\n        fun preinit(): Boolean {\n            return Shell.cmd(\"magisk --preinit-device\").exec().isSuccess\n        }\n\n        fun lsposed(): Boolean {\n            return Build.VERSION.SDK_INT in 27..34\n        }\n\n        fun shamiko(): Boolean {\n            return Build.VERSION.SDK_INT >= 27\n        }\n\n        private const val MODULE_UPDATE_PATH  = \"/data/adb/modules_update\"\n        private const val MODULE_ERROR = \"Module zip processing incorrect\"\n        const val MOUNT_TEST = \"mount_test\"\n        const val SEPOLICY_RULE = \"sepolicy_rule\"\n        const val INVALID_ZYGISK = \"invalid_zygisk\"\n        const val REMOVE_TEST = \"remove_test\"\n        const val REMOVE_TEST_MARKER = \"/dev/.remove_test_removed\"\n        const val EMPTY_ZYGISK = \"empty_zygisk\"\n        const val UPGRADE_TEST = \"upgrade_test\"\n    }\n\n    object TimberLog : CallbackList<String>(Runnable::run) {\n        override fun onAddElement(e: String) {\n            Timber.i(e)\n        }\n    }\n\n    private fun checkModuleZip(file: File) {\n        // Make sure module processing is correct\n        ZipFile.Builder().setFile(file).get().use { zip ->\n            val meta = zip.entries\n                .asSequence()\n                .filter { it.name.startsWith(\"META-INF\") }\n                .toMutableList()\n            assertEquals(MODULE_ERROR, 6, meta.size)\n\n            val binary = zip.getInputStream(\n                zip.getEntry(\"META-INF/com/google/android/update-binary\")\n            ).use { it.readBytes() }\n            val ref = appContext.assets.open(\"module_installer.sh\").use { it.readBytes() }\n            assertArrayEquals(MODULE_ERROR, ref, binary)\n\n            val script = zip.getInputStream(\n                zip.getEntry(\"META-INF/com/google/android/updater-script\")\n            ).use { it.readBytes() }\n            assertArrayEquals(MODULE_ERROR, \"#MAGISK\\n\".toByteArray(), script)\n        }\n    }\n\n    private fun setupMountTest(root: ExtendedFile) {\n        val error = \"$MOUNT_TEST setup failed\"\n        val path = root.getChildFile(MOUNT_TEST)\n\n        // Create /system/fonts/newfile\n        val etc = path.getChildFile(\"system\").getChildFile(\"fonts\")\n        assertTrue(error, etc.mkdirs())\n        assertTrue(error, etc.getChildFile(\"newfile\").createNewFile())\n\n        // Create /system/app/EasterEgg/.replace\n        val egg = path.getChildFile(\"system\").getChildFile(\"app\").getChildFile(\"EasterEgg\")\n        assertTrue(error, egg.mkdirs())\n        assertTrue(error, egg.getChildFile(\".replace\").createNewFile())\n\n        // Create /system/app/EasterEgg/newfile\n        assertTrue(error, egg.getChildFile(\"newfile\").createNewFile())\n\n        // Delete /system/bin/screenrecord\n        val bin = path.getChildFile(\"system\").getChildFile(\"bin\")\n        assertTrue(error, bin.mkdirs())\n        assertTrue(error, Shell.cmd(\"mknod $bin/screenrecord c 0 0\").exec().isSuccess)\n\n        assertTrue(error, Shell.cmd(\"set_default_perm $path\").exec().isSuccess)\n    }\n\n    private fun setupSystemlessHost() {\n        val error = \"hosts setup failed\"\n        assertTrue(error, runBlocking { RootUtils.addSystemlessHosts() })\n        assertTrue(error, RootUtils.fs.getFile(Const.MODULE_PATH).getChildFile(\"hosts\").exists())\n    }\n\n    private fun setupSepolicyRuleModule(root: ExtendedFile) {\n        val error = \"$SEPOLICY_RULE setup failed\"\n        val path = root.getChildFile(SEPOLICY_RULE)\n        assertTrue(error, path.mkdirs())\n\n        // Add sepolicy patch\n        PrintStream(path.getChildFile(\"sepolicy.rule\").newOutputStream()).use {\n            it.println(\"type magisk_test domain\")\n        }\n\n        assertTrue(error, Shell.cmd(\n            \"set_default_perm $path\",\n            \"copy_preinit_files\"\n        ).exec().isSuccess)\n    }\n\n    private fun setupEmptyZygiskModule(root: ExtendedFile) {\n        val error = \"$EMPTY_ZYGISK setup failed\"\n        val path = root.getChildFile(EMPTY_ZYGISK)\n\n        // Create an empty zygisk folder\n        val module = LocalModule(path)\n        assertTrue(error, module.zygiskFolder.mkdirs())\n    }\n\n    private fun setupInvalidZygiskModule(root: ExtendedFile) {\n        val error = \"$INVALID_ZYGISK setup failed\"\n        val path = root.getChildFile(INVALID_ZYGISK)\n\n        // Create invalid zygisk libraries\n        val module = LocalModule(path)\n        assertTrue(error, module.zygiskFolder.mkdirs())\n        assertTrue(error, module.zygiskFolder.getChildFile(\"armeabi-v7a.so\").createNewFile())\n        assertTrue(error, module.zygiskFolder.getChildFile(\"arm64-v8a.so\").createNewFile())\n        assertTrue(error, module.zygiskFolder.getChildFile(\"x86.so\").createNewFile())\n        assertTrue(error, module.zygiskFolder.getChildFile(\"x86_64.so\").createNewFile())\n\n        assertTrue(error, Shell.cmd(\"set_default_perm $path\").exec().isSuccess)\n    }\n\n    private fun setupRemoveModule(root: ExtendedFile) {\n        val error = \"$REMOVE_TEST setup failed\"\n        val path = root.getChildFile(REMOVE_TEST)\n\n        // Create a new module but mark is as \"remove\"\n        val module = LocalModule(path)\n        assertTrue(error, path.mkdirs())\n        // Create uninstaller script\n        path.getChildFile(\"uninstall.sh\").newOutputStream().writer().use {\n            it.write(\"touch $REMOVE_TEST_MARKER\")\n        }\n        assertTrue(error, path.getChildFile(\"service.sh\").createNewFile())\n        module.remove = true\n\n        assertTrue(error, Shell.cmd(\"set_default_perm $path\").exec().isSuccess)\n    }\n\n    private fun setupUpgradeModule(root: ExtendedFile, update: ExtendedFile) {\n        val error = \"$UPGRADE_TEST setup failed\"\n        val oldPath = root.getChildFile(UPGRADE_TEST)\n        val newPath = update.getChildFile(UPGRADE_TEST)\n\n        // Create an existing module but mark as \"disable\n        val module = LocalModule(oldPath)\n        assertTrue(error, oldPath.mkdirs())\n        module.enable = false\n        // Install service.sh into the old module\n        assertTrue(error, oldPath.getChildFile(\"service.sh\").createNewFile())\n\n        // Create an upgrade module\n        assertTrue(error, newPath.mkdirs())\n        // Install post-fs-data.sh into the new module\n        assertTrue(error, newPath.getChildFile(\"post-fs-data.sh\").createNewFile())\n\n        assertTrue(error, Shell.cmd(\n            \"set_default_perm $oldPath\",\n            \"set_default_perm $newPath\",\n        ).exec().isSuccess)\n    }\n\n    @Test\n    fun setupEnvironment() {\n        runBlocking {\n            assertTrue(\n                \"Magisk setup failed\",\n                MagiskInstaller.Emulator(TimberLog, TimberLog).exec()\n            )\n        }\n\n        val notify = object : DownloadNotifier {\n            override val context = appContext\n            override fun notifyUpdate(id: Int, editor: (Notification.Builder) -> Unit) {}\n        }\n        val processor = DownloadProcessor(notify)\n\n        val shamiko = appContext.cachedFile(\"shamiko.zip\")\n        runBlocking {\n            testContext.assets.open(\"shamiko.zip\").use {\n                processor.handleModule(it, shamiko.toUri())\n            }\n            checkModuleZip(shamiko)\n            if (shamiko()) {\n                assertTrue(\n                    \"Shamiko installation failed\",\n                    FlashZip(shamiko.toUri(), TimberLog, TimberLog).exec()\n                )\n            }\n        }\n\n        val lsp = appContext.cachedFile(\"lsposed.zip\")\n        runBlocking {\n            testContext.assets.open(\"lsposed.zip\").use {\n                processor.handleModule(it, lsp.toUri())\n            }\n            checkModuleZip(lsp)\n            if (lsposed()) {\n                assertTrue(\n                    \"LSPosed installation failed\",\n                    FlashZip(lsp.toUri(), TimberLog, TimberLog).exec()\n                )\n            }\n        }\n\n        val root = RootUtils.fs.getFile(Const.MODULE_PATH)\n        val update = RootUtils.fs.getFile(MODULE_UPDATE_PATH)\n        if (mount()) { setupMountTest(update) }\n        if (preinit()) { setupSepolicyRuleModule(update) }\n        setupSystemlessHost()\n        setupEmptyZygiskModule(update)\n        setupInvalidZygiskModule(update)\n        setupRemoveModule(root)\n        setupUpgradeModule(root, update)\n    }\n\n    @Test\n    fun setupAppHide() {\n        runBlocking {\n            assertTrue(\n                \"App hiding failed\",\n                AppMigration.patchAndHide(\n                    context = appContext,\n                    label = \"Settings\",\n                    pkg = \"repackaged.$APP_PACKAGE_NAME\"\n                )\n            )\n        }\n    }\n\n    @Test\n    fun setupAppRestore() {\n        runBlocking {\n            assertTrue(\n                \"App restoration failed\",\n                AppMigration.restoreApp(appContext)\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/java/com/topjohnwu/magisk/test/MagiskAppTest.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Build\nimport android.os.ParcelFileDescriptor.AutoCloseInputStream\nimport androidx.annotation.Keep\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport com.topjohnwu.magisk.core.Config\nimport com.topjohnwu.magisk.core.Info\nimport com.topjohnwu.magisk.core.di.ServiceLocator\nimport com.topjohnwu.magisk.core.model.su.SuPolicy\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertNotNull\nimport org.junit.Assert.assertTrue\nimport org.junit.BeforeClass\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport java.util.concurrent.TimeUnit\n\n@Keep\n@RunWith(AndroidJUnit4::class)\nclass MagiskAppTest : BaseTest {\n\n    companion object {\n        @BeforeClass\n        @JvmStatic\n        fun before() = BaseTest.prerequisite()\n    }\n\n    @Test\n    fun testZygisk() {\n        assertTrue(\"Zygisk should be enabled\", Info.isZygiskEnabled)\n    }\n\n    @Test\n    fun testSuRequest() {\n        // Bypass the need to actually show a dialog\n        Config.suAutoResponse = Config.Value.SU_AUTO_ALLOW\n        Config.prefs.edit().commit()\n\n        // Inject an undetermined + mute logging policy for ADB shell\n        val policy = SuPolicy(\n            uid = 2000,\n            logging = false,\n            notification = false,\n            remain = 0L\n        )\n        runBlocking {\n            ServiceLocator.policyDB.update(policy)\n        }\n\n        val filter = IntentFilter(Intent.ACTION_VIEW)\n        filter.addCategory(Intent.CATEGORY_DEFAULT)\n        val monitor = instrumentation.addMonitor(filter, null, false)\n\n        // Try to call su from ADB shell\n        val cmd = if (Build.VERSION.SDK_INT < 24) {\n            // API 23 runs executeShellCommand as root\n            \"/system/xbin/su 2000 su -c id\"\n        } else {\n            \"su -c id\"\n        }\n        val pfd = uiAutomation.executeShellCommand(cmd)\n\n        // Make sure SuRequestActivity is launched\n        val suRequest = monitor.waitForActivityWithTimeout(TimeUnit.SECONDS.toMillis(10))\n        assertNotNull(\"SuRequestActivity is not launched\", suRequest)\n\n        // Check that the request went through\n        AutoCloseInputStream(pfd).reader().use {\n            assertTrue(\n                \"Cannot grant root permission from shell\",\n                it.readText().contains(\"uid=0\")\n            )\n        }\n\n        // Check that the database is updated\n        runBlocking {\n            val policy = ServiceLocator.policyDB.fetch(2000)\n                ?: throw AssertionError(\"PolicyDB is invalid\")\n            assertEquals(\"Policy for shell is incorrect\", SuPolicy.ALLOW, policy.policy)\n        }\n    }\n}\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_extension.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"@color/dark\"\n        android:pathData=\"M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_favorite.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_fingerprint.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_github.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24.0dip\"\n        android:height=\"24.0dip\"\n        android:viewportHeight=\"24.0\"\n        android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#757575\"\n        android:pathData=\"M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z\"/>\n</vector>"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_logo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"oval\">\n            <solid android:color=\"#00AF9C\"/>\n        </shape>\n    </item>\n\n    <item android:drawable=\"@drawable/ic_magisk\" />\n\n</layer-list>"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_magisk.xml",
    "content": "<vector android:height=\"48dp\" android:viewportHeight=\"720\"\n    android:viewportWidth=\"720\" android:width=\"48dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#303030\" android:pathData=\"M332.48,421.18c0,0 3.77,22.45 -0.82,71.95c-5.76,62.06 23.64,160.64 23.64,160.64c0,0 40.1,-98.78 33.1,-162.59c-5.75,-52.45 2.6,-70.79 0.82,-68.33c-30.81,42.57 -56.75,-1.67 -56.75,-1.67z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M407.6,474.45c5.01,38.77 -0.57,60.01 -7.81,101.51c-3.66,20.99 74.78,-63.1 104.86,-113.23c5.02,-8.36 -28.77,32.6 -62.19,3.35c-23.18,-20.28 -27.16,-26.44 -45.18,-44.06c-6.08,-5.94 6.74,24.72 10.32,52.43z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M321.99,425.09c-18.02,17.62 -22,23.78 -45.18,44.06c-33.42,29.25 -67.21,-11.71 -62.19,-3.35c30.08,50.13 108.52,134.22 104.86,113.23c-7.24,-41.5 -12.82,-62.74 -7.81,-101.51c3.58,-27.71 16.4,-58.37 10.32,-52.43z\"/>\n    <path android:fillColor=\"#303030\" android:pathData=\"M399.15,355.87c36.67,10.57 50.89,61.5 87.91,67.8c7.65,1.3 16.27,3.6 26.31,3.12c18.77,-0.9 42.51,-11.51 74.22,-56.5c9.38,-13.3 -23.27,85.66 -105.13,86.86c-59.96,0.88 -66.97,-58.7 -106.93,-60.51c-14.43,-0.65 -15.34,-28.17 -15.34,-28.17c0,0 17.22,-18.86 38.96,-12.6z\"/>\n    <path android:fillColor=\"#303030\" android:pathData=\"M321.51,355.59c-36.67,10.57 -50.89,61.5 -87.91,67.8c-7.65,1.3 -16.27,3.6 -26.31,3.12c-18.77,-0.9 -42.51,-11.51 -74.22,-56.5c-9.38,-13.3 23.27,85.66 105.13,86.86c59.96,0.88 66.97,-58.7 106.93,-60.51c14.43,-0.65 15.34,-28.17 15.34,-28.17c0,0 -17.22,-18.86 -38.96,-12.6z\"/>\n    <path android:fillColor=\"#fbbcc9\" android:pathData=\"M458.64,355.09c36.87,27.94 25.88,58.7 46.57,49.92c69.7,-29.55 57.51,-181.21 51.87,-162.87c-31.77,103.41 -100.99,109.2 -167.61,61.63c-13.01,-9.29 48.38,35.57 69.16,51.31z\"/>\n    <path android:fillColor=\"#fbbcc9\" android:pathData=\"M330.91,303.77c-66.62,47.56 -135.84,41.78 -167.61,-61.63c-5.63,-18.34 -17.82,133.31 51.87,162.87c20.7,8.78 9.7,-21.98 46.57,-49.92c20.78,-15.75 82.17,-60.6 69.16,-51.31z\"/>\n    <path android:fillColor=\"#3747a9\" android:pathData=\"M465.61,318c80.43,-3.32 95.29,-135.17 88.96,-119.08c-28.39,72.22 -135.86,45.05 -146.13,90.64c-2.02,8.94 18.2,30.06 57.17,28.45z\"/>\n    <path android:fillColor=\"#3747a9\" android:pathData=\"M311.95,289.55c-10.27,-45.59 -117.75,-18.41 -146.13,-90.64c-6.32,-16.09 8.53,115.76 88.96,119.08c38.97,1.61 59.19,-19.5 57.17,-28.45z\"/>\n    <path android:fillColor=\"#ff6e40\" android:pathData=\"M403.42,269.47c0,0 43.73,-23.5 81.16,-33.74c34.99,-9.58 61.22,-33.13 64.14,-58.01c2.18,-18.53 -27.05,-53.55 -27.05,-53.55c0,0 -20.51,56.9 -47.41,85.34c-29.28,30.96 -18.15,26.78 -70.84,59.96z\"/>\n    <path android:fillColor=\"#ff6e40\" android:pathData=\"M246.13,209.51c-26.9,-28.44 -47.41,-85.34 -47.41,-85.34c0,0 -29.23,35.01 -27.05,53.55c2.93,24.88 29.16,48.43 64.14,58.01c37.43,10.25 81.16,33.74 81.16,33.74c-52.69,-33.18 -41.55,-29 -70.84,-59.96z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M398.12,265.85c47.36,-38.85 72.53,-89.54 113.51,-145.02c7.73,-10.46 -34.58,-35.7 -51.31,-37.37c-16.73,-1.67 -30.77,59.79 -32.35,95.94c-1.44,33.01 -36.21,91.68 -29.84,86.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M292.42,179.39c-1.58,-36.15 -15.62,-97.61 -32.35,-95.94c-16.73,1.67 -59.04,26.91 -51.31,37.37c40.98,55.48 66.14,106.17 113.51,145.02c6.37,5.22 -28.4,-53.45 -29.84,-86.45z\"/>\n    <path android:fillColor=\"#ffb327\" android:pathData=\"M402.86,140.35c3.34,-26.76 15.37,-46.32 39.32,-62.75c-21.17,-7.08 -38.77,-12.83 -47.97,-5.3c-9.2,7.53 -34.2,32.7 -30.85,73.68c3.34,40.98 0.18,194.09 7.43,191.25c3.9,-104.87 37.09,-135 32.07,-196.89z\"/>\n    <path android:fillColor=\"#ffb327\" android:pathData=\"M349.59,337.24c7.24,2.83 4.08,-150.27 7.43,-191.25c3.34,-40.98 -21.65,-66.16 -30.85,-73.68c-9.2,-7.53 -26.8,-1.78 -47.97,5.3c23.95,16.43 35.98,35.98 39.32,62.75c-5.02,61.89 28.17,92.02 32.07,196.89z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_magisk_outline.xml",
    "content": "<vector android:height=\"48dp\" android:viewportHeight=\"720\"\n    android:viewportWidth=\"720\" android:width=\"48dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M332.48,421.18c0,0 3.77,22.45 -0.82,71.95c-5.76,62.06 23.64,160.64 23.64,160.64c0,0 40.1,-98.78 33.1,-162.59c-5.75,-52.45 2.6,-70.79 0.82,-68.33c-30.81,42.57 -56.75,-1.67 -56.75,-1.67z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M407.6,474.45c5.01,38.77 -0.57,60.01 -7.81,101.51c-3.66,20.99 74.78,-63.1 104.86,-113.23c5.02,-8.36 -28.77,32.6 -62.19,3.35c-23.18,-20.28 -27.16,-26.44 -45.18,-44.06c-6.08,-5.94 6.74,24.72 10.32,52.43z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M321.99,425.09c-18.02,17.62 -22,23.78 -45.18,44.06c-33.42,29.25 -67.21,-11.71 -62.19,-3.35c30.08,50.13 108.52,134.22 104.86,113.23c-7.24,-41.5 -12.82,-62.74 -7.81,-101.51c3.58,-27.71 16.4,-58.37 10.32,-52.43z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M399.15,355.87c36.67,10.57 50.89,61.5 87.91,67.8c7.65,1.3 16.27,3.6 26.31,3.12c18.77,-0.9 42.51,-11.51 74.22,-56.5c9.38,-13.3 -23.27,85.66 -105.13,86.86c-59.96,0.88 -66.97,-58.7 -106.93,-60.51c-14.43,-0.65 -15.34,-28.17 -15.34,-28.17c0,0 17.22,-18.86 38.96,-12.6z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M321.51,355.59c-36.67,10.57 -50.89,61.5 -87.91,67.8c-7.65,1.3 -16.27,3.6 -26.31,3.12c-18.77,-0.9 -42.51,-11.51 -74.22,-56.5c-9.38,-13.3 23.27,85.66 105.13,86.86c59.96,0.88 66.97,-58.7 106.93,-60.51c14.43,-0.65 15.34,-28.17 15.34,-28.17c0,0 -17.22,-18.86 -38.96,-12.6z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M458.64,355.09c36.87,27.94 25.88,58.7 46.57,49.92c69.7,-29.55 57.51,-181.21 51.87,-162.87c-31.77,103.41 -100.99,109.2 -167.61,61.63c-13.01,-9.29 48.38,35.57 69.16,51.31z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M330.91,303.77c-66.62,47.56 -135.84,41.78 -167.61,-61.63c-5.63,-18.34 -17.82,133.31 51.87,162.87c20.7,8.78 9.7,-21.98 46.57,-49.92c20.78,-15.75 82.17,-60.6 69.16,-51.31z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M465.61,318c80.43,-3.32 95.29,-135.17 88.96,-119.08c-28.39,72.22 -135.86,45.05 -146.13,90.64c-2.02,8.94 18.2,30.06 57.17,28.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M311.95,289.55c-10.27,-45.59 -117.75,-18.41 -146.13,-90.64c-6.32,-16.09 8.53,115.76 88.96,119.08c38.97,1.61 59.19,-19.5 57.17,-28.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M403.42,269.47c0,0 43.73,-23.5 81.16,-33.74c34.99,-9.58 61.22,-33.13 64.14,-58.01c2.18,-18.53 -27.05,-53.55 -27.05,-53.55c0,0 -20.51,56.9 -47.41,85.34c-29.28,30.96 -18.15,26.78 -70.84,59.96z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M246.13,209.51c-26.9,-28.44 -47.41,-85.34 -47.41,-85.34c0,0 -29.23,35.01 -27.05,53.55c2.93,24.88 29.16,48.43 64.14,58.01c37.43,10.25 81.16,33.74 81.16,33.74c-52.69,-33.18 -41.55,-29 -70.84,-59.96z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M398.12,265.85c47.36,-38.85 72.53,-89.54 113.51,-145.02c7.73,-10.46 -34.58,-35.7 -51.31,-37.37c-16.73,-1.67 -30.77,59.79 -32.35,95.94c-1.44,33.01 -36.21,91.68 -29.84,86.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M292.42,179.39c-1.58,-36.15 -15.62,-97.61 -32.35,-95.94c-16.73,1.67 -59.04,26.91 -51.31,37.37c40.98,55.48 66.14,106.17 113.51,145.02c6.37,5.22 -28.4,-53.45 -29.84,-86.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M402.86,140.35c3.34,-26.76 15.37,-46.32 39.32,-62.75c-21.17,-7.08 -38.77,-12.83 -47.97,-5.3c-9.2,7.53 -34.2,32.7 -30.85,73.68c3.34,40.98 0.18,194.09 7.43,191.25c3.9,-104.87 37.09,-135 32.07,-196.89z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M349.59,337.24c7.24,2.83 4.08,-150.27 7.43,-191.25c3.34,-40.98 -21.65,-66.16 -30.85,-73.68c-9.2,-7.53 -26.8,-1.78 -47.97,5.3c23.95,16.43 35.98,35.98 39.32,62.75c-5.02,61.89 28.17,92.02 32.07,196.89z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_magisk_padded.xml",
    "content": "<vector android:height=\"108dp\" android:viewportHeight=\"1080\"\n    android:viewportWidth=\"1080\" android:width=\"108dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#303030\" android:pathData=\"M512.48,601.18c0,0 3.77,22.45 -0.82,71.95c-5.76,62.06 23.64,160.64 23.64,160.64c0,0 40.1,-98.78 33.1,-162.59c-5.75,-52.45 2.6,-70.79 0.82,-68.33c-30.81,42.57 -56.75,-1.67 -56.75,-1.67z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M587.6,654.45c5.01,38.77 -0.57,60.01 -7.81,101.51c-3.66,20.99 74.78,-63.1 104.86,-113.23c5.02,-8.36 -28.77,32.6 -62.19,3.35c-23.18,-20.28 -27.16,-26.44 -45.18,-44.06c-6.08,-5.94 6.74,24.72 10.32,52.43z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M501.99,605.09c-18.02,17.62 -22,23.78 -45.18,44.06c-33.42,29.25 -67.21,-11.71 -62.19,-3.35c30.08,50.13 108.52,134.22 104.86,113.23c-7.24,-41.5 -12.82,-62.74 -7.81,-101.51c3.58,-27.71 16.4,-58.37 10.32,-52.43z\"/>\n    <path android:fillColor=\"#303030\" android:pathData=\"M579.15,535.87c36.67,10.57 50.89,61.5 87.91,67.8c7.65,1.3 16.27,3.6 26.31,3.12c18.77,-0.9 42.51,-11.51 74.22,-56.5c9.38,-13.3 -23.27,85.66 -105.13,86.86c-59.96,0.88 -66.97,-58.7 -106.93,-60.51c-14.43,-0.65 -15.34,-28.17 -15.34,-28.17c0,0 17.22,-18.86 38.96,-12.6z\"/>\n    <path android:fillColor=\"#303030\" android:pathData=\"M501.51,535.59c-36.67,10.57 -50.89,61.5 -87.91,67.8c-7.65,1.3 -16.27,3.6 -26.31,3.12c-18.77,-0.9 -42.51,-11.51 -74.22,-56.5c-9.38,-13.3 23.27,85.66 105.13,86.86c59.96,0.88 66.97,-58.7 106.93,-60.51c14.43,-0.65 15.34,-28.17 15.34,-28.17c0,0 -17.22,-18.86 -38.96,-12.6z\"/>\n    <path android:fillColor=\"#fbbcc9\" android:pathData=\"M638.64,535.09c36.87,27.94 25.88,58.7 46.57,49.92c69.7,-29.55 57.51,-181.21 51.87,-162.87c-31.77,103.41 -100.99,109.2 -167.61,61.63c-13.01,-9.29 48.38,35.57 69.16,51.31z\"/>\n    <path android:fillColor=\"#fbbcc9\" android:pathData=\"M510.91,483.77c-66.62,47.56 -135.84,41.78 -167.61,-61.63c-5.63,-18.34 -17.82,133.31 51.87,162.87c20.7,8.78 9.7,-21.98 46.57,-49.92c20.78,-15.75 82.17,-60.6 69.16,-51.31z\"/>\n    <path android:fillColor=\"#3747a9\" android:pathData=\"M645.61,498c80.43,-3.32 95.29,-135.17 88.96,-119.08c-28.39,72.22 -135.86,45.05 -146.13,90.64c-2.02,8.94 18.2,30.06 57.17,28.45z\"/>\n    <path android:fillColor=\"#3747a9\" android:pathData=\"M491.95,469.55c-10.27,-45.59 -117.75,-18.41 -146.13,-90.64c-6.32,-16.09 8.53,115.76 88.96,119.08c38.97,1.61 59.19,-19.5 57.17,-28.45z\"/>\n    <path android:fillColor=\"#ff6e40\" android:pathData=\"M583.42,449.47c0,0 43.73,-23.5 81.16,-33.74c34.99,-9.58 61.22,-33.13 64.14,-58.01c2.18,-18.53 -27.05,-53.55 -27.05,-53.55c0,0 -20.51,56.9 -47.41,85.34c-29.28,30.96 -18.15,26.78 -70.84,59.96z\"/>\n    <path android:fillColor=\"#ff6e40\" android:pathData=\"M426.13,389.51c-26.9,-28.44 -47.41,-85.34 -47.41,-85.34c0,0 -29.23,35.01 -27.05,53.55c2.93,24.88 29.16,48.43 64.14,58.01c37.43,10.25 81.16,33.74 81.16,33.74c-52.69,-33.18 -41.55,-29 -70.84,-59.96z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M578.12,445.85c47.36,-38.85 72.53,-89.54 113.51,-145.02c7.73,-10.46 -34.58,-35.7 -51.31,-37.37c-16.73,-1.67 -30.77,59.79 -32.35,95.94c-1.44,33.01 -36.21,91.68 -29.84,86.45z\"/>\n    <path android:fillColor=\"#ffffff\" android:pathData=\"M472.42,359.39c-1.58,-36.15 -15.62,-97.61 -32.35,-95.94c-16.73,1.67 -59.04,26.91 -51.31,37.37c40.98,55.48 66.14,106.17 113.51,145.02c6.37,5.22 -28.4,-53.45 -29.84,-86.45z\"/>\n    <path android:fillColor=\"#ffb327\" android:pathData=\"M582.86,320.35c3.34,-26.76 15.37,-46.32 39.32,-62.75c-21.17,-7.08 -38.77,-12.83 -47.97,-5.3c-9.2,7.53 -34.2,32.7 -30.85,73.68c3.34,40.98 0.18,194.09 7.43,191.25c3.9,-104.87 37.09,-135 32.07,-196.89z\"/>\n    <path android:fillColor=\"#ffb327\" android:pathData=\"M529.59,517.24c7.24,2.83 4.08,-150.27 7.43,-191.25c3.34,-40.98 -21.65,-66.16 -30.85,-73.68c-9.2,-7.53 -26.8,-1.78 -47.97,5.3c23.95,16.43 35.98,35.98 39.32,62.75c-5.02,61.89 28.17,92.02 32.07,196.89z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_more.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_patreon.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M15.386,0.524c-4.764,0 -8.64,3.876 -8.64,8.64 0,4.75 3.876,8.613 8.64,8.613 4.75,0 8.614,-3.864 8.614,-8.613C24,4.4 20.136,0.524 15.386,0.524M0.003,23.537h4.22V0.524H0.003\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_paypal.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M6.908,24L3.804,24c-0.664,0 -1.086,-0.529 -0.936,-1.18l0.149,-0.674h2.071c0.666,0 1.336,-0.533 1.482,-1.182l1.064,-4.592c0.15,-0.648 0.816,-1.18 1.48,-1.18h0.883c3.789,0 6.734,-0.779 8.84,-2.34s3.16,-3.6 3.16,-6.135c0,-1.125 -0.195,-2.055 -0.588,-2.789 0,-0.016 -0.016,-0.031 -0.016,-0.046l0.135,0.075c0.75,0.465 1.32,1.064 1.711,1.814 0.404,0.75 0.598,1.68 0.598,2.791 0,2.535 -1.049,4.574 -3.164,6.135 -2.1,1.545 -5.055,2.324 -8.834,2.324h-0.9c-0.66,0 -1.334,0.525 -1.484,1.186L8.39,22.812c-0.149,0.645 -0.81,1.17 -1.47,1.17L6.908,24zM4.231,21.305L1.126,21.305c-0.663,0 -1.084,-0.529 -0.936,-1.18L4.563,1.182C4.714,0.529 5.378,0 6.044,0h6.465c1.395,0 2.609,0.098 3.648,0.289 1.035,0.189 1.92,0.519 2.684,0.99 0.736,0.465 1.322,1.072 1.697,1.818 0.389,0.748 0.584,1.68 0.584,2.797 0,2.535 -1.051,4.574 -3.164,6.119 -2.1,1.561 -5.056,2.326 -8.836,2.326h-0.883c-0.66,0 -1.328,0.524 -1.478,1.169L5.7,20.097c-0.149,0.646 -0.817,1.172 -1.485,1.172l0.016,0.036zM11.677,3.936h-1.014c-0.666,0 -1.332,0.529 -1.48,1.178l-0.93,4.02c-0.15,0.648 0.27,1.179 0.93,1.179h0.766c1.664,0 2.97,-0.343 3.9,-1.021 0.929,-0.686 1.395,-1.654 1.395,-2.912 0,-0.83 -0.301,-1.445 -0.9,-1.84 -0.6,-0.404 -1.5,-0.605 -2.686,-0.605l0.019,0.001z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_superuser.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:height=\"24dp\"\n    android:width=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@color/dark\"\n        android:pathData=\"M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z\" />\n</vector>"
  },
  {
    "path": "app/core/src/main/res/drawable/ic_twitter.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M23.954,4.569c-0.885,0.389 -1.83,0.654 -2.825,0.775 1.014,-0.611 1.794,-1.574 2.163,-2.723 -0.951,0.555 -2.005,0.959 -3.127,1.184 -0.896,-0.959 -2.173,-1.559 -3.591,-1.559 -2.717,0 -4.92,2.203 -4.92,4.917 0,0.39 0.045,0.765 0.127,1.124C7.691,8.094 4.066,6.13 1.64,3.161c-0.427,0.722 -0.666,1.561 -0.666,2.475 0,1.71 0.87,3.213 2.188,4.096 -0.807,-0.026 -1.566,-0.248 -2.228,-0.616v0.061c0,2.385 1.693,4.374 3.946,4.827 -0.413,0.111 -0.849,0.171 -1.296,0.171 -0.314,0 -0.615,-0.03 -0.916,-0.086 0.631,1.953 2.445,3.377 4.604,3.417 -1.68,1.319 -3.809,2.105 -6.102,2.105 -0.39,0 -0.779,-0.023 -1.17,-0.067 2.189,1.394 4.768,2.209 7.557,2.209 9.054,0 13.999,-7.496 13.999,-13.986 0,-0.209 0,-0.42 -0.015,-0.63 0.961,-0.689 1.8,-1.56 2.46,-2.548l-0.047,-0.02z\"/>\n</vector>\n"
  },
  {
    "path": "app/core/src/main/res/drawable/sc_extension.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"oval\">\n            <solid android:color=\"@color/su_request_background\"/>\n        </shape>\n    </item>\n    <item>\n        <inset\n            android:drawable=\"@drawable/ic_extension\"\n            android:inset=\"13dp\"/>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/core/src/main/res/drawable/sc_superuser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape android:shape=\"oval\">\n            <solid android:color=\"@color/su_request_background\"/>\n        </shape>\n    </item>\n    <item>\n        <inset\n            android:drawable=\"@drawable/ic_superuser\"\n            android:inset=\"13dp\"/>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/core/src/main/res/drawable-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@drawable/ic_magisk_padded\" />\n    <monochrome android:drawable=\"@drawable/ic_magisk_padded\" />\n</adaptive-icon>\n"
  },
  {
    "path": "app/core/src/main/res/drawable-v26/sc_extension.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/light\" />\n    <foreground>\n        <inset\n            android:drawable=\"@drawable/ic_extension\"\n            android:inset=\"30%\" />\n    </foreground>\n    <monochrome>\n        <inset\n            android:drawable=\"@drawable/ic_extension\"\n            android:inset=\"30%\" />\n    </monochrome>\n</adaptive-icon>\n"
  },
  {
    "path": "app/core/src/main/res/drawable-v26/sc_superuser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/light\" />\n    <foreground>\n        <inset\n            android:drawable=\"@drawable/ic_superuser\"\n            android:inset=\"30%\" />\n    </foreground>\n    <monochrome>\n        <inset\n            android:drawable=\"@drawable/ic_superuser\"\n            android:inset=\"30%\" />\n    </monochrome>\n</adaptive-icon>\n"
  },
  {
    "path": "app/core/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"allow_timeout\">\n        <item>@string/forever</item>\n        <item>@string/once</item>\n        <item>@string/tenmin</item>\n        <item>@string/twentymin</item>\n        <item>@string/thirtymin</item>\n        <item>@string/sixtymin</item>\n    </string-array>\n\n    <string-array name=\"su_access\">\n        <item>@string/settings_su_disable</item>\n        <item>@string/settings_su_app</item>\n        <item>@string/settings_su_adb</item>\n        <item>@string/settings_su_app_adb</item>\n    </string-array>\n\n    <string-array name=\"request_timeout\">\n        <item>@string/settings_su_request_10</item>\n        <item>@string/settings_su_request_15</item>\n        <item>@string/settings_su_request_20</item>\n        <item>@string/settings_su_request_30</item>\n        <item>@string/settings_su_request_45</item>\n        <item>@string/settings_su_request_60</item>\n    </string-array>\n\n    <string-array name=\"auto_response\">\n        <item>@string/prompt</item>\n        <item>@string/deny</item>\n        <item>@string/grant</item>\n    </string-array>\n\n    <string-array name=\"su_notification\">\n        <item>@string/none</item>\n        <item>@string/toast</item>\n        <item>@string/notification</item>\n    </string-array>\n\n    <string-array name=\"multiuser_mode\">\n        <item>@string/settings_owner_only</item>\n        <item>@string/settings_owner_manage</item>\n        <item>@string/settings_user_independent</item>\n    </string-array>\n\n    <string-array name=\"multiuser_summary\">\n        <item>@string/owner_only_summary</item>\n        <item>@string/owner_manage_summary</item>\n        <item>@string/user_independent_summary</item>\n    </string-array>\n\n    <string-array name=\"namespace\">\n        <item>@string/settings_ns_global</item>\n        <item>@string/settings_ns_requester</item>\n        <item>@string/settings_ns_isolate</item>\n    </string-array>\n\n    <string-array name=\"namespace_summary\">\n        <item>@string/global_summary</item>\n        <item>@string/requester_summary</item>\n        <item>@string/isolate_summary</item>\n    </string-array>\n\n    <string-array name=\"update_channel\">\n        <item>@string/settings_update_stable</item>\n        <item>@string/settings_update_beta</item>\n        <item>@string/settings_update_debug</item>\n        <item>@string/settings_update_custom</item>\n    </string-array>\n\n    <string-array name=\"color_mode\">\n        <item>@string/color_mode_system</item>\n        <item>@string/color_mode_light</item>\n        <item>@string/color_mode_dark</item>\n        <item>@string/color_mode_monet_system</item>\n        <item>@string/color_mode_monet_light</item>\n        <item>@string/color_mode_monet_dark</item>\n    </string-array>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#00AF9C</color>\n    <color name=\"dark\">#00796B</color>\n    <color name=\"light\">#e0e0e0</color>\n    <color name=\"su_request_background\">#e0e0e0</color>\n    <color name=\"splash_background\">@color/ic_launcher_background</color>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values/resources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- Static strings -->\n    <string name=\"magisk\" translatable=\"false\">Magisk</string>\n    <string name=\"zygisk\" translatable=\"false\">Zygisk</string>\n    <string name=\"ramdisk\" translatable=\"false\">Ramdisk</string>\n    <string name=\"empty\" translatable=\"false\"/>\n\n    <string name=\"paypal\" translatable=\"false\">PayPal</string>\n    <string name=\"patreon\" translatable=\"false\">Patreon</string>\n    <string name=\"twitter\" translatable=\"false\">Twitter</string>\n    <string name=\"github\" translatable=\"false\">GitHub</string>\n\n    <drawable name=\"ic_launcher\">@drawable/ic_logo</drawable>\n\n    <bool name=\"enable_fg_service\">true</bool>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modules</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Logs</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"install\">Install</string>\n    <string name=\"section_home\">Home</string>\n    <string name=\"section_theme\">Themes</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">No connection available</string>\n    <string name=\"app_changelog\">Changelog</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"update\">Update</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Hide</string>\n    <string name=\"restore\">Restore</string>\n    <string name=\"home_package\">Package</string>\n    <string name=\"home_app_title\">App</string>\n    <string name=\"home_core_title\">Core</string>\n    <string name=\"reinstall\">Reinstall</string>\n    <string name=\"home_notice_content\">Download Magisk ONLY from the official GitHub page. Files from unknown sources can be malicious!</string>\n    <string name=\"home_support_title\">Support Us</string>\n    <string name=\"home_follow_title\">Follow Us</string>\n    <string name=\"home_item_source\">Source</string>\n    <string name=\"home_support_content\">Magisk is, and always will be, free, and open source. However, you can show us that you care by making a donation.</string>\n    <string name=\"donate\">Donate</string>\n    <string name=\"documents\">Documents</string>\n    <string name=\"report_bugs\">Report Bugs</string>\n    <string name=\"home_status_title\">Status</string>\n    <string name=\"home_installed_version\">Installed</string>\n    <string name=\"home_latest_version\">Latest</string>\n    <string name=\"invalid_update_channel\">Invalid update channel</string>\n    <string name=\"uninstall\">Uninstall</string>\n    <string name=\"uninstall_magisk_title\">Uninstall Magisk</string>\n    <string name=\"uninstall_magisk_msg\">All modules will be disabled/removed!\\nRoot will be removed!\\nAny internal storage unencrypted through the use of Magisk will be re-encrypted!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Preserve force encryption</string>\n    <string name=\"keep_dm_verity\">Preserve AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Recovery mode</string>\n    <string name=\"install_options_title\">Options</string>\n    <string name=\"install_method_title\">Method</string>\n    <string name=\"install_next\">Next</string>\n    <string name=\"install_start\">Let\\'s go</string>\n    <string name=\"manager_download_install\">Press to download and install</string>\n    <string name=\"direct_install\">Direct install (Recommended)</string>\n    <string name=\"direct_install_summary\">Directly install Magisk to the boot partition</string>\n    <string name=\"install_inactive_slot\">Install to inactive slot (After OTA)</string>\n    <string name=\"install_inactive_slot_summary\">Install to the inactive slot after an OTA update</string>\n    <string name=\"select_patch_file_summary\">Patch a raw image, ODIN tar, or payload.bin file</string>\n    <string name=\"install_inactive_slot_msg\">Your device will be FORCED to boot to the current inactive slot after a reboot!\\nOnly use this option after OTA is done.\\nContinue?</string>\n    <string name=\"setup_title\">Additional setup</string>\n    <string name=\"select_patch_file\">Select and patch a file</string>\n    <string name=\"patch_file_msg\">Select a raw image (*.img) or an ODIN tarfile (*.tar) or a payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Rebooting in 5 seconds…</string>\n    <string name=\"flash_screen_title\">Installation</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">This app is requesting root permission. Grants full access to your device. Deny if you\\'re not sure!</string>\n    <string name=\"touch_filtered_warning\">Because an app is obscuring a Superuser request, Magisk can\\'t verify your response.</string>\n    <string name=\"deny\">Deny</string>\n    <string name=\"prompt\">Prompt</string>\n    <string name=\"restrict\">Restrict</string>\n    <string name=\"grant\">Grant</string>\n    <string name=\"su_warning\">Grants full access to your device.\\nDeny if you\\'re not sure!</string>\n    <string name=\"forever\">Forever</string>\n    <string name=\"once\">Once</string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">%1$s was granted Superuser rights</string>\n    <string name=\"su_deny_toast\">%1$s was denied Superuser rights</string>\n    <string name=\"su_snack_grant\">Superuser rights of %1$s are granted</string>\n    <string name=\"su_snack_deny\">Superuser rights of %1$s are denied</string>\n    <string name=\"su_snack_notif_on\">Notifications of %1$s are enabled</string>\n    <string name=\"su_snack_notif_off\">Notifications of %1$s are disabled</string>\n    <string name=\"su_snack_log_on\">Logging of %1$s is enabled</string>\n    <string name=\"su_snack_log_off\">Logging of %1$s is disabled</string>\n    <string name=\"su_revoke_title\">Revoke?</string>\n    <string name=\"su_revoke_msg\">Confirm to revoke %1$s Superuser rights</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"notification\">Notification</string>\n    <string name=\"none\">None</string>\n    <string name=\"su_notification_channel\">Superuser Notifications</string>\n    <string name=\"su_notification_granted_title\">Superuser Granted</string>\n    <string name=\"su_notification_denied_title\">Superuser Denied</string>\n    <string name=\"superuser_toggle_notification\">Notifications</string>\n    <string name=\"superuser_toggle_revoke\">Revoke</string>\n    <string name=\"superuser_policy_none\">No apps have asked for Superuser permission yet.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">You\\'re log-free. Try using your root apps more.</string>\n    <string name=\"log_data_magisk_none\">Magisk logs are empty, that\\'s weird.</string>\n    <string name=\"menuSaveLog\">Save log</string>\n    <string name=\"menuClearLog\">Clear log now</string>\n    <string name=\"logs_cleared\">Log successfully cleared</string>\n    <string name=\"save_log\">Save log</string>\n    <string name=\"clear_log\">Clear log</string>\n    <string name=\"back\">Back</string>\n    <string name=\"superuser_setting\">Superuser Setting</string>\n    <string name=\"enabled\">Enabled</string>\n    <string name=\"disabled\">Disabled</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Target UID: %1$d</string>\n    <string name=\"target_pid\">Target PID: %s</string>\n    <string name=\"selinux_context\">SELinux context: %s</string>\n    <string name=\"supp_group\">Supplementary group: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Show system apps</string>\n    <string name=\"show_os_app\">Show OS apps</string>\n    <string name=\"hide_filter_hint\">Filter by name</string>\n    <string name=\"sort_by_name\">Sort by name</string>\n    <string name=\"sort_by_package_name\">Sort by package name</string>\n    <string name=\"sort_by_install_time\">Sort by install time</string>\n    <string name=\"sort_by_update_time\">Sort by update time</string>\n    <string name=\"sort_reverse\">Reverse order</string>\n    <string name=\"menu_sort\">Sort</string>\n    <string name=\"hide_search\">Search</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(No info provided)</string>\n    <string name=\"reboot_userspace\">Soft reboot</string>\n    <string name=\"reboot_recovery\">Reboot to Recovery</string>\n    <string name=\"reboot_bootloader\">Reboot to Bootloader</string>\n    <string name=\"reboot_download\">Reboot to Download</string>\n    <string name=\"reboot_edl\">Reboot to EDL</string>\n    <string name=\"reboot_safe_mode\">Safe mode</string>\n    <string name=\"module_version_author\">%1$s by %2$s</string>\n    <string name=\"module_state_remove\">Remove</string>\n    <string name=\"module_action\">Action</string>\n    <string name=\"module_state_restore\">Restore</string>\n    <string name=\"module_action_install_external\">Install from storage</string>\n    <string name=\"update_available\">Update available</string>\n    <string name=\"suspend_text_riru\">Module suspended because %1$s is enabled</string>\n    <string name=\"suspend_text_zygisk\">Module suspended because %1$s isn\\'t enabled</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk module not loaded due to incompatibility</string>\n    <string name=\"module_empty\">No module installed</string>\n    <string name=\"confirm_install\">Install module %1$s?</string>\n    <string name=\"confirm_install_title\">Install confirmation</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Theme mode</string>\n    <string name=\"settings_dark_mode_message\">Select mode which best suits your style!</string>\n    <string name=\"settings_dark_mode_light\">Always light</string>\n    <string name=\"settings_dark_mode_system\">Follow system</string>\n    <string name=\"settings_dark_mode_dark\">Always dark</string>\n    <string name=\"settings_download_path_title\">Download path</string>\n    <string name=\"settings_download_path_message\">Files will be saved to %1$s</string>\n    <string name=\"settings_hide_app_title\">Hide the Magisk app</string>\n    <string name=\"settings_hide_app_summary\">Install a proxy app with a random package ID and custom app label</string>\n    <string name=\"settings_restore_app_title\">Restore the Magisk app</string>\n    <string name=\"settings_restore_app_summary\">Unhide the app and restore the original APK</string>\n    <string name=\"language\">Language</string>\n    <string name=\"system_default\">(System default)</string>\n    <string name=\"settings_check_update_title\">Check for updates</string>\n    <string name=\"settings_check_update_summary\">Periodically check for updates in the background</string>\n    <string name=\"settings_update_channel_title\">Update channel</string>\n    <string name=\"settings_update_stable\">Stable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Custom</string>\n    <string name=\"settings_update_custom_msg\">Insert a custom channel URL</string>\n    <string name=\"settings_zygisk_summary\">Run parts of Magisk in the Zygote daemon</string>\n    <string name=\"settings_denylist_title\">Enforce DenyList</string>\n    <string name=\"settings_denylist_summary\">Processes on the denylist will have all Magisk modifications reverted</string>\n    <string name=\"settings_denylist_config_title\">Configure DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Select the processes to be included on the denylist</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Systemless hosts support for ad blocking apps</string>\n    <string name=\"settings_hosts_toast\">Added systemless hosts module</string>\n    <string name=\"settings_app_name_hint\">New name</string>\n    <string name=\"settings_app_name_helper\">App will be repackaged with this name</string>\n    <string name=\"settings_app_name_error\">Invalid format</string>\n    <string name=\"settings_su_app_adb\">Apps and ADB</string>\n    <string name=\"settings_su_app\">Apps only</string>\n    <string name=\"settings_su_adb\">ADB only</string>\n    <string name=\"settings_su_disable\">Disabled</string>\n    <string name=\"settings_su_request_10\">10 seconds</string>\n    <string name=\"settings_su_request_15\">15 seconds</string>\n    <string name=\"settings_su_request_20\">20 seconds</string>\n    <string name=\"settings_su_request_30\">30 seconds</string>\n    <string name=\"settings_su_request_45\">45 seconds</string>\n    <string name=\"settings_su_request_60\">60 seconds</string>\n    <string name=\"superuser_access\">Superuser access</string>\n    <string name=\"auto_response\">Automatic response</string>\n    <string name=\"request_timeout\">Request timeout</string>\n    <string name=\"superuser_notification\">Superuser notification</string>\n    <string name=\"settings_su_reauth_title\">Reauthenticate after upgrade</string>\n    <string name=\"settings_su_reauth_summary\">Ask for Superuser permissions again after upgrading apps</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking protection</string>\n    <string name=\"settings_su_tapjack_summary\">The Superuser prompt dialog won\\'t respond to input while obscured by any other window or overlay</string>\n    <string name=\"settings_su_auth_title\">User authentication</string>\n    <string name=\"settings_su_auth_summary\">Ask for user authentication during Superuser requests</string>\n    <string name=\"settings_su_auth_insecure\">No authentication method is configured on the device</string>\n    <string name=\"settings_su_restrict_title\">Restrict root capabilities</string>\n    <string name=\"settings_su_restrict_summary\">Will restrict new Superuser apps by default. Warning: this will break most apps. Don\\'t enable it unless you know what you\\'re doing.</string>\n    <string name=\"settings_customization\">Customization</string>\n    <string name=\"settings_color_mode\">Color mode</string>\n    <string name=\"color_mode_system\">System</string>\n    <string name=\"color_mode_light\">Light</string>\n    <string name=\"color_mode_dark\">Dark</string>\n    <string name=\"color_mode_monet_system\">Monet (System)</string>\n    <string name=\"color_mode_monet_light\">Monet (Light)</string>\n    <string name=\"color_mode_monet_dark\">Monet (Dark)</string>\n    <string name=\"setting_add_shortcut_summary\">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Workaround DNS poisoning in some nations</string>\n    <string name=\"settings_random_name_title\">Randomize output name</string>\n    <string name=\"settings_random_name_description\">Randomize the output file name of patched images and tar files to prevent detection</string>\n    <string name=\"multiuser_mode\">Multiuser mode</string>\n    <string name=\"settings_owner_only\">Device owner only</string>\n    <string name=\"settings_owner_manage\">Device owner managed</string>\n    <string name=\"settings_user_independent\">User-independent</string>\n    <string name=\"owner_only_summary\">Only owner has root access</string>\n    <string name=\"owner_manage_summary\">Only owner can manage root access and receive request prompts</string>\n    <string name=\"user_independent_summary\">Each user has their own separate root rules</string>\n    <string name=\"mount_namespace_mode\">Mount namespace mode</string>\n    <string name=\"settings_ns_global\">Global namespace</string>\n    <string name=\"settings_ns_requester\">Inherit namespace</string>\n    <string name=\"settings_ns_isolate\">Isolated namespace</string>\n    <string name=\"global_summary\">All root sessions use the global mount namespace</string>\n    <string name=\"requester_summary\">Root sessions will inherit their requester\\'s namespace</string>\n    <string name=\"isolate_summary\">Each root session will have its own isolated namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk updates</string>\n    <string name=\"progress_channel\">Progress notifications</string>\n    <string name=\"updated_channel\">Update complete</string>\n    <string name=\"download_complete\">Download complete</string>\n    <string name=\"download_file_error\">Error downloading file</string>\n    <string name=\"magisk_update_title\">Magisk update available!</string>\n    <string name=\"updated_title\">Magisk updated</string>\n    <string name=\"updated_text\">Tap to open app</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"repo_install_title\">Install %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Download</string>\n    <string name=\"reboot\">Reboot</string>\n    <string name=\"close\">Close</string>\n    <string name=\"release_notes\">Release notes</string>\n    <string name=\"flashing\">Flashing…</string>\n    <string name=\"running\">Running…</string>\n    <string name=\"done\">Done!</string>\n    <string name=\"done_action\">Done running action of %1$s</string>\n    <string name=\"failure\">Failed!</string>\n    <string name=\"hide_app_title\">Hiding the Magisk app…</string>\n    <string name=\"open_link_failed_toast\">No app found to open the link</string>\n    <string name=\"complete_uninstall\">Complete uninstall</string>\n    <string name=\"restore_img\">Restore images</string>\n    <string name=\"restore_img_msg\">Restoring…</string>\n    <string name=\"restore_done\">Restoration done!</string>\n    <string name=\"restore_fail\">Stock backup doesn\\'t exist!</string>\n    <string name=\"setup_fail\">Setup failed</string>\n    <string name=\"env_fix_title\">Requires additional setup</string>\n    <string name=\"env_fix_msg\">Your device needs additional setup for Magisk to work properly. Do you want to proceed and reboot?</string>\n    <string name=\"env_full_fix_msg\">Your device needs reflash Magisk to work properly. Please reinstall Magisk within app, Recovery mode cannot get correct device info.</string>\n    <string name=\"setup_msg\">Running environment setup…</string>\n    <string name=\"unsupport_magisk_title\">Unsupported Magisk version</string>\n    <string name=\"unsupport_magisk_msg\">This version of the app doesn\\'t support Magisk versions lower than %1$s.\\n\\nThe app will behave as if no Magisk is installed. Please update Magisk as soon as possible.</string>\n    <string name=\"unsupport_general_title\">Abnormal state</string>\n    <string name=\"unsupport_system_app_msg\">Running this app as a system app isn\\'t supported. Please revert the app to a user app.</string>\n    <string name=\"unsupport_other_su_msg\">A \\\"su\\\" binary not from Magisk has been detected. Please remove any competing root solution and/or reinstall Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk is installed to external storage. Please move the app to internal storage.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Grant storage permission to enable this functionality</string>\n    <string name=\"post_notifications_denied\">Grant notifications permission to enable this functionality</string>\n    <string name=\"install_unknown_denied\">Allow \\\"Install unknown apps\\\" to enable this functionality</string>\n    <string name=\"add_shortcut_title\">Add shortcut to home screen</string>\n    <string name=\"add_shortcut_msg\">After hiding this app, its name and icon might become difficult to recognize. Do you want to add a pretty shortcut to the home screen?</string>\n    <string name=\"app_not_found\">No app found to handle this action</string>\n    <string name=\"reboot_apply_change\">Reboot to apply changes</string>\n    <string name=\"restore_app_confirmation\">This will restore the hidden app back to the original app. Do you really want to do this?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <style name=\"SplashTheme\" parent=\"Theme.SplashScreen\">\n        <item name=\"windowSplashScreenBackground\">@color/splash_background</item>\n        <item name=\"windowSplashScreenAnimatedIcon\">@drawable/ic_magisk_padded</item>\n    </style>\n    <style name=\"StubSplashTheme\" parent=\"SplashTheme\"/>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ar/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">الإضافات</string>\n    <string name=\"superuser\">صلاحية الروت</string>\n    <string name=\"logs\">السجلات</string>\n    <string name=\"settings\">الإعدادات</string>\n    <string name=\"install\">تثبيت</string>\n    <string name=\"section_home\">الصفحة الرئيسية</string>\n    <string name=\"section_theme\">المظهر</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">لا يوجد إتصال</string>\n    <string name=\"app_changelog\">تفاصيل التحديث</string>\n    <string name=\"loading\">جارٍ التحميل...</string>\n    <string name=\"update\">تحديث</string>\n    <string name=\"not_available\">غير متوفر</string>\n    <string name=\"hide\">إخفاء</string>\n    <string name=\"home_package\">الحزمة</string>\n\n    <string name=\"home_support_title\">تبرع لنا</string>\n    <string name=\"home_item_source\">الكود المصدري للتطبيق</string>\n    <string name=\"home_support_content\">ماجيسك هو، وسيظل دوماً، مجانياّ و مفتوح المصدر، اظهر اهتمامك لنا لكي نبقيه هكذا بدعم مالي صغير</string>\n    <string name=\"home_installed_version\">تم التثبيت</string>\n    <string name=\"home_latest_version\">آخر إصدار</string>\n    <string name=\"invalid_update_channel\">مصدر التحديث غير صالح</string>\n    <string name=\"uninstall_magisk_title\">إلغاء تثبيت ماجيسك</string>\n    <string name=\"uninstall_magisk_msg\">ستُعطل/ستُحذف جميع الإضافات. سيُحذف الروت، وربما ستشفر بياناتك إذا لم تكن غير مشفرة حالياً.</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">فرض التشفير الإجباري</string>\n    <string name=\"keep_dm_verity\">فرض تشفيرات AVB2.0/dm-verity</string>\n    <string name=\"recovery_mode\">وضـع الريكفري</string>\n    <string name=\"install_options_title\">الخيارات</string>\n    <string name=\"install_method_title\">الطريقة</string>\n    <string name=\"install_next\">التالي</string>\n    <string name=\"install_start\">هيا بنا</string>\n    <string name=\"manager_download_install\">اضغط للتنزيل و التثبيت</string>\n    <string name=\"direct_install\">تثبيت مباشر (موصى بها)</string>\n    <string name=\"install_inactive_slot\">التثبيت على المنطقة الغير نشطة (بعد OTA)</string>\n    <string name=\"install_inactive_slot_msg\">سيُجبر جهازك للاقلاع على المنطقة الغير النشطة بعد إعادة التشغيل!\\n استخدم هذا الخيار فقط بعد الانتهاء من OTA. استمرار؟</string>\n    <string name=\"setup_title\">إعدادات إضافية</string>\n    <string name=\"select_patch_file\">حدد و عدل ملفاً</string>\n    <string name=\"patch_file_msg\">اختر ملف (*.img) أو ملف odin (*.tar)</string>\n    <string name=\"reboot_delay_toast\">إعادة التشغيل بعد ٥ ثواني…</string>\n    <string name=\"flash_screen_title\">التثبيت</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">طلبات صلاحية الروت</string>\n    <string name=\"deny\">رفض</string>\n    <string name=\"prompt\">طلب</string>\n    <string name=\"grant\">سماح</string>\n    <string name=\"su_warning\">يمنح حق الوصول الكامل إلى جهازك. ارفض إذا كنت غير متأكد!</string>\n    <string name=\"forever\">للأبد</string>\n    <string name=\"once\">مرة واحدة</string>\n    <string name=\"tenmin\">10 دقائق</string>\n    <string name=\"twentymin\">20 دقيقة</string>\n    <string name=\"thirtymin\">30 دقيقة</string>\n    <string name=\"sixtymin\">60 دقيقة</string>\n    <string name=\"su_allow_toast\">تم منح صلاحيات الروت لـ%1$s</string>\n    <string name=\"su_deny_toast\">تم رفض صلاحيات الروت لـ%1$s </string>\n    <string name=\"su_snack_grant\">تم منح صلاحيات الروت لـ%1$s</string>\n    <string name=\"su_snack_deny\">تم رفض صلاحيات الروت لـ%1$s</string>\n    <string name=\"su_snack_notif_on\">تم تفعيل الإشعارات لـ%1$s</string>\n    <string name=\"su_snack_notif_off\">تم تعطيل الإشعارات لـ%1$s</string>\n    <string name=\"su_snack_log_on\">تم تفعيل السجلات لـ%1$s</string>\n    <string name=\"su_snack_log_off\">تم تعطيل السجلات لـ%1$s</string>\n    <string name=\"su_revoke_title\">منع؟</string>\n    <string name=\"su_revoke_msg\">هل تريد منع صلاحية %1$s?</string>\n    <string name=\"toast\">اشعار</string>\n    <string name=\"none\">بدون</string>\n\n    <string name=\"superuser_toggle_notification\">الإشعارات</string>\n    <string name=\"superuser_toggle_revoke\">منع</string>\n    <string name=\"superuser_policy_none\">لم يسأل آية تطبيق لصلاحيات الروت</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">لا يوجد أية سجلات هنا :-:، حاول استعمال تطبيقات روت اكثر</string>\n    <string name=\"log_data_magisk_none\">لا توجد أيةَ سجلات ⊙_⊙</string>\n    <string name=\"menuSaveLog\">حفظ السجلات</string>\n    <string name=\"menuClearLog\">حذف السجلات</string>\n    <string name=\"logs_cleared\">تم الحذف بنجاح</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">إظهار برامج النظام</string>\n    <string name=\"hide_filter_hint\">البحث بالاسم</string>\n    <string name=\"hide_search\">ابحث</string>\n\n    <!--Module Fragment-->\n    <string name=\"no_info_provided\">(لا تتوفر معلومات)</string>\n    <string name=\"reboot_recovery\">إعادة التشغيل إلى Recovery</string>\n    <string name=\"reboot_bootloader\">إعادة التشغيل إلى Bootloader</string>\n    <string name=\"reboot_download\">إعادة التشغيل إلى وضـع Odin</string>\n    <string name=\"reboot_edl\">إعادة التشغيل إلى EDL</string>\n    <string name=\"module_version_author\">%1$sبواسطة%2$s</string>\n    <string name=\"module_state_remove\">إزالة </string>\n    <string name=\"module_state_restore\">إسترجاع</string>\n    <string name=\"module_action_install_external\">اختر من الذاكرة الداخلية</string>\n    <string name=\"update_available\">التحديث متوفر</string>\n    <string name=\"external_rw_permission_denied\">امنحني إذن الولوج للذاكرة الداخلية</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">المظهر</string>\n    <string name=\"settings_dark_mode_message\">حدد المظهر الذي يناسب ذوقك</string>\n    <string name=\"settings_dark_mode_light\">الوضـع المضيء</string>\n    <string name=\"settings_dark_mode_system\">اتبّع النظام</string>\n    <string name=\"settings_dark_mode_dark\">الوضع المظلم</string>\n    <string name=\"settings_download_path_title\">مسار التحميل</string>\n    <string name=\"settings_download_path_message\">ستحمل الملفات إلى %1$s</string>\n    <string name=\"language\">اللغة</string>\n    <string name=\"system_default\">(الإفتراضي)</string>\n    <string name=\"settings_check_update_title\">تحقق من التحديثات</string>\n    <string name=\"settings_check_update_summary\">التحقق من التحديثات في الخلفية بشكل دوري</string>\n    <string name=\"settings_update_channel_title\">مصدر التحديثات</string>\n    <string name=\"settings_update_stable\">مستقر</string>\n    <string name=\"settings_update_beta\">تجريبي</string>\n    <string name=\"settings_update_custom\">مخصص</string>\n    <string name=\"settings_update_custom_msg\">أدخل الرابط لمصدرك المخصص</string>\n    <string name=\"settings_hosts_title\">موانع الاعلانات</string>\n    <string name=\"settings_hosts_summary\">حجب الاعلانات دون تعديل النظام</string>\n    <string name=\"settings_hosts_toast\">تم تمكين خاصية حجب الاعلانات</string>\n    <string name=\"settings_app_name_hint\">الاسم الجديد</string>\n    <string name=\"settings_app_name_helper\">التطبيق الجديد سوف يملك هذا الاسم</string>\n    <string name=\"settings_app_name_error\">الصيغة غير مقبولة</string>\n    <string name=\"settings_su_app_adb\">التطبيقات و ADB</string>\n    <string name=\"settings_su_app\">التطبيقات فقط</string>\n    <string name=\"settings_su_adb\">ADB فقط</string>\n    <string name=\"settings_su_disable\">معطل</string>\n    <string name=\"settings_su_request_10\">10 ثواني</string>\n    <string name=\"settings_su_request_15\">15 ثانية</string>\n    <string name=\"settings_su_request_20\">20 ثانية</string>\n    <string name=\"settings_su_request_30\">30 ثانية</string>\n    <string name=\"settings_su_request_45\">45 ثانية</string>\n    <string name=\"settings_su_request_60\">60 ثانية</string>\n    <string name=\"superuser_access\">صلاحيات الروت</string>\n    <string name=\"auto_response\">الفعل التلقائي</string>\n    <string name=\"request_timeout\">المهلة قبل الفعل التلقائي</string>\n    <string name=\"superuser_notification\">إشعارات طلبات الروت</string>\n    <string name=\"settings_su_reauth_title\">إعادة المصادقة بعد التحديث</string>\n    <string name=\"settings_su_reauth_summary\">أعد مصادقة صلاحيات الروت بعد تحديث التطبيق</string>\n    <string name=\"settings_customization\">تخصيص</string>\n\n    <string name=\"multiuser_mode\">نمط المستخدم المزدوج</string>\n    <string name=\"settings_owner_only\">مالك الجهاز فقط</string>\n    <string name=\"settings_owner_manage\">المالك هو من يحدد</string>\n    <string name=\"settings_user_independent\">مستقل</string>\n    <string name=\"owner_only_summary\">للمالك فقط له صلاحيات الروت</string>\n    <string name=\"owner_manage_summary\">فقط المالك من يرفض و يمنح صلاحيات الروت</string>\n    <string name=\"user_independent_summary\">كل مستخدم له قواعد روت خاصة به</string>\n\n    <string name=\"mount_namespace_mode\">نمط Mount Namespace</string>\n    <string name=\"settings_ns_global\">نمط Namespace عام</string>\n    <string name=\"settings_ns_requester\">نمط NameSpace متوارث</string>\n    <string name=\"settings_ns_isolate\">نمط NameSpace معزول</string>\n    <string name=\"global_summary\">جميع الجلسات الروت تستخدم NameSpace العام</string>\n    <string name=\"requester_summary\">جميع الجلسات الروت تستخدم NameSpace المتوارث</string>\n    <string name=\"isolate_summary\">جميع الجلسات الروت تستخدم NameSpace المعزول</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">تحديثات ماجيسك</string>\n    <string name=\"progress_channel\">إشعارات التقدم</string>\n    <string name=\"download_complete\">اكتمل التنزيل</string>\n    <string name=\"download_file_error\">فشل تنزيل الملف</string>\n    <string name=\"magisk_update_title\">تحديث ماجيسك متوفر!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">نعم</string>\n    <string name=\"no\">لا</string>\n    <string name=\"download\">تنزيل</string>\n    <string name=\"reboot\">إعادة التشغيل</string>\n    <string name=\"release_notes\">معلومات الإصدار الجديد</string>\n    <string name=\"flashing\">يتم التثبيت...</string>\n    <string name=\"done\">تم!</string>\n    <string name=\"failure\">فشل!</string>\n    <string name=\"open_link_failed_toast\">لم يُعثر على تطبيق لفتح الرابط …</string>\n    <string name=\"complete_uninstall\">إلغاء التثبيت بالكامل</string>\n    <string name=\"restore_img\">استعادة الصور</string>\n    <string name=\"restore_img_msg\">جار الإستعادة…</string>\n    <string name=\"restore_done\">تم الإستعادة</string>\n    <string name=\"restore_fail\">النسخة الإحتياطية الأصلية غير موجودة!</string>\n    <string name=\"setup_fail\">فشل الإعداد</string>\n    <string name=\"env_fix_title\">الإعداد الإضافي مطلوب</string>\n    <string name=\"setup_msg\">جار إعداد البيئة</string>\n    <string name=\"unsupport_magisk_title\">إصدار ماجيسك غير مدعوم</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ast/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--Sections-->\n    <string name=\"modules\">Módulos</string>\n    <string name=\"superuser\">Superusuariu</string>\n    <string name=\"logs\">Rexistru</string>\n    <string name=\"settings\">Configuración</string>\n    <string name=\"install\">Instalar</string>\n    <string name=\"section_home\">Aniciu</string>\n    <string name=\"section_theme\">Estilos</string>\n    <string name=\"denylist\">Llista d\\'esclusión</string>\n    <!--Home-->\n    <string name=\"no_connection\">L\\'accesu a internet nun ta disponible</string>\n    <string name=\"app_changelog\">Rexistru de cambeos</string>\n    <string name=\"loading\">Cargando…</string>\n    <string name=\"update\">Anovar</string>\n    <string name=\"not_available\">N/D</string>\n    <string name=\"hide\">Esconder</string>\n    <string name=\"home_package\">Paquete</string>\n    <string name=\"home_app_title\">Aplicación</string>\n    <string name=\"home_notice_content\">Baxa Magisk NAMÁS dende la páxina oficial de GitHub. ¡Los ficheros de fontes desconocíes puen ser maliciosos!</string>\n    <string name=\"home_support_title\">Collaboración</string>\n    <string name=\"home_follow_title\">Redes sociales</string>\n    <string name=\"home_item_source\">Códigu fonte</string>\n    <string name=\"home_support_content\">Magisk ye y va ser de códigu abiertu y gratuitu. Sicasí, pues ayudanos faciendo una donación o collaborando.</string>\n    <string name=\"home_installed_version\">Versión instalada</string>\n    <string name=\"home_latest_version\">Última versión</string>\n    <string name=\"invalid_update_channel\">La canal d\\'anovamientu nun ye válida</string>\n    <string name=\"uninstall_magisk_title\">Desinstalar Magisk</string>\n    <string name=\"uninstall_magisk_msg\">¡Van quitase y desactivase tolos módulos y l\\'accesu root!\\nCualesquier almacenamientu internu ensin cifrar pente l\\'usu de Magisk va volver cifrase.</string>\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Caltener el cifráu forciáu</string>\n    <string name=\"keep_dm_verity\">Caltener l\\'AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mou de recuperación</string>\n    <string name=\"install_options_title\">Opciones</string>\n    <string name=\"install_method_title\">Métodu</string>\n    <string name=\"install_next\">Siguiente</string>\n    <string name=\"install_start\">Siguir</string>\n    <string name=\"manager_download_install\">Primi equí pa baxalu ya instalalu</string>\n    <string name=\"direct_install\">Instalación direuta (aconséyase)</string>\n    <string name=\"install_inactive_slot\">Instalar na ralura inactiva (darréu del OTA)</string>\n    <string name=\"install_inactive_slot_msg\">¡El preséu va arrincar OBLIGATORIAMENTE na ralura inactiva dempués de reaniciar!\\nUsa esta opción namás dempués d\\'acabar l\\'anovamientu per OTA.\\n¿Quies siguir?</string>\n    <string name=\"setup_title\">Configuración adicional</string>\n    <string name=\"select_patch_file\">Seleicionar y parchiar un ficheru</string>\n    <string name=\"patch_file_msg\">Seleiciona una imaxe en bruto (*.img), un archivu d\\'ODIN (*.tar) o un ficheru payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Reaniciando en 5 segundos…</string>\n    <string name=\"flash_screen_title\">Instalación</string>\n    <!--Superuser-->\n    <string name=\"su_request_title\">Solicitú de superusuariu</string>\n    <string name=\"touch_filtered_warning\">Como una aplicación torga la solicitú de superusuariu, Magisk nun pue verificar la to rempuesta</string>\n    <string name=\"deny\">Negar</string>\n    <string name=\"prompt\">Entrugar</string>\n    <string name=\"grant\">Conceder</string>\n    <string name=\"su_warning\">Concede l\\'accesu completu al preséu.\\n¡Niégalu en casu de dulda!</string>\n    <string name=\"forever\">Siempres</string>\n    <string name=\"once\">Una vegada</string>\n    <string name=\"tenmin\">10 minutos</string>\n    <string name=\"twentymin\">20 minutos</string>\n    <string name=\"thirtymin\">30 minutos</string>\n    <string name=\"sixtymin\">60 minutos</string>\n    <string name=\"su_allow_toast\">A %1$s concediéronse-y los derechos de superusuariu</string>\n    <string name=\"su_deny_toast\">A %1$s negáronse-y los derechos de superusuariu</string>\n    <string name=\"su_snack_grant\">Concediéronse los derechos de superusuariu a %1$s</string>\n    <string name=\"su_snack_deny\">Negáronse los derechos de superusuariu a %1$s</string>\n    <string name=\"su_snack_notif_on\">Activáronse los avisos de: %1$s</string>\n    <string name=\"su_snack_notif_off\">Desactiváronse los avisos de: %1$s</string>\n    <string name=\"su_snack_log_on\">Activóse\\'l rexistru de: %1$s</string>\n    <string name=\"su_snack_log_off\">Desactivóse\\'l rexistru de: %1$s</string>\n    <string name=\"su_revoke_title\">Revocación de derechos</string>\n    <string name=\"su_revoke_msg\">Confirma la revocación de los derechos de superusuariu pa «%1$s»</string>\n    <string name=\"toast\">Burbuya d\\'avisu emerxente</string>\n    <string name=\"none\">Nada</string>\n    <string name=\"superuser_toggle_notification\">Avisos</string>\n    <string name=\"superuser_toggle_revoke\">Revocar</string>\n    <string name=\"superuser_policy_none\">Pel momentu, nenguna aplicación pidió\\'l permisu de superusuariu.</string>\n    <!--Logs-->\n    <string name=\"log_data_none\">El rexistru ta baleru. Prueba a usar más aplicaciones de root</string>\n    <string name=\"log_data_magisk_none\">El rexistru de Magisk ta baleru. ¡Qué raro!</string>\n    <string name=\"menuSaveLog\">Guardar el rexistru</string>\n    <string name=\"menuClearLog\">Llimpiar el rexistru</string>\n    <string name=\"logs_cleared\">El rexistru borróse correutamente</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID de destín: %1$d</string>\n    <string name=\"target_pid\">Mount ns target PID: %s</string>\n    <string name=\"selinux_context\">Contestu de SELinux: %s</string>\n    <string name=\"supp_group\">Grupu suplementariu: %s</string>\n    <!--SafetyNet-->\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Aplicaciones del sistema</string>\n    <string name=\"show_os_app\">Aplicaciones del SO</string>\n    <string name=\"hide_filter_hint\">Peñerar pol nome</string>\n    <string name=\"hide_search\">Buscar</string>\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nun s\\'apurrió nenguna información)</string>\n    <string name=\"reboot_userspace\">Reaniciu del sistema</string>\n    <string name=\"reboot_recovery\">Reaniciar al recovery</string>\n    <string name=\"reboot_bootloader\">Reaniciar al cargador d\\'arrinque</string>\n    <string name=\"reboot_download\">Reaniciar al mou de descarga</string>\n    <string name=\"reboot_edl\">Reaniciar al mou EDL</string>\n    <string name=\"reboot_safe_mode\">Mou seguru</string>\n    <string name=\"module_version_author\">%1$s por %2$s</string>\n    <string name=\"module_state_remove\">Quitar</string>\n    <string name=\"module_action\">Aición</string>\n    <string name=\"module_state_restore\">Restaurar</string>\n    <string name=\"module_action_install_external\">Instalar dende l\\'almacenamientu</string>\n    <string name=\"update_available\">Hai un anovamientu disponible</string>\n    <string name=\"suspend_text_riru\">Suspendióse\\'l módulu porque s\\'activó «%1$s»</string>\n    <string name=\"suspend_text_zygisk\">Suspendióse\\'l módulu porque nun s\\'activó «%1$s»</string>\n    <string name=\"zygisk_module_unloaded\">El módulu de Zygisk nun cargó por haber incompatibilidaes</string>\n    <string name=\"module_empty\">Nun hai nengún módulu instaláu</string>\n    <string name=\"confirm_install\">¿Quies instalar el módulu «%1$s»?</string>\n    <string name=\"confirm_install_title\">Confirmación de la instalación</string>\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mou del estilu</string>\n    <string name=\"settings_dark_mode_message\">¡Seleiciona\\'l mou que meyor s\\'adaute al to estilu!</string>\n    <string name=\"settings_dark_mode_light\">Claridá</string>\n    <string name=\"settings_dark_mode_system\">L\\'estilu del sistema</string>\n    <string name=\"settings_dark_mode_dark\">Escuridá</string>\n    <string name=\"settings_download_path_title\">Camín de les descargues</string>\n    <string name=\"settings_download_path_message\">Los ficheros van guardase en «%1$s»</string>\n    <string name=\"settings_hide_app_title\">Esconder Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instala una aplicación intermedia con una ID y una etiqueta al debalu</string>\n    <string name=\"settings_restore_app_title\">Restaurar el mou visible</string>\n    <string name=\"settings_restore_app_summary\">Fai que l\\'aplicación orixinal vuelva ser visible</string>\n    <string name=\"language\">Llingua</string>\n    <string name=\"system_default\">(Lo predeterminao)</string>\n    <string name=\"settings_check_update_title\">Comprobación d\\'anovamientos</string>\n    <string name=\"settings_check_update_summary\">Comprueba si hai anovamientos en segundu planu</string>\n    <string name=\"settings_update_channel_title\">Canal d\\'anovamientu</string>\n    <string name=\"settings_update_stable\">Estable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Canal personalizada</string>\n    <string name=\"settings_update_custom_msg\">Inxerta la URL d\\'una canal personalizada</string>\n    <string name=\"settings_zygisk_summary\">Executa partes de Magisk nel degorriu de Zygote</string>\n    <string name=\"settings_denylist_title\">Forciar la llista d\\'esclusión</string>\n    <string name=\"settings_denylist_summary\">Los procesos de la llista d\\'esclusión tienen toles modificaciones de Magisk anulaes</string>\n    <string name=\"settings_denylist_config_title\">Configurar la llista d\\'esclusión</string>\n    <string name=\"settings_denylist_config_summary\">Seleiciona los procesos que s\\'inclúin na llista d\\'esclusión</string>\n    <string name=\"settings_hosts_title\">Módulu «Systemless Hosts»</string>\n    <string name=\"settings_hosts_summary\">Un módulu pa les aplicaciones que bloquien anuncios</string>\n    <string name=\"settings_hosts_toast\">Amestóse\\'l módulu «Systemless Hosts»</string>\n    <string name=\"settings_app_name_hint\">Nome nuevu</string>\n    <string name=\"settings_app_name_helper\">L\\'aplicación va volver empaquetase con esti nome</string>\n    <string name=\"settings_app_name_error\">El formatu nun ye válidu</string>\n    <string name=\"settings_su_app_adb\">Aplicaciones y ADB</string>\n    <string name=\"settings_su_app\">Namás aplicaciones</string>\n    <string name=\"settings_su_adb\">Namás ADB</string>\n    <string name=\"settings_su_disable\">Non</string>\n    <string name=\"settings_su_request_10\">10 segundos</string>\n    <string name=\"settings_su_request_15\">15 segundos</string>\n    <string name=\"settings_su_request_20\">20 segundos</string>\n    <string name=\"settings_su_request_30\">30 segundos</string>\n    <string name=\"settings_su_request_45\">45 segundos</string>\n    <string name=\"settings_su_request_60\">60 segundos</string>\n    <string name=\"superuser_access\">Accesu de superusuariu</string>\n    <string name=\"auto_response\">Rempuesta automática</string>\n    <string name=\"request_timeout\">Tiempu d\\'espera de les solicitúes</string>\n    <string name=\"superuser_notification\">Avisu de superusuariu</string>\n    <string name=\"settings_su_reauth_title\">Volver autenticar dempués d\\'anovar</string>\n    <string name=\"settings_su_reauth_summary\">Vuelve pidir los permisos de superusuariu dempués d\\'anovar les aplicaciones</string>\n    <string name=\"settings_su_tapjack_title\">Proteición escontra\\'l tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">El diálogu de concesión de permisos de superusuariu nun respuende a la entrada mentanto lu torgue otra ventana o superposición</string>\n    <string name=\"settings_su_auth_title\">Autenticación d\\'usuariu</string>\n    <string name=\"settings_su_auth_summary\">Pide l\\'autenticación demientres les solicitúes de superusuariu</string>\n    <string name=\"settings_su_auth_insecure\">Nun se configuró nengún métodu d\\'autenticación nel preséu</string>\n    <string name=\"settings_customization\">Personalización</string>\n    <string name=\"setting_add_shortcut_summary\">Amiesta un atayu a la pantalla d\\'aniciu en casu de que\\'l nome y l\\'iconu seyan difíciles de reconocer dempués d\\'esconder l\\'aplicación</string>\n    <string name=\"settings_doh_title\">DNS per HTTPS</string>\n    <string name=\"settings_doh_description\">Una igua alternativa pal envelenamientu de DNS en dalgunos países</string>\n    <string name=\"settings_random_name_title\">Nome de la salida aleatoriu</string>\n    <string name=\"settings_random_name_description\">Fai que\\'l nome de ficheru de la salida de les imáxenes parchiaes y los ficheros tar seya aleatoriu pa impidir la deteición</string>\n    <string name=\"multiuser_mode\">Mou de multiusuariu</string>\n    <string name=\"settings_owner_only\">Namás el propietariu del preséu</string>\n    <string name=\"settings_owner_manage\">El propietariu xestionáu del preséu</string>\n    <string name=\"settings_user_independent\">Con independencia del usuariu</string>\n    <string name=\"owner_only_summary\">Namás el propietariu tien accesu de root</string>\n    <string name=\"owner_manage_summary\">Namás el propietariu pue xestionar l\\'accesu de root y recibir solicitúes</string>\n    <string name=\"user_independent_summary\">Cada usuariu tien les sos regles de root individuales</string>\n    <string name=\"mount_namespace_mode\">Mou del montaxe del espaciu de nomes</string>\n    <string name=\"settings_ns_global\">Espaciu de nomes global</string>\n    <string name=\"settings_ns_requester\">Espaciu de nomes heredáu</string>\n    <string name=\"settings_ns_isolate\">Espaciu de nomes aisláu</string>\n    <string name=\"global_summary\">Toles sesiones de root usen l\\'espaciu de nomes global</string>\n    <string name=\"requester_summary\">Les sesiones de root herieden l\\'espaciu de nomes de los solicitantes</string>\n    <string name=\"isolate_summary\">Cada sesión de root tien el so espaciu de nomes propiu</string>\n    <!--Notifications-->\n    <string name=\"update_channel\">Anovamientos de Magisk</string>\n    <string name=\"progress_channel\">Avisos de progresos</string>\n    <string name=\"updated_channel\">Anovamientu completáu</string>\n    <string name=\"download_complete\">Completóse la descarga</string>\n    <string name=\"download_file_error\">Hebo un fallu al baxar el ficheru</string>\n    <string name=\"magisk_update_title\">¡Hai un anovamientu pa Magisk!</string>\n    <string name=\"updated_title\">Magisk anovóse</string>\n    <string name=\"updated_text\">Toca equí p\\'abrir l\\'aplicación</string>\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">Non</string>\n    <string name=\"repo_install_title\">Instalación de: %1$s %2$s (%3$d)</string>\n    <string name=\"download\">Baxar</string>\n    <string name=\"reboot\">Reaniciar</string>\n    <string name=\"close\">Zarrar</string>\n    <string name=\"release_notes\">Notes de la versión</string>\n    <string name=\"flashing\">Flaxando…</string>\n    <string name=\"running\">Executando…</string>\n    <string name=\"done\">¡Fecho!</string>\n    <string name=\"done_action\">Completóse l\\'aición de: %1$s</string>\n    <string name=\"failure\">¡Falló!</string>\n    <string name=\"hide_app_title\">Escondiendo l\\'aplicación Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nun s\\'atopó nenguna aplicación p\\'abrir l\\'enllaz</string>\n    <string name=\"complete_uninstall\">Desinstalar dafechu</string>\n    <string name=\"restore_img\">Restaurar les imáxenes</string>\n    <string name=\"restore_img_msg\">Restaurando…</string>\n    <string name=\"restore_done\">¡Restauración fecha!</string>\n    <string name=\"restore_fail\">¡La copia de seguranza de la imaxe de fábrica nun esiste!</string>\n    <string name=\"setup_fail\">La configuración falló</string>\n    <string name=\"env_fix_title\">Configuración adicional</string>\n    <string name=\"env_fix_msg\">El preséu precisa una configuración adicional pa que Magisk funcione afayadizamente. ¿Quies siguir y reaniciar?</string>\n    <string name=\"env_full_fix_msg\">El preséu precisa volver flaxar Magisk pa que funcione afayadizamente. Volvi instalar Magisk dientro de l\\'aplicación porque\\'l mou de recuperación nun pue consiguir la información correuta del preséu.</string>\n    <string name=\"setup_msg\">Executando la configuración del entornu…</string>\n    <string name=\"unsupport_magisk_title\">Versión non compatible</string>\n    <string name=\"unsupport_magisk_msg\">Esta versión de l\\'aplicación nun ye compatible coles versiones de Magisk anteriores a la %1$s.\\n\\nL\\'aplicación va comportase como si Magisk nun tuviere instaláu, anueva Magisk namás que puedas.</string>\n    <string name=\"unsupport_general_title\">Estáu anormal</string>\n    <string name=\"unsupport_system_app_msg\">Esta aplicación nun se pue executar nel espaciu del sistema. Volvi instalala mas nel espaciu del usuariu.</string>\n    <string name=\"unsupport_other_su_msg\">Detectóse un binariu «su» que nun ye de Magisk. Quita cualesquier solución de root y/o volvi instalar Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk ta instaláu nel almacenamientu esternu. Movi l\\'aplicación al almacenamientu internu, por favor.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Magisk nun pue siguir funcionando nel mou escondíu darréu que se perdió\\'l root. Restaura\\'l mou visible de l\\'aplicación.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Concede\\'l permisu d\\'almacenamientu p\\'activar esta funcionalidá</string>\n    <string name=\"post_notifications_denied\">Concede\\'l permisu de los avisos p\\'activar esta función</string>\n    <string name=\"install_unknown_denied\">Permite la instalación d\\'aplicaciones desconocíes p\\'activar esta funcionalidá</string>\n    <string name=\"add_shortcut_title\">Amestar un atayu a la pantalla d\\'aniciu</string>\n    <string name=\"add_shortcut_msg\">Dempués d\\'esconder esta aplicación, el so nome ya iconu van ser difíciles de reconocer. ¿Quies amestar un atayu a la pantalla d\\'aniciu?</string>\n    <string name=\"app_not_found\">Nun s\\'atopó nenguna aplicación pa remanar esta aición</string>\n    <string name=\"reboot_apply_change\">Reanicia p\\'aplicar los cambeos</string>\n    <string name=\"restore_app_confirmation\">Esta aición va restaurar l\\'aplicación orixinal y desanicia la intermedia. ¿De xuru que quies facelo?</string>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-az/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modullar</string>\n    <string name=\"superuser\">Super İstifadəçi</string>\n    <string name=\"logs\">Qeydlər</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"install\">Quraşdır</string>\n    <string name=\"section_home\">Əsas Səhifə</string>\n    <string name=\"section_theme\">Mövzular</string>\n    <string name=\"denylist\">Rəddedilənlər siyahısı</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Bağlantı yoxdur</string>\n    <string name=\"app_changelog\">Dəyişikliklər</string>\n    <string name=\"loading\">Yüklənir…</string>\n    <string name=\"update\">Yenilə</string>\n    <string name=\"not_available\">Yüklü deyil</string>\n    <string name=\"hide\">Gizlə</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">Tətbiq</string>\n\n    <string name=\"home_notice_content\">Magiski YALNIZ rəsmi GitHub səhifəsindən endirin. Bilinməyən mənbələrdəki fayllar zərərli ola bilər!</string>\n    <string name=\"home_support_title\">Bizə Dəstək ol</string>\n    <string name=\"home_follow_title\">Bizi İzləyin</string>\n    <string name=\"home_item_source\">Mənbə</string>\n    <string name=\"home_support_content\">Magisk pulsuzdur və həmişə belə qalacaq. Amma istəsən bizə dəstək ola bilərsən.</string>\n    <string name=\"home_installed_version\">Qurulan</string>\n    <string name=\"home_latest_version\">Ən son versiya</string>\n    <string name=\"invalid_update_channel\">Keçərsiz yeniləmə kanalı</string>\n    <string name=\"uninstall_magisk_title\">Magisk\\'i sil</string>\n    <string name=\"uninstall_magisk_msg\">Bütün modullar ləğv olunacaq/silinəcək. Root silinəcək, və əgər hal-hazırda deyilsə, bütün məlumatlarınız potensiyal olaraq şifrələnəcək.</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Zorla şifrələməni saxla</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity saxla</string>\n    <string name=\"recovery_mode\">Xilasetmə Modu</string>\n    <string name=\"install_options_title\">Ayarlar</string>\n    <string name=\"install_method_title\">Metod</string>\n    <string name=\"install_next\">Növbəti</string>\n    <string name=\"install_start\">Başlat</string>\n    <string name=\"manager_download_install\">Endirmək və quraşdırmaq üçün bas</string>\n    <string name=\"direct_install\">Birbaşa Quraşdır (Tövsiyyə olunur)</string>\n    <string name=\"install_inactive_slot\">Aktiv olmayan Slot\\'a quraşdır (OTA\\'dan sonra)</string>\n    <string name=\"install_inactive_slot_msg\">Cihazınız yenidən başladıqdan sonra aktiv olmayan slotda başlamağa MƏCBUR ediləcək!\\nBu seçimi ancaq OTA bitdikdən sonra tətbiq edin.\\nDavam edilsin?</string>\n    <string name=\"setup_title\">Əlavə Sazlamalar</string>\n    <string name=\"select_patch_file\">Fayl seç və yamaqla</string>\n    <string name=\"patch_file_msg\">(*.img) yaxud ODIN (*.tar) seç</string>\n    <string name=\"reboot_delay_toast\">5 saniyəyə yenidən başlayacaq…</string>\n    <string name=\"flash_screen_title\">Quraşdırılma</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Super İstifadəçi İcazəsi</string>\n    <string name=\"touch_filtered_warning\">Hansısa tətbiq Super İstifadəçi icazəsini pozduğuna görə, Magisk göstərişinizi yerinə yetirə bilmir</string>\n    <string name=\"deny\">İmtina et</string>\n    <string name=\"prompt\">İstək</string>\n    <string name=\"grant\">İcazə ver</string>\n    <string name=\"su_warning\">Bu tətbiqə tam səlahiyyət verəcək.\\nƏmin deyilsənsə imtina et!</string>\n    <string name=\"forever\">Həmişə</string>\n    <string name=\"once\">1 dəfəlik</string>\n    <string name=\"tenmin\">10 dəqiqəlik</string>\n    <string name=\"twentymin\">20 dəqiqəlik</string>\n    <string name=\"thirtymin\">30 dəqiqəlik</string>\n    <string name=\"sixtymin\">60 dəqiqəlik</string>\n    <string name=\"su_allow_toast\">%1$s Super İstifadəçi icazəsi aldı</string>\n    <string name=\"su_deny_toast\">%1$s Super İstifadəçi icazəsi ala bilmədi</string>\n    <string name=\"su_snack_grant\">%1$s üçün Super İstifadəçi haqları verilib</string>\n    <string name=\"su_snack_deny\">%1$s üçün Super İstifadəçi haqları verilməyib</string>\n    <string name=\"su_snack_notif_on\">%1$s üçün bildirişlər açıqdır</string>\n    <string name=\"su_snack_notif_off\">%1$s üçün bildirişlər qapalıdır</string>\n    <string name=\"su_snack_log_on\">%1$s qeydləri açıqdır</string>\n    <string name=\"su_snack_log_off\">%1$s qeydləri qapalıdır</string>\n    <string name=\"su_revoke_title\">Sil</string>\n    <string name=\"su_revoke_msg\">%1$s üçün Super İstifadəçi haqlarının silinməsini təsdiqlə</string>\n    <string name=\"toast\">Tost</string>\n    <string name=\"none\">Heçbiri</string>\n\n    <string name=\"superuser_toggle_notification\">bildirişlər</string>\n    <string name=\"superuser_toggle_revoke\">Sil</string>\n    <string name=\"superuser_policy_none\">Hələ heçbir tətbiq Super İstifadəçi icazəsi istəməyib.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Qeyd yoxdur, root icazəli tətbiqlərdən daha çox istifadə edin</string>\n    <string name=\"log_data_magisk_none\">Qəribədir, Magisk qeydləri boşdur</string>\n    <string name=\"menuSaveLog\">Qeydləri saxla</string>\n    <string name=\"menuClearLog\">Qeydləri sil</string>\n    <string name=\"logs_cleared\">Qeydlər silindi</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Hədəf UID: %1$d</string>\n    <string name=\"selinux_context\">SELinux kontenti: %s</string>\n    <string name=\"supp_group\">Əlavə grup: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Sistem tətbiqlərini göstər</string>\n    <string name=\"show_os_app\">Əməliyyat sistemi tətbiqlərini göstər</string>\n    <string name=\"hide_filter_hint\">Adla sırala</string>\n    <string name=\"hide_search\">Axtar</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Məlumat verilməyib)</string>\n    <string name=\"reboot_userspace\">Yumuşaq yenidən başlat</string>\n    <string name=\"reboot_recovery\">Xilasetmə modunda yenidən başlat</string>\n    <string name=\"reboot_bootloader\">Önyükləyici modunda yenidən başlat</string>\n    <string name=\"reboot_download\">Yükləmə modunda yenidən başlat</string>\n    <string name=\"reboot_edl\">EDL modunda yenidən başlat</string>\n    <string name=\"module_version_author\">%2$s tərəfindən %1$s buraxılışı</string>\n    <string name=\"module_state_remove\">Sil</string>\n    <string name=\"module_state_restore\">Bərpa et</string>\n    <string name=\"module_action_install_external\">Yaddaşdan yüklə</string>\n    <string name=\"update_available\">Yeniləmə Mövcuddur</string>\n    <string name=\"suspend_text_riru\">%1$s açıq olduğundan Magisk modulu dayandırıldı</string>\n    <string name=\"suspend_text_zygisk\">%1$s qapalı olduğu üçün Magisk modulu dayandırıldı</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk modulu uyğunsuzluq səbəbindən açılmadı</string>\n    <string name=\"module_empty\">Yüklü modul yoxdur</string>\n    <string name=\"confirm_install\">%1$s modulu yüklənsin?</string>\n    <string name=\"confirm_install_title\">Yükləmə Təsdiqi</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mövzu Modu</string>\n    <string name=\"settings_dark_mode_message\">Zövqünüzə uyğun modu seçin!</string>\n    <string name=\"settings_dark_mode_light\">Həmişə Açıq</string>\n    <string name=\"settings_dark_mode_system\">Sistemə Uyğun</string>\n    <string name=\"settings_dark_mode_dark\">Həmişə Qaranlıq</string>\n    <string name=\"settings_download_path_title\">Endirmə yolu</string>\n    <string name=\"settings_download_path_message\">Fayllar %1$s\\'a yerləşdiriləcək</string>\n    <string name=\"settings_hide_app_title\">Magisk tətbiqini gizlət</string>\n    <string name=\"settings_hide_app_summary\">Təsadüfi paket ID\\'si ilə proxy yüklə və özəl tətbiq adı yaz</string>\n    <string name=\"settings_restore_app_title\">Magisk tətbiqini bərpa et</string>\n    <string name=\"settings_restore_app_summary\">Tətbiqi üzə çıxar və orijinal APKnı bərpa et</string>\n    <string name=\"language\">Dil</string>\n    <string name=\"system_default\">(Sistem Dili)</string>\n    <string name=\"settings_check_update_title\">Yeniləməni Yoxla</string>\n    <string name=\"settings_check_update_summary\">Vaxtaşırı arxaplanda yeniləmələri yoxla</string>\n    <string name=\"settings_update_channel_title\">Yeniləmə Kanalı</string>\n    <string name=\"settings_update_stable\">Sabit</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Özəl</string>\n    <string name=\"settings_update_custom_msg\">Özəl kanal URL\\'si daxil et</string>\n    <string name=\"settings_zygisk_summary\">Magiskin bəzi hissələrini zygote daemonda aç</string>\n    <string name=\"settings_denylist_title\">İstisnaları Məcbur İşlət</string>\n    <string name=\"settings_denylist_summary\">İstisnalar siyahısındakı proseslər Magisk əlavələrini tərsinə çevirəcək</string>\n    <string name=\"settings_denylist_config_title\">İstisnaları Ayarla</string>\n    <string name=\"settings_denylist_config_summary\">İstisnalara yerləşdiriləcək prosesi seçin</string>\n    <string name=\"settings_hosts_title\">Sistemsiz hostlar</string>\n    <string name=\"settings_hosts_summary\">Reklam əngəlləyici tətbiqlər üçün host faylları</string>\n    <string name=\"settings_hosts_toast\">Sistemsiz hostların əlavəsi quruldu</string>\n    <string name=\"settings_app_name_hint\">Yeni Ad</string>\n    <string name=\"settings_app_name_helper\">Tətbiq bu adla yenidən paketlənəcək</string>\n    <string name=\"settings_app_name_error\">Keçərsiz Format</string>\n    <string name=\"settings_su_app_adb\">Tətbiqlər və ADB</string>\n    <string name=\"settings_su_app\">Yalmız Tətbiqlər</string>\n    <string name=\"settings_su_adb\">Yalnız ADB</string>\n    <string name=\"settings_su_disable\">Heç Biri</string>\n    <string name=\"settings_su_request_10\">10 saniyə</string>\n    <string name=\"settings_su_request_15\">15 saniyə</string>\n    <string name=\"settings_su_request_20\">20 saniyə</string>\n    <string name=\"settings_su_request_30\">30 saniyə</string>\n    <string name=\"settings_su_request_45\">45 saniyə</string>\n    <string name=\"settings_su_request_60\">60 saniyə</string>\n    <string name=\"superuser_access\">Super istifadəçi İcazəsi</string>\n    <string name=\"auto_response\">Avtomatik Cavab</string>\n    <string name=\"request_timeout\">İstək Vaxtaşımı</string>\n    <string name=\"superuser_notification\">Super İstifadəçi Bildirişi</string>\n    <string name=\"settings_su_reauth_title\">Yüksəltdikdən sonra yenidən səlahiyətləndir</string>\n    <string name=\"settings_su_reauth_summary\">Tətbiqləri yüksəltdikdən sonra Super İstifadəçi icazəsi istə</string>\n    <string name=\"settings_su_tapjack_title\">Saxta Ekran (Tapjacking) Qoruması</string>\n    <string name=\"settings_su_tapjack_summary\">Super İstifadəçi icazə pəncərəsi digər pəncərələr tərəfindən əngəlləndikdə heçbir girişə cavab verməyəcək</string>\n    <string name=\"settings_customization\">Özəlləşdirmə</string>\n    <string name=\"setting_add_shortcut_summary\">Tətbiqi gizlətdikdən sonra tapmaq çətin olmasın deyə ana ekrana qısayol əlavə et</string>\n    <string name=\"settings_doh_title\">HTTPS üzərindən DNS</string>\n    <string name=\"settings_doh_description\">Bəzi ölkələrdə DNS problemlərini həll edir</string>\n\n    <string name=\"multiuser_mode\">Çox istifadəçi Modu</string>\n    <string name=\"settings_owner_only\">Yalnız Cihazın Sahibi</string>\n    <string name=\"settings_owner_manage\">Cihaz Sahibinin İdarəsində</string>\n    <string name=\"settings_user_independent\">İstifadəçidən Qeyri-Asılı</string>\n    <string name=\"owner_only_summary\">Yalnız cihaz sahibində root icazəsi var</string>\n    <string name=\"owner_manage_summary\">Yalnız cihaz sahibində root icazəsi verə yaxud istəyə bilər</string>\n    <string name=\"user_independent_summary\">Hər istifadəçinin öz root icazəsi var</string>\n\n    <string name=\"mount_namespace_mode\">Adlarfəzası modunu qoş</string>\n    <string name=\"settings_ns_global\">Beynəlxalq Adlarfəzası</string>\n    <string name=\"settings_ns_requester\">Adlarfəzası Daxil et</string>\n    <string name=\"settings_ns_isolate\">Təklənmiş Adlarfəzası</string>\n    <string name=\"global_summary\">Bütün kök girişləri beynəlxaq adlarfəzası işlədəcək</string>\n    <string name=\"requester_summary\">Bütün kök girişləri öz adlarfəzalarından icazə alacaq</string>\n    <string name=\"isolate_summary\">Hər kök giriş üçün ayrıca kök adlarfəzası olacaq</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk Yeniləmələri</string>\n    <string name=\"progress_channel\">İrəliləmə Bildirişləri</string>\n    <string name=\"updated_channel\">Yeniləmə Bitdi</string>\n    <string name=\"download_complete\">Endirmə Bitdi</string>\n    <string name=\"download_file_error\">Faylı endirmək münkün olmadı</string>\n    <string name=\"magisk_update_title\">Magisk Yeniləməsi Var!</string>\n    <string name=\"updated_title\">Magisk Yeniləndi</string>\n    <string name=\"updated_text\">Tətbiqi açmaq üçün toxun</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Mövcud</string>\n    <string name=\"no\">Mövcud Deyil</string>\n    <string name=\"repo_install_title\">Quraşdır %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Endir</string>\n    <string name=\"reboot\">Yenidən başlat</string>\n    <string name=\"release_notes\">Yeniliklər</string>\n    <string name=\"flashing\">Flashlanır...</string>\n    <string name=\"done\">Bitdi!</string>\n    <string name=\"failure\">Alınmadı!</string>\n    <string name=\"hide_app_title\">Magisk tətbiqi gizlədilir…</string>\n    <string name=\"open_link_failed_toast\">Keçidi açaçaq tətbiq yoxdur</string>\n    <string name=\"complete_uninstall\">Tamamilə sil</string>\n    <string name=\"restore_img\">Nüsxələri Bərpa et</string>\n    <string name=\"restore_img_msg\">Bərpa Edilir…</string>\n    <string name=\"restore_done\">Bərpa Edildi!</string>\n    <string name=\"restore_fail\">Bərpa üçün nüxsələr mövcud deyil!</string>\n    <string name=\"setup_fail\">Qurma uğursuz oldu</string>\n    <string name=\"env_fix_title\">Əlavə yükləməyə ethiyac var</string>\n    <string name=\"env_fix_msg\">Cihazınız Magiskin düzgün işləməsi üçün əlavə yükləməyə ehtiyac duyur. Yenidən başlatmaq istəyirsiniz?</string>\n    <string name=\"setup_msg\">Yüklənir…</string>\n    <string name=\"unsupport_magisk_title\">Bu Magisk versiyası dəstəklənmir</string>\n    <string name=\"unsupport_magisk_msg\">Tətbiqin bu versiyası Magiskin %1$s versiyasından aşağıları dəstəkləmir.\\n\\nTətbiq sanki Magisk heç yoxmuş kimi davranacaq, lütfən Magiski yüksəldin.</string>\n    <string name=\"unsupport_general_title\">Anormal Hal</string>\n    <string name=\"unsupport_system_app_msg\">Bu tətbiqi sistem tətbiqi kimi açmaq olmur. Onu yenidən istifadəçi tətbiqi etməlisiniz.</string>\n    <string name=\"unsupport_other_su_msg\">Magiskdən olmayan \\\"su\\\" tapıldı. Lütfən digər kök tətbiqi silin yaxud Magiski yenidən quraşdırın.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk xarici yaddaşa qurulub. Lütfən tətbiqi daxili yaddaşa keçirin.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Gizli Magisk tətbiqi kök itirildiyinə görə işləməyəcək. Orijinal APKnı geri qaytarın.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Bu funksiyanı işə salmaq üçün yaddaş icazəsi verin</string>\n    <string name=\"post_notifications_denied\">Bu funksiyanı işə salmaq üçün bildiriş icazəsi verin</string>\n    <string name=\"install_unknown_denied\">\"bilinməyən tətbiqləri quraşdır\"\\ı açsanız bu funksiya işə düşəcək</string>\n    <string name=\"add_shortcut_title\">Ana ekranda qısayol yarat</string>\n    <string name=\"add_shortcut_msg\">Tətbiqi gizlətdikdən sonra tapmaq çətin olmasın deyə ana ekranda qısayol yaradılsın?</string>\n    <string name=\"app_not_found\">Bu işi görəcək heçbir tətbiq yoxdur</string>\n    <string name=\"reboot_apply_change\">Dəyişikliklər təkrar başladıqdan sonra olacaq</string>\n    <string name=\"restore_app_confirmation\">Gizli tətbiq əvvəlki halına qayıdacaq. Davam edilsin?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-b+sr+Latn/strings.xml",
    "content": "<resources>\n    <!--Author: Radoš Milićev (https://github.com/rammba)-->\n\n    <!--Sections-->\n    <string name=\"modules\">Moduli</string>\n    <string name=\"superuser\">Super-korisnik</string>\n    <string name=\"logs\">Logovi</string>\n    <string name=\"settings\">Podešavanja</string>\n    <string name=\"install\">Instalacija</string>\n    <string name=\"section_home\">Početno</string>\n    <string name=\"section_theme\">Teme</string>\n    <string name=\"denylist\">Lista zabrana</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nedostupna konekcija</string>\n    <string name=\"app_changelog\">Promene u aplikaciji</string>\n    <string name=\"loading\">Učitavanje…</string>\n    <string name=\"update\">Ažuriranje</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Sakrij</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">Apl.</string>\n    <string name=\"home_notice_content\">Preuzmite Magisk SAMO sa zvanične GitHub stranice. Fajlovi iz nepoznatih izvora mogu biti maliciozni!</string>\n    <string name=\"home_support_title\">Podržite nas</string>\n    <string name=\"home_follow_title\">Zapratite nas</string>\n    <string name=\"home_item_source\">Izvor</string>\n    <string name=\"home_support_content\">Magisk jeste i uvek će biti besplatan i open source. Međutim, možete pokazati da vam je stalo svojom donacijom.</string>\n    <string name=\"home_installed_version\">Instalirano</string>\n    <string name=\"home_latest_version\">Najnovije</string>\n    <string name=\"invalid_update_channel\">Nevalidan kanal ažuriranja</string>\n    <string name=\"uninstall_magisk_title\">Deinstaliraj Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Svi moduli će biti onemogućeni/uklonjeni!\\nKoren će biti uklonjen!\\nSvako neenkriptovano interno skladište će upotrebom Magisk-a biti ponovo enkriptovano!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Zadrži forsiranu enkripciju</string>\n    <string name=\"keep_dm_verity\">Zadrži AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Režim oporavka</string>\n    <string name=\"install_options_title\">Opcije</string>\n    <string name=\"install_method_title\">Metod</string>\n    <string name=\"install_next\">Naredno</string>\n    <string name=\"install_start\">Počnimo</string>\n    <string name=\"manager_download_install\">Pritisni da preuzmeš i instaliraš</string>\n    <string name=\"direct_install\">Direktna instalacija (Preporučeno)</string>\n    <string name=\"install_inactive_slot\">Instalacija na neaktivan slot (Nakon OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Vaš uređaj će biti FORSIRAN da se pokrene na trenutno neaktivnom slotu nakon ponovnog pokretanja!\\nKoristite opciju samo kad se OTA završi.\\nNastavi?</string>\n    <string name=\"setup_title\">Dodatne postavke</string>\n    <string name=\"select_patch_file\">Izaberite fajl</string>\n    <string name=\"patch_file_msg\">Izaberite sliku (*.img) ili ODIN tarfile (*.tar) ili payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Ponovo pokretanje za 5 sekundi…</string>\n    <string name=\"flash_screen_title\">Instalacija</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Super-korisnički zahtev</string>\n    <string name=\"touch_filtered_warning\">Magisk ne može da verifikuje vaš odgovor, jer aplikacija prikriva super-korisnički zahtev.</string>\n    <string name=\"deny\">Zabrani</string>\n    <string name=\"prompt\">Zahtev</string>\n    <string name=\"restrict\">Ograniči</string>\n    <string name=\"grant\">Dozvoli</string>\n    <string name=\"su_warning\">Pruža potpun pristup vašem uređaju.\\nZabranite ako niste sigurni!</string>\n    <string name=\"forever\">Zauvek</string>\n    <string name=\"once\">Jednom</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s je dobio prava na super-korisnika</string>\n    <string name=\"su_deny_toast\">%1$s nije dobio prava na super-korisnika</string>\n    <string name=\"su_snack_grant\">Super-korisnička prava od %1$s su pružena</string>\n    <string name=\"su_snack_deny\">Super-korisnička prava od %1$s su odbijena</string>\n    <string name=\"su_snack_notif_on\">Notifikacije od %1$s su omogućene</string>\n    <string name=\"su_snack_notif_off\">Notifikacije od %1$s su onemogućene</string>\n    <string name=\"su_snack_log_on\">Logovanje za %1$s je omogućeno</string>\n    <string name=\"su_snack_log_off\">Logovanje za %1$s je onemogućeno</string>\n    <string name=\"su_revoke_title\">Opozovi?</string>\n    <string name=\"su_revoke_msg\">Potvrdi da opozoveš prava na super-korisnika od %1$s?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Ništa</string>\n    <string name=\"superuser_toggle_notification\">Notifikacije</string>\n    <string name=\"superuser_toggle_revoke\">Opozovi</string>\n    <string name=\"superuser_policy_none\">Nijedna aplikacija nije tražila permisije za super-korisnika još uvek.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nemate logova. Pokušajte koristiti korenske aplikacije više.</string>\n    <string name=\"log_data_magisk_none\">Magisk logovi su prazni, to je čudno.</string>\n    <string name=\"menuSaveLog\">Sačuvaj log</string>\n    <string name=\"menuClearLog\">Ukloni log</string>\n    <string name=\"logs_cleared\">Log uspešno uklonjen</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Ciljani UID: %1$d</string>\n    <string name=\"target_pid\">Ciljani PID: %s</string>\n    <string name=\"selinux_context\">SELinux kontekst: %s</string>\n    <string name=\"supp_group\">Dopunska grupa: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Prikaži sistemske apl.</string>\n    <string name=\"show_os_app\">Prikaži apl. OS-a</string>\n    <string name=\"hide_filter_hint\">Filtriraj po imenu</string>\n    <string name=\"hide_search\">Pretraga</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Bez informacija)</string>\n    <string name=\"reboot_userspace\">Lako ponovo pokretanje</string>\n    <string name=\"reboot_recovery\">Ponovo pokreni za oporavak</string>\n    <string name=\"reboot_bootloader\">Ponovo pokreni za bootloader</string>\n    <string name=\"reboot_download\">Ponovo pokreni za preuzimanje</string>\n    <string name=\"reboot_edl\">Ponovo pokreni za EDL</string>\n    <string name=\"reboot_safe_mode\">Siguran mod</string>\n    <string name=\"module_version_author\">%1$s od %2$s</string>\n    <string name=\"module_state_remove\">Ukloni</string>\n    <string name=\"module_action\">Akcija</string>\n    <string name=\"module_state_restore\">Povrati</string>\n    <string name=\"module_action_install_external\">Instaliraj iz skladišta</string>\n    <string name=\"update_available\">Ažuriranje dostupno</string>\n    <string name=\"suspend_text_riru\">Modul je suspendovan jer je %1$s omogućeno</string>\n    <string name=\"suspend_text_zygisk\">Modul je suspendovan jer %1$s nije omogućeno</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk modul nije učitan zbog nekompatibilnosti</string>\n    <string name=\"module_empty\">Nijedan modul nije instaliran</string>\n    <string name=\"confirm_install\">Instaliraj modul %1$s?</string>\n    <string name=\"confirm_install_title\">Potvrda instalacije</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Tema</string>\n    <string name=\"settings_dark_mode_message\">Izaberite temu koja vam najviše odgovara!</string>\n    <string name=\"settings_dark_mode_light\">Uvek svetlo</string>\n    <string name=\"settings_dark_mode_system\">Prati sistem</string>\n    <string name=\"settings_dark_mode_dark\">Uvek tamno</string>\n    <string name=\"settings_download_path_title\">Putanja za preuzimanje</string>\n    <string name=\"settings_download_path_message\">Fajlovi će biti sačuvani na %1$s</string>\n    <string name=\"settings_hide_app_title\">Sakrij Magisk apl.</string>\n    <string name=\"settings_hide_app_summary\">Instaliraj proxy aplikaciju sa nasumičnim ID-jem paketa i prilagođenom labelom</string>\n    <string name=\"settings_restore_app_title\">Povrati Magisk apl.</string>\n    <string name=\"settings_restore_app_summary\">Otkrij apl. i povrati originalni APK</string>\n    <string name=\"language\">Jezik</string>\n    <string name=\"system_default\">(Podrazumevano sistemski)</string>\n    <string name=\"settings_check_update_title\">Proveri ažuriranja</string>\n    <string name=\"settings_check_update_summary\">Periodično proveri ažuriranja u pozadini</string>\n    <string name=\"settings_update_channel_title\">Kanal ažuriranja</string>\n    <string name=\"settings_update_stable\">Stabilno</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Prilagođeno</string>\n    <string name=\"settings_update_custom_msg\">Unesi prilagođeni URL kanala</string>\n    <string name=\"settings_zygisk_summary\">Pokreni delove Magisk-a u Zygote daemon-u</string>\n    <string name=\"settings_denylist_title\">Sprovedi listu zabrana</string>\n    <string name=\"settings_denylist_summary\">Procesi na listi zabrana će povratiti sve Magisk izmene</string>\n    <string name=\"settings_denylist_config_title\">Konfiguriši listu zabrana</string>\n    <string name=\"settings_denylist_config_summary\">Izaberi procese koji će biti na listi zabrana</string>\n    <string name=\"settings_hosts_title\">Bezsistemski domaćini (hosts)</string>\n    <string name=\"settings_hosts_summary\">Podrška bezsistemskih domaćina za aplikacije blokiranja reklama</string>\n    <string name=\"settings_hosts_toast\">Modul bezsistemskih domaćina dodat</string>\n    <string name=\"settings_app_name_hint\">Novo ime</string>\n    <string name=\"settings_app_name_helper\">Apl. će biti spakovana pod ovim imenom</string>\n    <string name=\"settings_app_name_error\">Nevalidan format</string>\n    <string name=\"settings_su_app_adb\">Aplikacije i ADB</string>\n    <string name=\"settings_su_app\">Samo aplikacije</string>\n    <string name=\"settings_su_adb\">Samo ADB</string>\n    <string name=\"settings_su_disable\">Onemogućeno</string>\n    <string name=\"settings_su_request_10\">10 sekundi</string>\n    <string name=\"settings_su_request_15\">15 sekundi</string>\n    <string name=\"settings_su_request_20\">20 sekundi</string>\n    <string name=\"settings_su_request_30\">30 sekundi</string>\n    <string name=\"settings_su_request_45\">45 sekundi</string>\n    <string name=\"settings_su_request_60\">60 sekundi</string>\n    <string name=\"superuser_access\">Pristup super-korisnika</string>\n    <string name=\"auto_response\">Automatski odgovor</string>\n    <string name=\"request_timeout\">Istek zahteva</string>\n    <string name=\"superuser_notification\">Notifikacije super-korisnika</string>\n    <string name=\"settings_su_reauth_title\">Ponovo odobri nakon ažuriranja</string>\n    <string name=\"settings_su_reauth_summary\">Ponovo traži permisije super-korisnika nakon ažuriranja aplikacija</string>\n    <string name=\"settings_su_tapjack_title\">Zaštita od tapjacking-a</string>\n    <string name=\"settings_su_tapjack_summary\">Prompt dijalog super-korisnika neće reagovati dok je prikriven drugim prozorom ili overlay-em</string>\n    <string name=\"settings_su_auth_title\">Autentifikacija korisnika</string>\n    <string name=\"settings_su_auth_summary\">Traži autentifikaciju korisnika tokom zahteva super-korisnika</string>\n    <string name=\"settings_su_auth_insecure\">Nijedan metod autentifikacije nije podešen na uređaju</string>\n    <string name=\"settings_su_restrict_title\">Ograniči korenske sposobnosti</string>\n    <string name=\"settings_su_restrict_summary\">Podrazumevano ograničava apl. super-korisnika. Upozorenje: ovo će većinu aplikacija skršiti. Ne omogućavaj, osim ako znaš šta radiš.</string>\n    <string name=\"settings_customization\">Prilagođavanje</string>\n    <string name=\"setting_add_shortcut_summary\">Dodaj lepu prečicu na početni ekran u slučaju da se ime i ikonica ne prepoznaju lako nakon skrivanja aplikacije</string>\n    <string name=\"settings_doh_title\">DNS preko HTTPS-a</string>\n    <string name=\"settings_doh_description\">Zaobilazno rešenje DNS trovanja u nekim nacijama</string>\n    <string name=\"settings_random_name_title\">Nasumično ime na izlazu</string>\n    <string name=\"settings_random_name_description\">Nasumično ime izlaznog fajla slika i tar fajlova radi sprečavanja detekcije</string>\n    <string name=\"multiuser_mode\">Višekorisnički režim</string>\n    <string name=\"settings_owner_only\">Samo vlasnik uređaja</string>\n    <string name=\"settings_owner_manage\">Određeno od strane vlasnika</string>\n    <string name=\"settings_user_independent\">Nezavisno od korisnika</string>\n    <string name=\"owner_only_summary\">Samo vlasnik ima pristup korenu</string>\n    <string name=\"owner_manage_summary\">Samo vlasnik može da pristupa korenu i da prima zahteve za njega</string>\n    <string name=\"user_independent_summary\">Svaki korisnik ima svoja pravila korena</string>\n    <string name=\"mount_namespace_mode\">Mount režim namespace-a</string>\n    <string name=\"settings_ns_global\">Globalni namespace</string>\n    <string name=\"settings_ns_requester\">Nasleđeni namespace</string>\n    <string name=\"settings_ns_isolate\">Izolovani namespace</string>\n    <string name=\"global_summary\">Sve korenske sesije koriste globalni mount namespace</string>\n    <string name=\"requester_summary\">Korenske sesije će naslediti namespace od podnosioca zahteva</string>\n    <string name=\"isolate_summary\">Svaka korenska sesija će imati svoj izolovani namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Ažuriranja Magisk-a</string>\n    <string name=\"progress_channel\">Notifikacije o progresu</string>\n    <string name=\"updated_channel\">Ažuriranje završeno</string>\n    <string name=\"download_complete\">Preuzimanje završeno</string>\n    <string name=\"download_file_error\">Greška pri preuzimanju fajla</string>\n    <string name=\"magisk_update_title\">Ažuriranje Magisk-a dostupno!</string>\n    <string name=\"updated_title\">Magisk je ažuriran</string>\n    <string name=\"updated_text\">Klikni da otvoriš aplikaciju</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Da</string>\n    <string name=\"no\">Ne</string>\n    <string name=\"repo_install_title\">Instaliraj %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Preuzmi</string>\n    <string name=\"reboot\">Ponovo pokreni</string>\n    <string name=\"close\">Zatvori</string>\n    <string name=\"release_notes\">Release notes</string>\n    <string name=\"flashing\">Flešovanje…</string>\n    <string name=\"running\">Pokretanje…</string>\n    <string name=\"done\">Završeno!</string>\n    <string name=\"done_action\">Pokretanje akcije %1$s završeno</string>\n    <string name=\"failure\">Neuspešno!</string>\n    <string name=\"hide_app_title\">Skrivanje Magisk aplikacije…</string>\n    <string name=\"open_link_failed_toast\">Nije pronađena aplikacija za otvaranje linka</string>\n    <string name=\"complete_uninstall\">Kompletna deinstalacija</string>\n    <string name=\"restore_img\">Povrati slike</string>\n    <string name=\"restore_img_msg\">Povratak…</string>\n    <string name=\"restore_done\">Povratak uspešan!</string>\n    <string name=\"restore_fail\">Fabrički bekap ne postoji!</string>\n    <string name=\"setup_fail\">Neuspešna postavka</string>\n    <string name=\"env_fix_title\">Potrebno dodatno podešavanje</string>\n    <string name=\"env_fix_msg\">Vaš uređaj zahteva dodatno podešavanje da bi Magisk radio kako treba. Da li želite nastaviti i pokrenuti ponovo?</string>\n    <string name=\"env_full_fix_msg\">Vaš uređaj zahteva ponovno flešovanje da bi Magisk radio kako treba. Reinstalirajte Magisk kroz aplikaciju, režim oporavka ne može dobiti tačne informacije o uređaju.</string>\n    <string name=\"setup_msg\">Pokretanje podešavanja okruženja…</string>\n    <string name=\"unsupport_magisk_title\">Nepodržana verzija Magisk-a</string>\n    <string name=\"unsupport_magisk_msg\">Ova verzija aplikacije ne podržava Magisk verzije manje od %1$s.\\n\\nAplikacija će se ponašati kao da Magisk nije instaliran. Molimo ažurirajte Magisk što pre.</string>\n    <string name=\"unsupport_general_title\">Nenormalno stanje</string>\n    <string name=\"unsupport_system_app_msg\">Pokretanje aplikacije kao sistemske nije podržano. Molimo postavite aplikaciju da bude korisnička.</string>\n    <string name=\"unsupport_other_su_msg\">Detektovan \\\"su\\\" binary koji nije Magisk-ov. Molimo uklonite konkurentno korensko rešenje i/ili reinstalirajte Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk je instaliran na eksterno skladište. Molimo pomerite apl. u interno skladište.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Skrivena Magisk aplikacija ne može nastaviti sa radom jer je koren izgubljen. Molimo povratite originalni APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Dozvolite permisiju za skladište da biste omogućili ovu funkcionalnost</string>\n    <string name=\"post_notifications_denied\">Dozvolite permisiju za notifikacije da biste omogućili ovu funkcionalnost</string>\n    <string name=\"install_unknown_denied\">Dozvolite \\\"instaliranje nepoznatih aplikacija\\\" da biste omogućili ovu funkcionalnost</string>\n    <string name=\"add_shortcut_title\">Dodaj prečicu na početni ekran</string>\n    <string name=\"add_shortcut_msg\">Nakon skrivanja aplikacije, njeno ime i ikonicu ćete teško prepoznati. Želite li dodati lepu prečicu na početni ekran?</string>\n    <string name=\"app_not_found\">Nije pronađena aplikacija za ovu akciju</string>\n    <string name=\"reboot_apply_change\">Ponovo pokreni da primeniš izmene</string>\n    <string name=\"restore_app_confirmation\">Ovo će vratiti skrivenu aplikaciju na originalnu. Da li stvarno to želite?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-be/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--Sections-->\n    <string name=\"modules\">Модулі</string>\n    <string name=\"superuser\">Правы суперкарыстальніка</string>\n    <string name=\"logs\">Журналы</string>\n    <string name=\"settings\">Налады</string>\n    <string name=\"install\">Усталёўка</string>\n    <string name=\"section_home\">Хатняя старонка</string>\n    <string name=\"section_theme\">Тэмы</string>\n    <!--Home-->\n    <string name=\"no_connection\">Злучэнне адсутнічае</string>\n    <string name=\"app_changelog\">Спіс змен</string>\n    <string name=\"loading\">Загрузка…</string>\n    <string name=\"update\">Абнавіць</string>\n    <string name=\"not_available\">Не</string>\n    <string name=\"hide\">Схаваць</string>\n    <string name=\"home_package\">Пакунак</string>\n    <string name=\"home_app_title\">Праграма</string>\n    <string name=\"home_notice_content\">Спампоўваце Magisk ТОЛЬКІ з афіцыйнай старонкі на GitHub. Файлы з невядомых крыніц могуць быць шкоднаснымі!</string>\n    <string name=\"home_support_title\">Падтрымайце нас</string>\n    <string name=\"home_item_source\">Зыходны код</string>\n    <string name=\"home_support_content\">Magisk ёсць і заўсёды будзе бясплатным праектам з адкрытым зыходным кодам. Але вы заўсёды можаце ахвяраваць нам на распрацоўку.</string>\n    <string name=\"home_installed_version\">Усталявана</string>\n    <string name=\"home_latest_version\">Апошні</string>\n    <string name=\"invalid_update_channel\">Хібны канал абнаўлення</string>\n    <string name=\"uninstall_magisk_title\">Выдаліць Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Усе модулі будуць адключаныя/выдаленыя!\\nRoot будзе выдалены!\\nВашыя даныя будуць зашыфраваныя!</string>\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Прымусова захаваць шыфраванне</string>\n    <string name=\"keep_dm_verity\">Захаваць AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Рэжым Recovery</string>\n    <string name=\"install_options_title\">Параметры</string>\n    <string name=\"install_method_title\">Метад</string>\n    <string name=\"install_next\">Далей</string>\n    <string name=\"install_start\">Усталяваць</string>\n    <string name=\"manager_download_install\">Націсніце, каб спампаваць і ўсталяваць</string>\n    <string name=\"direct_install\">Непасрэдная ўсталёўка (рэкамендуецца)</string>\n    <string name=\"install_inactive_slot\">Усталяваць у неактыўны слот (пасля OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Ваша прылада ПРЫМУСОВА перазапусціцца ў неактыўны слот!\\nВыкарыстоўвайце гэты параметр толькі пасля завяршэння OTA.\\nПрацягнуць?</string>\n    <string name=\"setup_title\">Дадатковая ўсталёўка</string>\n    <string name=\"select_patch_file\">Абраць файл і ўжыць да яго патч</string>\n    <string name=\"patch_file_msg\">Абярыце файл вобраза (*.img) альбо архіў ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Перазапуск праз 5 секунд…</string>\n    <string name=\"flash_screen_title\">Усталёўка</string>\n    <!--Superuser-->\n    <string name=\"su_request_title\">Запыт правоў суперкарыстальніка</string>\n    <string name=\"touch_filtered_warning\">Праграма перакрывае запыт на выдачу правоў суперкарыстальніка, таму Magisk не можа апрацаваць ваш адказ</string>\n    <string name=\"deny\">Адмовіць</string>\n    <string name=\"prompt\">Запытаць</string>\n    <string name=\"grant\">Даць</string>\n    <string name=\"su_warning\">Даць поўны доступ да вашай прылады.\\nКалі вы не ўпэўненыя, адхіліце запыт!</string>\n    <string name=\"forever\">Назаўсёды</string>\n    <string name=\"once\">Адзін раз</string>\n    <string name=\"tenmin\">10 хвіл</string>\n    <string name=\"twentymin\">20 хвіл</string>\n    <string name=\"thirtymin\">30 хвіл</string>\n    <string name=\"sixtymin\">60 хвіл</string>\n    <string name=\"su_allow_toast\">Праграме \\\"%1$s\\\" дадзеныя правы суперкарыстальніка</string>\n    <string name=\"su_deny_toast\">Праграме \\\"%1$s\\\" было адмоўлена ў правах суперкарыстальніка</string>\n    <string name=\"su_snack_grant\">Праграме \\\"%1$s\\\" дадзеныя правы суперкарыстальніка</string>\n    <string name=\"su_snack_deny\">Праграме \\\"%1$s\\\" адмоўлена ў правах суперкарыстальніка</string>\n    <string name=\"su_snack_notif_on\">Апавяшчэнні для \\\"%1$s\\\" уключаныя</string>\n    <string name=\"su_snack_notif_off\">Апавяшчэнні для \\\"%1$s\\\" адключаныя</string>\n    <string name=\"su_snack_log_on\">Вядзенне журнала для \\\"%1$s\\\" уключана</string>\n    <string name=\"su_snack_log_off\">Вядзенне журнала для \\\"%1$s\\\" адключана</string>\n    <string name=\"su_revoke_title\">Адклікаць?</string>\n    <string name=\"su_revoke_msg\">Адклікаць правы для \\\"%1$s\\\"?</string>\n    <string name=\"toast\">Выплыўныя апавяшчэнні</string>\n    <string name=\"none\">Нічога</string>\n    <string name=\"superuser_toggle_notification\">Апавяшчэнні</string>\n    <string name=\"superuser_toggle_revoke\">Адклікаць</string>\n    <string name=\"superuser_policy_none\">Праграма яшчэ не запытвала правоў суперкарыстальніка.</string>\n    <!--Logs-->\n    <string name=\"log_data_none\">Журналаў няма. Паспрабуйце даць праграмам правы суперкарыстальніка.</string>\n    <string name=\"log_data_magisk_none\">Журналы адсутнічаюць.</string>\n    <string name=\"menuSaveLog\">Захаваць журнал</string>\n    <string name=\"menuClearLog\">Ачысціць журнал</string>\n    <string name=\"logs_cleared\">Журнал паспяхова ачышчаны</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Мэтавы UID: %1$d</string>\n    <!--SafetyNet-->\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Паказваць сістэмныя праграмы</string>\n    <string name=\"show_os_app\">Паказваць сістэмныя праграмы</string>\n    <string name=\"hide_filter_hint\">Фільтраваць па назве</string>\n    <string name=\"hide_search\">Пошук</string>\n    <!--Module-->\n    <string name=\"no_info_provided\">(Няма інфармацыі)</string>\n    <string name=\"reboot_userspace\">Праграмны перазапуск</string>\n    <string name=\"reboot_recovery\">Перазапуск у Recovery</string>\n    <string name=\"reboot_bootloader\">Перазапуск у Bootloader</string>\n    <string name=\"reboot_download\">Перазапуск у Download</string>\n    <string name=\"reboot_edl\">Перазапуск у EDL</string>\n    <string name=\"module_version_author\">%1$s ад %2$s</string>\n    <string name=\"module_state_remove\">Выдаліць</string>\n    <string name=\"module_state_restore\">Аднавіць</string>\n    <string name=\"module_action_install_external\">Усталяваць са сховішча</string>\n    <string name=\"update_available\">Даступныя абнаўленні</string>\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Рэжым афармлення</string>\n    <string name=\"settings_dark_mode_message\">Абярыце рэжым, які вам больш даспадобы!</string>\n    <string name=\"settings_dark_mode_light\">Заўсёды светлы</string>\n    <string name=\"settings_dark_mode_system\">Сістэмны</string>\n    <string name=\"settings_dark_mode_dark\">Заўсёды цёмны</string>\n    <string name=\"settings_download_path_title\">Каталог спамповак</string>\n    <string name=\"settings_download_path_message\">Файлы будуць спампоўвацца ў %1$s</string>\n    <string name=\"settings_hide_app_title\">Схаваць праграму Magisk</string>\n    <string name=\"settings_hide_app_summary\">Усталяваць проксі-праграму з выпадковым ідэнтыфікатарам пакунка і адвольным значком</string>\n    <string name=\"settings_restore_app_title\">Аднавіць праграму Magisk</string>\n    <string name=\"settings_restore_app_summary\">Вярнуць праграму да зыходнага стану</string>\n    <string name=\"language\">Мова</string>\n    <string name=\"system_default\">(Сістэмная)</string>\n    <string name=\"settings_check_update_title\">Правяраць на абнаўленні</string>\n    <string name=\"settings_check_update_summary\">Перыядычна правяраць наяўнасць абнаўленняў ў фонавым рэжыме</string>\n    <string name=\"settings_update_channel_title\">Канал абнаўлення</string>\n    <string name=\"settings_update_stable\">Стабільны</string>\n    <string name=\"settings_update_beta\">Бэта</string>\n    <string name=\"settings_update_custom\">Адвольны канал</string>\n    <string name=\"settings_update_custom_msg\">Устаўце URL</string>\n    <string name=\"settings_hosts_title\">Пазасістэмны файл hosts</string>\n    <string name=\"settings_hosts_summary\">Падтрымка пазасістэмнага файла hosts для праграм, якія блакуюць рэкламу</string>\n    <string name=\"settings_hosts_toast\">Дададзены модуль пазасістэмнага файла hosts</string>\n    <string name=\"settings_app_name_hint\">Новая назва</string>\n    <string name=\"settings_app_name_helper\">Праграма будзе перапакаваная з гэтай назвай</string>\n    <string name=\"settings_app_name_error\">Хібны фармат</string>\n    <string name=\"settings_su_app_adb\">Праграмы і ADB</string>\n    <string name=\"settings_su_app\">Толькі праграмы</string>\n    <string name=\"settings_su_adb\">Толькі ADB</string>\n    <string name=\"settings_su_disable\">Адключана</string>\n    <string name=\"settings_su_request_10\">10 секунд</string>\n    <string name=\"settings_su_request_15\">15 секунд</string>\n    <string name=\"settings_su_request_20\">20 секунд</string>\n    <string name=\"settings_su_request_30\">30 секунд</string>\n    <string name=\"settings_su_request_45\">45 секунд</string>\n    <string name=\"settings_su_request_60\">60 секунд</string>\n    <string name=\"superuser_access\">Доступ суперкарыстальніка</string>\n    <string name=\"auto_response\">Аўтаматычны адказ</string>\n    <string name=\"request_timeout\">Чаканне адказу</string>\n    <string name=\"superuser_notification\">Апавяшчэнне суперкарыстальніка</string>\n    <string name=\"settings_su_reauth_title\">Паўторная аўтэнтыфікацыя пасля абнаўлення</string>\n    <string name=\"settings_su_reauth_summary\">Паўторна запытваць правы суперкарыстальніка пасля абнаўлення праграмы</string>\n    <string name=\"settings_su_tapjack_title\">Уключыць абарону ад перахоплівання ўводу</string>\n    <string name=\"settings_su_tapjack_summary\">Дыялог выдачы правоў суперкарыстальніка не будзе адказваць на ўвод, калі па-над ім знаходзяцца іншыя вокны</string>\n    <string name=\"settings_customization\">Персаналізацыя</string>\n    <string name=\"setting_add_shortcut_summary\">Дадаць на хатні экран прыгожы цэтлік на той выпадак, калі пасля хавання праграмы будзе цяжка разглядзець значок і назву</string>\n    <string name=\"settings_doh_title\">DNS паверх HTTPS</string>\n    <string name=\"settings_doh_description\">Абыходны шлях для DNS у некаторых краінах</string>\n    <string name=\"multiuser_mode\">Шматкарыстальніцкі рэжым</string>\n    <string name=\"settings_owner_only\">Толькі ўладальнік</string>\n    <string name=\"settings_owner_manage\">Кіраванне ўладальнікам</string>\n    <string name=\"settings_user_independent\">Правілы карыстальнікаў</string>\n    <string name=\"owner_only_summary\">Толькі ўладальніку даступны рэжым суперкарыстальніка</string>\n    <string name=\"owner_manage_summary\">Толькі ўладальнік кіруе доступам суперкарыстальніка і апрацоўвае запыты</string>\n    <string name=\"user_independent_summary\">Кожны карыстальнік кіруе правіламі доступу суперкарыстальніка</string>\n    <string name=\"mount_namespace_mode\">Наладка прастораў назваў</string>\n    <string name=\"settings_ns_global\">Агульная прастора назваў</string>\n    <string name=\"settings_ns_requester\">Спадкаемная прастора назваў</string>\n    <string name=\"settings_ns_isolate\">Ізаляваная прастора назваў</string>\n    <string name=\"global_summary\">Сеансы суперкарыстальніка выкарыстоўваюць агульную прастору назваў</string>\n    <string name=\"requester_summary\">Сеансы суперкарыстальніка выкарыстоўваюць спадкаемную прастору назваў</string>\n    <string name=\"isolate_summary\">Сеансы суперкарыстальніка выкарыстоўваюць ізаляваную прастору назваў</string>\n    <!--Notifications-->\n    <string name=\"update_channel\">Абнаўленні Magisk</string>\n    <string name=\"progress_channel\">Апавяшчэнні пра ход выканання</string>\n    <string name=\"download_complete\">Спампоўка завершаная</string>\n    <string name=\"download_file_error\">Не атрымалася спампаваць файл</string>\n    <string name=\"magisk_update_title\">Даступна абнаўленне Magisk!</string>\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Так</string>\n    <string name=\"no\">Не</string>\n    <string name=\"repo_install_title\">Усталяваць %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Спампаваць</string>\n    <string name=\"reboot\">Перазапуск</string>\n    <string name=\"release_notes\">Пра выпуск</string>\n    <string name=\"flashing\">Усталёўка…</string>\n    <string name=\"done\">Завершана!</string>\n    <string name=\"failure\">Не атрымалася</string>\n    <string name=\"hide_app_title\">Хаванне праграмы Magisk…</string>\n    <string name=\"open_link_failed_toast\">Праграмы для адкрыцця спасылкі не знойдзена</string>\n    <string name=\"complete_uninstall\">Поўнае выдаленне</string>\n    <string name=\"restore_img\">Аднавіць раздзелы</string>\n    <string name=\"restore_img_msg\">Аднаўленне…</string>\n    <string name=\"restore_done\">Аднаўленне завершана!</string>\n    <string name=\"restore_fail\">Рэзервовая копія адсутнічае!</string>\n    <string name=\"setup_fail\">Усталяваць не атрымалася</string>\n    <string name=\"env_fix_title\">Патрабуецца дадатковая ўсталёўка</string>\n    <string name=\"env_fix_msg\">Для карэктнай працы на вашай прыладзе спатрэбіцца ўсталяваць дадатковыя кампаненты. Хочаце працягнуць?</string>\n    <string name=\"setup_msg\">Наладка асяроддзя…</string>\n    <string name=\"unsupport_magisk_title\">Непадтрымліваемая версія Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Гэтая версія праграмы не падтрымлівае Magisk версіі ніжэй за %1$s.\\n\\nПраграма будзе працаваць так, як быццам Magisk не ўсталяваны. Як мага хутчэй абнавіце Magisk.</string>\n    <string name=\"unsupport_general_title\">Анамальны стан</string>\n    <string name=\"unsupport_system_app_msg\">Гэтую праграму немагчыма запусціць як сістэмную. Калі ласка, вярніце праграму да стану карыстальніцкай.</string>\n    <string name=\"unsupport_other_su_msg\">Выяўлены загад \\\"su\\\", які не належыць Magisk. Калі ласка, выдаліце іншы кліент.</string>\n    <string name=\"unsupport_external_storage_msg\">Праграма Magisk усталяваная ў вонкавым сховішчы. Калі ласка, перамясціце яе ва ўнітарнае сховішча.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Праграма не можа працягваць працу ў схаваным стане, бо root-доступ быў страчаны. Вярніце праграму да зыходнага стану.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Дайце доступ да сховішча, каб уключыць гэтую функцыю</string>\n    <string name=\"add_shortcut_title\">Дадаць цэтлік на хатні экран</string>\n    <string name=\"add_shortcut_msg\">Пасля хавання праграмы значок і назву можа быць цяжка знайсці. Хочаце дадаць цэтлік на хатні экран?</string>\n    <string name=\"app_not_found\">Не знойдзена праграм для апрацоўкі гэтага дзеяння</string>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-bg/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Модули</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Дневник</string>\n    <string name=\"settings\">Настройки</string>\n    <string name=\"install\">Инсталиране</string>\n    <string name=\"section_home\">Начало</string>\n    <string name=\"section_theme\">Теми</string>\n    <string name=\"denylist\">Черен списък</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Няма връзка</string>\n    <string name=\"app_changelog\">Списък с промени</string>\n    <string name=\"loading\">Зареждане…</string>\n    <string name=\"update\">Обновяване</string>\n    <string name=\"not_available\">Липсва</string>\n    <string name=\"hide\">Скриване</string>\n    <string name=\"home_package\">Пакет</string>\n    <string name=\"home_app_title\">Приложение</string>\n\n    <string name=\"home_notice_content\">Изтегляйте Magisk САМО от официалната страница в GitHub. Файловете от неизвестни източници могат да бъдат зловредни!</string>\n    <string name=\"home_support_title\">Подкрепете ни</string>\n    <string name=\"home_follow_title\">Последвайте ни</string>\n    <string name=\"home_item_source\">Изходен код</string>\n    <string name=\"home_support_content\">Magisk е, и винаги ще бъде, безплатен и с отворен изходен код. Въпреки това можете да покажете подкрапата си чрез дарение.</string>\n    <string name=\"home_installed_version\">Инсталиранo</string>\n    <string name=\"home_latest_version\">Последно</string>\n    <string name=\"invalid_update_channel\">Каналът за обновяване е недействителен</string>\n    <string name=\"uninstall_magisk_title\">Премахване на Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Всички модули ще бъдат изключени/премахнати. Достъпът до правата на суперпотребителя ще бъде премахнат!\\nАко вътрешното хранилище е разшифровано с Magisk, то ще бъде шифровано отново!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Запазване на наложеното шифроване</string>\n    <string name=\"keep_dm_verity\">Запазване на AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Режим за възстановяване</string>\n    <string name=\"install_options_title\">Настройки</string>\n    <string name=\"install_method_title\">Метод</string>\n    <string name=\"install_next\">Напред</string>\n    <string name=\"install_start\">Начало</string>\n    <string name=\"manager_download_install\">За да изтеглите и инсталирате, докоснете</string>\n    <string name=\"direct_install\">Директно инсталиране (препоръчително)</string>\n    <string name=\"install_inactive_slot\">Инсталиране в неактивен дял (след OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Устройство ЗАДЪЛЖИТЕЛНО ще зареди от текущия неактивен дял при следващия рестарт.\\nИзползвайте само при приключила инсталация на OTA.\\nПродължаване?</string>\n    <string name=\"setup_title\">Допълнителни настройки</string>\n    <string name=\"select_patch_file\">Избор и закърпване на файл</string>\n    <string name=\"patch_file_msg\">Изберете образ (*.img) или архив на ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Рестартиране след 5 секунди…</string>\n    <string name=\"flash_screen_title\">Инсталиране</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Запитване за достъп</string>\n    <string name=\"touch_filtered_warning\">Тъй като друго приложение закрива заявката за достъп до правата на суперпотребителя, Magisk не може да потвърди вашия отговор</string>\n    <string name=\"deny\">Отказ</string>\n    <string name=\"prompt\">Запитване</string>\n    <string name=\"grant\">Разрешаване</string>\n    <string name=\"su_warning\">Дава пълен достъп до устройството.\\nОткажете, ако не сте сигурни.</string>\n    <string name=\"forever\">Винаги</string>\n    <string name=\"once\">Веднъж</string>\n    <string name=\"tenmin\">10 мин.</string>\n    <string name=\"twentymin\">20 мин.</string>\n    <string name=\"thirtymin\">30 мин.</string>\n    <string name=\"sixtymin\">60 мин.</string>\n    <string name=\"su_allow_toast\">%1$s получи достъп до суперпотребителя</string>\n    <string name=\"su_deny_toast\">%1$s не получи достъп до суперпотребителя</string>\n    <string name=\"su_snack_grant\">Достъп до суперпотребителя е разрешен на %1$s</string>\n    <string name=\"su_snack_deny\">Достъп до суперпотребителя е отказан на %1$s</string>\n    <string name=\"su_snack_notif_on\">Известията за %1$s са включени</string>\n    <string name=\"su_snack_notif_off\">Известията за %1$s са изключени</string>\n    <string name=\"su_snack_log_on\">Записването в дневника за %1$s е включено</string>\n    <string name=\"su_snack_log_off\">Записването в дневника за %1$s е изключено</string>\n    <string name=\"su_revoke_title\">Оттегляне?</string>\n    <string name=\"su_revoke_msg\">Потвърждавате ли оттегляне на достъпа на %1$s?</string>\n    <string name=\"toast\">Тост</string>\n    <string name=\"none\">Без</string>\n\n    <string name=\"superuser_toggle_notification\">Известия</string>\n    <string name=\"superuser_toggle_revoke\">Оттегляне</string>\n    <string name=\"superuser_policy_none\">За момента няма приложения, които да са поискали достъп до правата на суперпотребителя.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Дневникът е празен</string>\n    <string name=\"log_data_magisk_none\">Дневникът на Magisk е празен</string>\n    <string name=\"menuSaveLog\">Запазване на дневника</string>\n    <string name=\"menuClearLog\">Изчистване на дневника</string>\n    <string name=\"logs_cleared\">Дневникът е изчистен</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Целеви UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Показване на системни приложения</string>\n    <string name=\"show_os_app\">Показване на приложения на ОС</string>\n    <string name=\"hide_filter_hint\">Филтрира по име</string>\n    <string name=\"hide_search\">Търсене</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Липсва информация)</string>\n    <string name=\"reboot_userspace\">Бърз рестарт</string>\n    <string name=\"reboot_recovery\">Рестарт в режим за възстановяване</string>\n    <string name=\"reboot_bootloader\">Рестарт в bootloader</string>\n    <string name=\"reboot_download\">Рестарт в режим за изтегляне</string>\n    <string name=\"reboot_edl\">Рестарт в EDL</string>\n    <string name=\"module_version_author\">%1$s от %2$s</string>\n    <string name=\"module_state_remove\">Премахване</string>\n    <string name=\"module_state_restore\">Възстановяване</string>\n    <string name=\"module_action_install_external\">Инсталиране от хранилището</string>\n    <string name=\"update_available\">Има обновяване</string>\n    <string name=\"suspend_text_riru\">Модулът е спрян, защото е включено: %1$s</string>\n    <string name=\"suspend_text_zygisk\">Модулът е спрян, защото не е включено: %1$s</string>\n    <string name=\"zygisk_module_unloaded\">Поради несъвместимост модулът Zygisk не е зареден</string>\n    <string name=\"module_empty\">Не са инсталирани модули</string>\n    <string name=\"confirm_install\">Инсталиране на модула %1$s?</string>\n    <string name=\"confirm_install_title\">Подвърждаване на инсталиране</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Режим на темата</string>\n    <string name=\"settings_dark_mode_message\">Изберете режима, който ви отива най-много!</string>\n    <string name=\"settings_dark_mode_light\">Винаги светло</string>\n    <string name=\"settings_dark_mode_system\">Според системата</string>\n    <string name=\"settings_dark_mode_dark\">Винаги тъмно</string>\n    <string name=\"settings_download_path_title\">Папка за изтегляния</string>\n    <string name=\"settings_download_path_message\">Файловете ще бъдат запазвани в %1$s</string>\n    <string name=\"settings_hide_app_title\">Скриване на приложението на Magisk</string>\n    <string name=\"settings_hide_app_summary\">Инсталира междинно приложение с произволен идестификатор и име</string>\n    <string name=\"settings_restore_app_title\">Възстановяване на приложението на Magisk</string>\n    <string name=\"settings_restore_app_summary\">Показва и възстановява оригиналното приложение</string>\n    <string name=\"language\">Език</string>\n    <string name=\"system_default\">(Според системата)</string>\n    <string name=\"settings_check_update_title\">Проверка за обновяване</string>\n    <string name=\"settings_check_update_summary\">Периодична проверка за обновяване във фонов режим</string>\n    <string name=\"settings_update_channel_title\">Канал за обновяване</string>\n    <string name=\"settings_update_stable\">Стабилен</string>\n    <string name=\"settings_update_beta\">Бета</string>\n    <string name=\"settings_update_custom\">Потребителски</string>\n    <string name=\"settings_update_custom_msg\">Адрес на потребителски канал</string>\n    <string name=\"settings_zygisk_summary\">Изпълняване на части от Magisk в демон на zygote</string>\n    <string name=\"settings_denylist_title\">Налагане на черен списък</string>\n    <string name=\"settings_denylist_summary\">На процесите в черния списък ще бъдат възстановени всички модификации, направени от Magisk</string>\n    <string name=\"settings_denylist_config_title\">Настройка на черен списък</string>\n    <string name=\"settings_denylist_config_summary\">Изберете процесите, които да бъдат включени в черния списък</string>\n    <string name=\"settings_hosts_title\">Безсистемни хостове</string>\n    <string name=\"settings_hosts_summary\">Поддръжка на безсистемни хостове, ползвани от приложения за спиране на реклами</string>\n    <string name=\"settings_hosts_toast\">Модулът за безсистемни хостове е добавен</string>\n    <string name=\"settings_app_name_hint\">Ново име</string>\n    <string name=\"settings_app_name_helper\">Приложението ще бъде пакетирано с това име</string>\n    <string name=\"settings_app_name_error\">Форматът е недействителен</string>\n    <string name=\"settings_su_app_adb\">Приложения и ADB</string>\n    <string name=\"settings_su_app\">Само приложения</string>\n    <string name=\"settings_su_adb\">Само ADB</string>\n    <string name=\"settings_su_disable\">Изключен</string>\n    <string name=\"settings_su_request_10\">10 секунди</string>\n    <string name=\"settings_su_request_15\">15 секунди</string>\n    <string name=\"settings_su_request_20\">20 секунди</string>\n    <string name=\"settings_su_request_30\">30 секунди</string>\n    <string name=\"settings_su_request_45\">45 секунди</string>\n    <string name=\"settings_su_request_60\">60 секунди</string>\n    <string name=\"superuser_access\">Достъп до суперпотребителя</string>\n    <string name=\"auto_response\">Автоматичен отговор</string>\n    <string name=\"request_timeout\">Време за запитване</string>\n    <string name=\"superuser_notification\">Известие за суперпотребителя</string>\n    <string name=\"settings_su_reauth_title\">Повторно запитване след обновяване</string>\n    <string name=\"settings_su_reauth_summary\">Повторно запитване за достъп до правата на суперпотребителя след обновяване на приложения</string>\n    <string name=\"settings_su_tapjack_title\">Предпазване от измамно докосване</string>\n    <string name=\"settings_su_tapjack_summary\">Прозорецът за достъп до суперпотребителя няма да реагира, докато е покрит от друг прозорец или слой</string>\n    <string name=\"settings_customization\">Приспособяване</string>\n    <string name=\"setting_add_shortcut_summary\">Добавя красив пряк път на началния екран ако името или текущата икона на скритото приложение са трудни за разпознаване</string>\n    <string name=\"settings_doh_title\">DNS през HTTPS</string>\n    <string name=\"settings_doh_description\">Заобикаля „отравянето“ на DNS в някои държави</string>\n\n    <string name=\"multiuser_mode\">Многопотребителски режим</string>\n    <string name=\"settings_owner_only\">Само собственика</string>\n    <string name=\"settings_owner_manage\">Управление от страна на собственика</string>\n    <string name=\"settings_user_independent\">Независимо от потребителя</string>\n    <string name=\"owner_only_summary\">Само собственикът има достъп до суперпотребителя</string>\n    <string name=\"owner_manage_summary\">Само собственикът може да управлява достъпа до суперпотребителя и да получава запитвания за достъп</string>\n    <string name=\"user_independent_summary\">Всеки потребител има собствени правила за достъп до суперпотребителя</string>\n\n    <string name=\"mount_namespace_mode\">Монтиране по пространства от имена</string>\n    <string name=\"settings_ns_global\">Общо</string>\n    <string name=\"settings_ns_requester\">Наследено</string>\n    <string name=\"settings_ns_isolate\">Изолирано</string>\n    <string name=\"global_summary\">Всички сесии с достъп до суперпотребителя използват общото пространство от имена</string>\n    <string name=\"requester_summary\">Всички сесии с достъп до суперпотребителя наследяват пространството от имена на запитващото приложение</string>\n    <string name=\"isolate_summary\">Всички сесии с достъп до суперпотребителя получават изолирани пространства от имена</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Обновяване на Magisk</string>\n    <string name=\"progress_channel\">Известия за напредък</string>\n    <string name=\"updated_channel\">Завършено обновяване</string>\n    <string name=\"download_complete\">Завършено изтегляне</string>\n    <string name=\"download_file_error\">Грешка при изтегляне на файл</string>\n    <string name=\"magisk_update_title\">Има обновяване на Magisk!</string>\n    <string name=\"updated_title\">Magisk е обновен</string>\n    <string name=\"updated_text\">За да отворите приложението, докоснете</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Да</string>\n    <string name=\"no\">Не</string>\n    <string name=\"repo_install_title\">Инсталиране на %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Изтегляне</string>\n    <string name=\"reboot\">Рестартиране</string>\n    <string name=\"release_notes\">Бележки по изданието</string>\n    <string name=\"flashing\">Инсталиране…</string>\n    <string name=\"done\">Готово!</string>\n    <string name=\"failure\">Грешка!</string>\n    <string name=\"hide_app_title\">Скриване на приложението на Magisk…</string>\n    <string name=\"open_link_failed_toast\">Не е намерено приложение, с което препратката да бъде отворена</string>\n    <string name=\"complete_uninstall\">Премахване</string>\n    <string name=\"restore_img\">Възстановяване на образи</string>\n    <string name=\"restore_img_msg\">Възстановяване…</string>\n    <string name=\"restore_done\">Възстановяването е успешно!</string>\n    <string name=\"restore_fail\">На устройството липсва резервно копие на заводския образ!</string>\n    <string name=\"setup_fail\">Грешка при първоначална настройка</string>\n    <string name=\"env_fix_title\">Необходима е допълнителна настройка</string>\n    <string name=\"env_fix_msg\">За да работи Magisk нормално, устройството се нуждае от допълнителна настройка. Да бъде ли продължено след рестарт?</string>\n    <string name=\"env_full_fix_msg\">За да работи Magisk нормално, е необходимо да бъде инсталиран отново. Преинсталирайте Magisk от приложението, защото режимът за възстановяване не получава необходимата информация за устройството.</string>\n    <string name=\"setup_msg\">Надстройка на средата…</string>\n    <string name=\"unsupport_magisk_title\">Неподдържано издание на Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Това издание на приложението не поддържа издания на Magisk преди %1$s.\\n\\nПриложението ще се държи все едно няма инсталиран Magisk. Обновете Magisk възможно най-скоро.</string>\n    <string name=\"unsupport_general_title\">Неочаквано състояние</string>\n    <string name=\"unsupport_system_app_msg\">Приложението не се поддържа да работи като системно. Върнете го като потребителско.</string>\n    <string name=\"unsupport_other_su_msg\">Намерен е двоичен файл „su“, който не е от Magisk. Премахнете всички други решения за достъп до правата на суперпотребителя и/или инсталирайте Magisk отново.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk е инсталиран във външно хранилище. Преместете приложението във вътрешното хранилище.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Скритото приложение на Magisk не може да продължи да работи, защото достъпът до правата на суперпотребителя е загубен. Възстановете оригиналното приложение.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">За да използвате нази възможност, разрешете достъп до хранилището</string>\n    <string name=\"post_notifications_denied\">За да използвате нази възможност, разрешете достъп до известията</string>\n    <string name=\"install_unknown_denied\">За да използвате тази възможност, разрешете „инсталиране на неизвестни приложения“</string>\n    <string name=\"add_shortcut_title\">Добавяне на пряк път на началния екран</string>\n    <string name=\"add_shortcut_msg\">След скриване на приложението името или икона може да станат трудни за разпознаване. Желаете ли да бъде добаве красив пряк път на началния екран?</string>\n    <string name=\"app_not_found\">Не е намерено приложение, което да извърши действието</string>\n    <string name=\"reboot_apply_change\">Рестартиране за прилагане на промените</string>\n    <string name=\"restore_app_confirmation\">По този начин ще възстановите скритото приложение в първоначалното му състояние. Това ли желаете да бъде направено?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-bn/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">মডিউল</string>\n    <string name=\"superuser\">সুপার ইউজার</string>\n    <string name=\"logs\">লগ</string>\n    <string name=\"settings\">সেটিংস</string>\n    <string name=\"install\">ইনস্টল করুন</string>\n    <string name=\"section_home\">হোম</string>\n    <string name=\"section_theme\">থিম</string>\n    <string name=\"denylist\">ডিনাইলিস্ট</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">কোনো সংযোগ উপলব্ধ নেই</string>\n    <string name=\"app_changelog\">চেঞ্জলগ</string>\n    <string name=\"loading\">লোড হচ্ছে…</string>\n    <string name=\"update\">হালনাগাদ</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">লুকান</string>\n    <string name=\"home_package\">প্যাকেজ</string>\n    <string name=\"home_app_title\">অ্যাপ</string>\n\n    <string name=\"home_notice_content\">শুধুমাত্র অফিসিয়াল গিটহাব পেজ থেকে ম্যাজিস্ক ডাউনলোড করুন। অজানা উৎস থেকে ফাইল ক্ষতিকর হতে পারে!</string>\n    <string name=\"home_support_title\">আমাদের সমর্থন</string>\n    <string name=\"home_follow_title\">আমাদের অনুসরণ করো</string>\n    <string name=\"home_item_source\">সূত্র</string>\n    <string name=\"home_support_content\">ম্যাজিস্ক হল, এবং সবসময় থাকবে, বিনামূল্যে, এবং ওপেন সোর্স। তবে আপনি দান করার মাধ্যমে আমাদের দেখাতে পারেন যে আপনি যত্নশীল।</string>\n    <string name=\"home_installed_version\">ইনস্টল করা হয়েছে</string>\n    <string name=\"home_latest_version\">সর্বশেষ</string>\n    <string name=\"invalid_update_channel\">অবৈধ আপডেট চ্যানেল</string>\n    <string name=\"uninstall_magisk_title\">ম্যাজিস্ক আনইনস্টল করুন</string>\n    <string name=\"uninstall_magisk_msg\">সমস্ত মডিউল নিষ্ক্রিয়/মুছে ফেলা হবে!\\nরুট সরানো হবে!\\nম্যাজিস্ক ব্যবহারের মাধ্যমে এনক্রিপ্ট করা যে কোনও অভ্যন্তরীণ স্টোরেজ পুনরায় এনক্রিপ্ট করা হবে!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">এনক্রিপশন সংরক্ষণ করুন</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity সংরক্ষণ করুন</string>\n    <string name=\"recovery_mode\">পুনরুদ্ধার অবস্থা</string>\n    <string name=\"install_options_title\">অপশন</string>\n    <string name=\"install_method_title\">পদ্ধতি</string>\n    <string name=\"install_next\">পরবর্তী</string>\n    <string name=\"install_start\">চলো যাই</string>\n    <string name=\"manager_download_install\">ডাউনলোড এবং ইনস্টল করুন</string>\n    <string name=\"direct_install\">সরাসরি ইনস্টল</string>\n    <string name=\"install_inactive_slot\">নিষ্ক্রিয় স্লটে ইনস্টল করুন (OTA এর পরে)</string>\n    <string name=\"install_inactive_slot_msg\">রিবুট করার পরে আপনার ডিভাইসটিকে বর্তমান নিষ্ক্রিয় স্লটে বুট করতে বাধ্য করা হবে!\\It হয়ে গেলেই এই বিকল্পটি ব্যবহার করুন।\\চালিয়ে রাখবেন?</string>\n    <string name=\"setup_title\">অতিরিক্ত সেটআপ</string>\n    <string name=\"select_patch_file\">একটি ফাইল নির্বাচন করুন এবং প্যাচ করুন</string>\n    <string name=\"patch_file_msg\">একটি কাঁচা চিত্র (*.img) বা একটি ODIN টারফাইল (*.tar) নির্বাচন করুন</string>\n    <string name=\"reboot_delay_toast\">৫ সেকেন্ডের মধ্যে রিবুট হচ্ছে...</string>\n    <string name=\"flash_screen_title\">স্থাপন</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">সুপার ইউজার অনুরোধ/স্ট্রিং</string>\n    <string name=\"touch_filtered_warning\">যেহেতু একটি অ্যাপ সুপার ব্যবহারকারীর অনুরোধকে অস্পষ্ট করছে, ম্যাজিস্ক আপনার প্রতিক্রিয়া যাচাই করতে পারে না</string>\n    <string name=\"deny\">অস্বীকার করুন</string>\n    <string name=\"prompt\">শীঘ্র</string>\n    <string name=\"grant\">প্রদান</string>\n    <string name=\"su_warning\">আপনার ডিভাইসে সম্পূর্ণ অ্যাক্সেস মঞ্জুর করে৷\\nআপনি নিশ্চিত না হলে অস্বীকার করুন!</string>\n    <string name=\"forever\">চিরতরে</string>\n    <string name=\"once\">একদা</string>\n    <string name=\"tenmin\">১০ মিনিট</string>\n    <string name=\"twentymin\">২০ মিনিট</string>\n    <string name=\"thirtymin\">৩০ মিনিট</string>\n    <string name=\"sixtymin\">৬০ মিনিট</string>\n    <string name=\"su_allow_toast\">%1$s সুপার ইউজার অধিকার দেওয়া হয়েছিল</string>\n    <string name=\"su_deny_toast\">%1$s সুপার ইউজার অধিকার অস্বীকার করা হয়েছিল</string>\n    <string name=\"su_snack_grant\">%1$s-এর সুপার ব্যবহারকারীর অধিকার মঞ্জুর করা হয়েছে</string>\n    <string name=\"su_snack_deny\">%1$s-এর সুপার ব্যবহারকারীর অধিকার অস্বীকার করা হয়েছে৷</string>\n    <string name=\"su_snack_notif_on\">%1$s-এর বিজ্ঞপ্তিগুলি সক্ষম করা হয়েছে৷</string>\n    <string name=\"su_snack_notif_off\">%1$s-এর বিজ্ঞপ্তিগুলি অক্ষম করা হয়েছে৷</string>\n    <string name=\"su_snack_log_on\">%1$s এর লগিং সক্ষম করা হয়েছে৷</string>\n    <string name=\"su_snack_log_off\">%1$s এর লগিং অক্ষম করা হয়েছে৷</string>\n    <string name=\"su_revoke_title\">প্রত্যাহার করুন?</string>\n    <string name=\"su_revoke_msg\">%1$s সুপার ব্যবহারকারীর অধিকার প্রত্যাহার করতে নিশ্চিত করুন৷</string>\n    <string name=\"toast\">টোস্ট</string>\n    <string name=\"none\">কোনোটিই নয়</string>\n    <string name=\"superuser_toggle_notification\">বিজ্ঞপ্তি</string>\n    <string name=\"superuser_toggle_revoke\">প্রত্যাহার করুন</string>\n    <string name=\"superuser_policy_none\">কোনো অ্যাপ এখনও সুপার ইউজারের অনুমতি চায়নি।</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">আপনি লগ-মুক্ত, আপনার রুট অ্যাপগুলি আরও ব্যবহার করার চেষ্টা করুন৷</string>\n    <string name=\"log_data_magisk_none\">ম্যাজিস্ক লগগুলি খালি, এটি অদ্ভুত</string>\n    <string name=\"menuSaveLog\">লগ সংরক্ষণ</string>\n    <string name=\"menuClearLog\">এখন লগ সাফ করুন</string>\n    <string name=\"logs_cleared\">লগ সফলভাবে সাফ করা হয়েছে৷</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">লক্ষ্য UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">সিস্টেম অ্যাপ দেখান</string>\n    <string name=\"show_os_app\">ওএস অ্যাপ দেখান</string>\n    <string name=\"hide_filter_hint\">নাম অনুসারে ফিল্টার করুন</string>\n    <string name=\"hide_search\">অনুসন্ধান করুন</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(কোন তথ্য প্রদান করা হয়</string>\n    <string name=\"reboot_userspace\">নরম রিবুট</string>\n    <string name=\"reboot_recovery\">রিকভারিতে রিবুট করুন</string>\n    <string name=\"reboot_bootloader\">বুটলোডারে রিবুট করুন</string>\n    <string name=\"reboot_download\">ডাউনলোড করতে রিবুট করুন</string>\n    <string name=\"reboot_edl\">EDL এ রিবুট করুন</string>\n    <string name=\"module_version_author\">%1$s by %2$s</string>\n    <string name=\"module_state_remove\">অপসারণ</string>\n    <string name=\"module_state_restore\">পুনরুদ্ধার করুন</string>\n    <string name=\"module_action_install_external\">স্টোরেজ থেকে ইনস্টল করুন</string>\n    <string name=\"update_available\">আপডেট উপলব্ধ</string>\n    <string name=\"suspend_text_riru\">মডিউল সাসপেন্ড করা হয়েছে কারণ %1$s সক্ষম হয়েছে৷</string>\n    <string name=\"suspend_text_zygisk\">মডিউল সাসপেন্ড করা হয়েছে কারণ %1$s সক্ষম করা নেই৷</string>\n    <string name=\"zygisk_module_unloaded\">অসামঞ্জস্যতার কারণে জাইগিস্ক মডিউল লোড করা হয়নি</string>\n    <string name=\"module_empty\">কোন মডিউল ইনস্টল করা নেই</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">থিম মোড</string>\n    <string name=\"settings_dark_mode_message\">আপনার শৈলী সবচেয়ে উপযুক্ত মোড নির্বাচন করুন!</string>\n    <string name=\"settings_dark_mode_light\">সবসময় আলো</string>\n    <string name=\"settings_dark_mode_system\">সিস্টেম অনুসরণ করুন</string>\n    <string name=\"settings_dark_mode_dark\">সবসময় অন্ধকার</string>\n    <string name=\"settings_download_path_title\">পাথ ডাউনলোড করুন</string>\n    <string name=\"settings_download_path_message\">ফাইলগুলি %1$s এ সংরক্ষণ করা হবে৷</string>\n    <string name=\"settings_hide_app_title\">ম্যাজিস্ক অ্যাপটি লুকান</string>\n    <string name=\"settings_hide_app_summary\">একটি র্যান্ডম প্যাকেজ আইডি এবং কাস্টম অ্যাপ লেব সহ একটি প্রক্সি অ্যাপ ইনস্টল করুনl</string>\n    <string name=\"settings_restore_app_title\">ম্যাজিস্ক অ্যাপটি পুনরুদ্ধার করুন</string>\n    <string name=\"settings_restore_app_summary\">অ্যাপটি আড়াল করুন এবং আসলটি পুনরুদ্ধার করুন</string>\n    <string name=\"language\">ভাষা</string>\n    <string name=\"system_default\">(সিস্টেমের ডিফল্ট)</string>\n    <string name=\"settings_check_update_title\">আপডেট চেক করুন</string>\n    <string name=\"settings_check_update_summary\">পর্যায়ক্রমে পটভূমিতে আপডেটের জন্য চেক করুন</string>\n    <string name=\"settings_update_channel_title\">চ্যানেল আপডেট করুন</string>\n    <string name=\"settings_update_stable\">স্থিতিশীল</string>\n    <string name=\"settings_update_beta\">বেটা</string>\n    <string name=\"settings_update_custom\">কাস্টম</string>\n    <string name=\"settings_update_custom_msg\">একটি কাস্টম চ্যানেল URL সন্নিবেশ করুন</string>\n    <string name=\"settings_zygisk_summary\">জাইগোট ডেমনে ম্যাজিস্কের অংশগুলি চালান</string>\n    <string name=\"settings_denylist_title\">তালিকা অস্বীকার করুন প্রয়োগ করুন</string>\n    <string name=\"settings_denylist_summary\">ডিনালিস্টের প্রসেসগুলিতে সমস্ত ম্যাজিস্ক পরিবর্তনগুলি ফিরিয়ে দেওয়া হবে</string>\n    <string name=\"settings_denylist_config_title\">অস্বীকার তালিকা কনফিগার করুন</string>\n    <string name=\"settings_denylist_config_summary\">অস্বীকৃত তালিকায় অন্তর্ভুক্ত করার জন্য প্রক্রিয়াগুলি নির্বাচন করুন৷</string>\n    <string name=\"settings_hosts_title\">সিস্টেমহীন হোস্ট</string>\n    <string name=\"settings_hosts_summary\">বিজ্ঞাপন ব্লকিং অ্যাপের জন্য সিস্টেমলেস হোস্ট</string>\n    <string name=\"settings_hosts_toast\">সিস্টেমহীন হোস্ট মডিউল যোগ করা হয়েছে</string>\n    <string name=\"settings_app_name_hint\">নতুন নাম</string>\n    <string name=\"settings_app_name_helper\">অ্যাপটি এই নামের সাথে পুনরায় প্যাকেজ করা হবে</string>\n    <string name=\"settings_app_name_error\">ভুল ফরম্যাট</string>\n    <string name=\"settings_su_app_adb\">অ্যাপস এবং এডিবি</string>\n    <string name=\"settings_su_app\">শুধুমাত্র অ্যাপস</string>\n    <string name=\"settings_su_adb\">শুধুমাত্র এডিবি</string>\n    <string name=\"settings_su_disable\">অক্ষম</string>\n    <string name=\"settings_su_request_10\">১০ সেকেন্ড</string>\n    <string name=\"settings_su_request_15\">১৫ সেকেন্ড</string>\n    <string name=\"settings_su_request_20\">২০ সেকেন্ড</string>\n    <string name=\"settings_su_request_30\">৩০ সেকেন্ড</string>\n    <string name=\"settings_su_request_45\">৪৫ সেকেন্ড</string>\n    <string name=\"settings_su_request_60\">৬০ সেকেন্ড</string>\n    <string name=\"superuser_access\">সুপার ইউজার অ্যাক্সেস</string>\n    <string name=\"auto_response\">স্বয়ংক্রিয় প্রতিক্রিয়া</string>\n    <string name=\"request_timeout\">অনুরোধের সময়সীমা শেষ</string>\n    <string name=\"superuser_notification\">সুপার ইউজার বিজ্ঞপ্তি</string>\n    <string name=\"settings_su_reauth_title\">আপগ্রেড করার পরে পুনরায় প্রমাণীকরণ করুন</string>\n    <string name=\"settings_su_reauth_summary\">অ্যাপগুলি আপগ্রেড করার পরে আবার সুপার ইউজার অনুমতির জন্য জিজ্ঞাসা করুন</string>\n    <string name=\"settings_su_tapjack_title\">ট্যাপজ্যাকিং সুরক্ষা</string>\n    <string name=\"settings_su_tapjack_summary\">সুপার ইউজার প্রম্পট ডায়ালগ অন্য কোনো উইন্ডো বা ওভারলে দ্বারা অস্পষ্ট থাকাকালীন ইনপুটটিতে সাড়া দেবে না</string>\n    <string name=\"settings_customization\">কাস্টমাইজেশন</string>\n    <string name=\"setting_add_shortcut_summary\">অ্যাপটি লুকানোর পরে নাম এবং আইকন সনাক্ত করা কঠিন হলে হোম স্ক্রিনে একটি সুন্দর শর্টকাট যোগ করুন</string>\n    <string name=\"settings_doh_title\">HTTPS এর উপর ডিএনএস</string>\n    <string name=\"settings_doh_description\">কিছু দেশে ডিএনএস বিষক্রিয়ার সমাধান</string>\n\n    <string name=\"multiuser_mode\">মাল্টিউজার মোড</string>\n    <string name=\"settings_owner_only\">শুধুমাত্র ডিভাইসের মালিক</string>\n    <string name=\"settings_owner_manage\">ডিভাইস মালিক পরিচালিত</string>\n    <string name=\"settings_user_independent\">ব্যবহারকারী-স্বাধীন</string>\n    <string name=\"owner_only_summary\">শুধুমাত্র মালিকের রুট অ্যাক্সেস আছে</string>\n    <string name=\"owner_manage_summary\">শুধুমাত্র মালিকই রুট অ্যাক্সেস পরিচালনা করতে পারে এবং অনুরোধ প্রম্পট গ্রহণ করতে পারে</string>\n    <string name=\"user_independent_summary\">প্রতিটি ব্যবহারকারীর নিজস্ব পৃথক রুট নিয়ম আছে</string>\n\n    <string name=\"mount_namespace_mode\">মাউন্ট নেমস্পেস মোড</string>\n    <string name=\"settings_ns_global\">গ্লোবাল নেমস্পেস</string>\n    <string name=\"settings_ns_requester\">নেমস্পেস ইনহেরিট করুন</string>\n    <string name=\"settings_ns_isolate\">বিচ্ছিন্ন নামস্থান</string>\n    <string name=\"global_summary\">সমস্ত রুট সেশন গ্লোবাল মাউন্ট নেমস্পেস ব্যবহার করে</string>\n    <string name=\"requester_summary\">রুট সেশন অনুরোধকারীর নামস্থান উত্তরাধিকারী হবে</string>\n    <string name=\"isolate_summary\">প্রতিটি রুট সেশনের নিজস্ব বিচ্ছিন্ন নামস্থান থাকবে</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">ম্যাজিস্ক আপডেট</string>\n    <string name=\"progress_channel\">অগ্রগতি বিজ্ঞপ্তি</string>\n    <string name=\"updated_channel\">আপডেট সম্পূর্ণ</string>\n    <string name=\"download_complete\">ডাউনলোড শেষ</string>\n    <string name=\"download_file_error\">ফাইল ডাউনলোড করার সময় ত্রুটি</string>\n    <string name=\"magisk_update_title\">ম্যাজিস্ক আপডেট উপলব্ধ!</string>\n    <string name=\"updated_title\">ম্যাজিস্ক আপডেট</string>\n    <string name=\"updated_text\">অ্যাপ খুলতে আলতো চাপুন</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">হ্যাঁ</string>\n    <string name=\"no\">না</string>\n    <string name=\"repo_install_title\">ইনস্টল করুন %1$s %2$s(%3$d)</string>\n    <string name=\"download\">ডাউনলোড করুন</string>\n    <string name=\"reboot\">রিবুট করুন</string>\n    <string name=\"release_notes\">অব্যাহতি পত্র</string>\n    <string name=\"flashing\">ঝলকানি…</string>\n    <string name=\"done\">সম্পন্ন!</string>\n    <string name=\"failure\">ব্যর্থ হয়েছে!</string>\n    <string name=\"hide_app_title\">ম্যাজিস্ক অ্যাপটি লুকিয়ে রাখছে…</string>\n    <string name=\"open_link_failed_toast\">লিঙ্ক খোলার জন্য কোনো অ্যাপ পাওয়া যায়নি</string>\n    <string name=\"complete_uninstall\">সম্পূর্ণ আনইনস্টল করুন</string>\n    <string name=\"restore_img\">ছবি পুনরুদ্ধার করুন</string>\n    <string name=\"restore_img_msg\">পুনরুদ্ধার করা হচ্ছে…</string>\n    <string name=\"restore_done\">পুনরুদ্ধার করা হয়েছে!</string>\n    <string name=\"restore_fail\">স্টক ব্যাকআপ বিদ্যমান নেই!</string>\n    <string name=\"setup_fail\">সেটআপ ব্যর্থ হয়েছে৷</string>\n    <string name=\"env_fix_title\">অতিরিক্ত সেটআপ প্রয়োজন</string>\n    <string name=\"env_fix_msg\">ম্যাজিস্ক সঠিকভাবে কাজ করার জন্য আপনার ডিভাইসের অতিরিক্ত সেটআপ প্রয়োজন। আপনি কি এগিয়ে যেতে এবং রিবুট করতে চান?</string>\n    <string name=\"setup_msg\">চলমান পরিবেশ সেটআপ…</string>\n    <string name=\"unsupport_magisk_title\">অসমর্থিত ম্যাজিস্ক সংস্করণ</string>\n    <string name=\"unsupport_magisk_msg\">অ্যাপটির এই সংস্করণটি %1$s-এর চেয়ে কম ম্যাগিস্ক সংস্করণগুলিকে সমর্থন করে না৷\\n\\nঅ্যাপটি এমন আচরণ করবে যেন কোনও ম্যাজিস্ক ইনস্টল করা নেই, দয়া করে যত তাড়াতাড়ি সম্ভব ম্যাজিস্ক আপগ্রেড করুন।</string>\n    <string name=\"unsupport_general_title\">অস্বাভাবিক অবস্থা</string>\n    <string name=\"unsupport_system_app_msg\">এই অ্যাপটিকে একটি সিস্টেম অ্যাপ হিসেবে চালানো সমর্থিত নয়। অনুগ্রহ করে অ্যাপটিকে একটি ব্যবহারকারী অ্যাপে ফিরিয়ে দিন।</string>\n    <string name=\"unsupport_other_su_msg\">ম্যাজিস্ক থেকে নয় একটি \\\"su\\\" বাইনারি সনাক্ত করা হয়েছে। অনুগ্রহ করে কোনো প্রতিযোগী রুট সমাধান সরান এবং/অথবা ম্যাজিস্ক পুনরায় ইনস্টল করুন।</string>\n    <string name=\"unsupport_external_storage_msg\">ম্যাজিস্ক বহিরাগত স্টোরেজ ইনস্টল করা হয়। অনুগ্রহ করে অ্যাপটিকে অভ্যন্তরীণ সঞ্চয়স্থানে সরান৷</string>\n    <string name=\"unsupport_nonroot_stub_msg\">লুকানো ম্যাজিস্ক অ্যাপটি কাজ চালিয়ে যেতে পারে না কারণ রুট হারিয়ে গেছে। অনুগ্রহ করে আসল APK পুনরুদ্ধার করুন।</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">এই কার্যকারিতা সক্ষম করতে স্টোরেজ অনুমতি দিন</string>\n    <string name=\"post_notifications_denied\">নোটিফিকেশন এর অনোমতি দিন</string>\n    <string name=\"install_unknown_denied\">এই কার্যকারিতা সক্ষম করতে \"অজানা অ্যাপগুলি ইনস্টল করুন\" এর অনুমতি দিন</string>\n    <string name=\"add_shortcut_title\">হোম স্ক্রিনে শর্টকাট যোগ করুন</string>\n    <string name=\"add_shortcut_msg\">এই অ্যাপটি লুকানোর পরে, এর নাম এবং আইকন চিনতে অসুবিধা হতে পারে। আপনি কি হোম স্ক্রিনে একটি সুন্দর শর্টকাট যোগ করতে চান?</string>\n    <string name=\"app_not_found\">এই ক্রিয়াটি পরিচালনা করার জন্য কোনো অ্যাপ পাওয়া যায়নি</string>\n    <string name=\"reboot_apply_change\">পরিবর্তনগুলি প্রয়োগ করতে রিবুট করুন</string>\n    <string name=\"restore_app_confirmation\">এটি লুকানো অ্যাপটিকে মূল অ্যাপে ফিরিয়ে আনবে। আপনি কি সত্যিই এটি করতে চান?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ca/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Mòduls</string>\n    <string name=\"superuser\">Superusuari</string>\n    <string name=\"logs\">Registre</string>\n    <string name=\"settings\">Configuració</string>\n    <string name=\"install\">Instal·lar</string>\n    <string name=\"section_home\">Inici</string>\n    <string name=\"section_theme\">Temes</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Connexió no disponible</string>\n    <string name=\"app_changelog\">Registre de canvis</string>\n    <string name=\"loading\">Carregant…</string>\n    <string name=\"update\">Actualització</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Amagar</string>\n    <string name=\"home_package\">Paquet</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">Descarregui Magisk NOMÉS des de la pàgina oficial de GitHub. Fitxers d\\'altres fonts desconegudes poden ser maliciosos!</string>\n    <string name=\"home_support_title\">Doni suport</string>\n    <string name=\"home_item_source\">Codi font</string>\n    <string name=\"home_support_content\">Magisk és, i sempre serà, gratis i codi lliure. De totes maneres, pot mostrar el seu interès fent una petita donació.</string>\n    <string name=\"home_installed_version\">Instal·lat</string>\n    <string name=\"home_latest_version\">Última</string>\n    <string name=\"invalid_update_channel\">Canal d\\'actualització invàlid</string>\n    <string name=\"uninstall_magisk_title\">Desinstal·lar Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Tots els mòduls seran desactivats i eliminats! L\\'accés d\\'arrel s\\'eliminarà i, possiblement, xifrarà totes les dades (si no estan ja xifrades).</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Mantenir el xifrat forçat</string>\n    <string name=\"keep_dm_verity\">Mantenir AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mode de Recuperació</string>\n    <string name=\"install_options_title\">Opcions</string>\n    <string name=\"install_method_title\">Mètode</string>\n    <string name=\"install_next\">Següent</string>\n    <string name=\"install_start\">Endavant</string>\n    <string name=\"manager_download_install\">Premi per baixar i instal·lar</string>\n    <string name=\"direct_install\">Instal·lació directa (Recomanat)</string>\n    <string name=\"install_inactive_slot\">Instal·la a la ranura inactiva (Després d\\'una OTA)</string>\n    <string name=\"install_inactive_slot_msg\">El teu dispositiu serà FORÇAT a arrancar en l\\'actual ranura inactiva després del reinici!\\nUtilitza aquesta opció NOMÉS quan l\\'OTA s\\'hagi fet.\\nContinuar?</string>\n    <string name=\"setup_title\">Instal·lació addicional</string>\n    <string name=\"select_patch_file\">Selecciona i arranja un arxiu</string>\n    <string name=\"patch_file_msg\">Selecciona una imatge crua (*.img) o un ODIN tarfile (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Reinici en 5 segons…</string>\n    <string name=\"flash_screen_title\">Instal·lació</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Petició de superusuari</string>\n    <string name=\"touch_filtered_warning\">Com que una aplicació està ofuscant la petició de superusuari, Magisk no pot verificar la seva resposta</string>\n    <string name=\"deny\">Denegar</string>\n    <string name=\"prompt\">Preguntar</string>\n    <string name=\"grant\">Permetre</string>\n    <string name=\"su_warning\">Permet accés total al seu dispositiu.\\nDenegui si no n\\'està segur!</string>\n    <string name=\"forever\">Sempre</string>\n    <string name=\"once\">Un cop </string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">Permesos els drets de superusuari de %1$s</string>\n    <string name=\"su_deny_toast\">Denegats els drets de superusuari de %1$s</string>\n    <string name=\"su_snack_grant\">Drets de superusuari de %1$s permesos</string>\n    <string name=\"su_snack_deny\">Drets de superusuari de %1$s denegats</string>\n    <string name=\"su_snack_notif_on\">Notificacions de %1$s habilitades</string>\n    <string name=\"su_snack_notif_off\">Notificacions de %1$s deshabilitades</string>\n    <string name=\"su_snack_log_on\">Registres de %1$s habilitats</string>\n    <string name=\"su_snack_log_off\">Registres de %1$s deshabilitats</string>\n    <string name=\"su_revoke_title\">Revocar?</string>\n    <string name=\"su_revoke_msg\">Confirmi per revocar drets de %1$s</string>\n    <string name=\"toast\">Avís</string>\n    <string name=\"none\">Cap</string>\n\n    <string name=\"superuser_toggle_notification\">Notificacions</string>\n    <string name=\"superuser_toggle_revoke\">Revocar</string>\n    <string name=\"superuser_policy_none\">Cap aplicació ha demanat permisos de superusuari.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">No hi ha cap registre. Provi d\\'utilitzar aplicacions que requereixen permisos de superusuari.</string>\n    <string name=\"log_data_magisk_none\">Els registres de Magisk estan buits. Això és estrany.</string>\n    <string name=\"menuSaveLog\">Desar registre</string>\n    <string name=\"menuClearLog\">Netejar registre ara</string>\n    <string name=\"logs_cleared\">Registre netejat correctament.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID de l\\'objectiu: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Mostra aplicacions del sistema</string>\n    <string name=\"show_os_app\">Mostra aplicacions del SO</string>\n    <string name=\"hide_filter_hint\">Filtra per nom</string>\n    <string name=\"hide_search\">Cerca</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(No hi ha informació)</string>\n    <string name=\"reboot_userspace\">Reinici suau</string>\n    <string name=\"reboot_recovery\">Reiniciar en Mode Recuperació</string>\n    <string name=\"reboot_bootloader\">Reiniciar en Mode Bootloader</string>\n    <string name=\"reboot_download\">Reiniciar en Mode Download</string>\n    <string name=\"reboot_edl\">Reiniciar a EDL</string>\n    <string name=\"module_version_author\">%1$s per %2$s</string>\n    <string name=\"module_state_remove\">Eliminar</string>\n    <string name=\"module_state_restore\">Recuperar</string>\n    <string name=\"module_action_install_external\">Instal·lar des de l\\'emmagatzematge</string>\n    <string name=\"update_available\">Actualització Disponible</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mode del tema</string>\n    <string name=\"settings_dark_mode_message\">Seleccioni el mode que més s\\'adeqüi al seu estil!</string>\n    <string name=\"settings_dark_mode_light\">Sempre clar</string>\n    <string name=\"settings_dark_mode_system\">Seguir al sistema</string>\n    <string name=\"settings_dark_mode_dark\">Sempre fosc</string>\n    <string name=\"settings_download_path_title\">Directori de baixades</string>\n    <string name=\"settings_download_path_message\">Els arxius es desaran a %1$s</string>\n    <string name=\"settings_hide_app_title\">Amagar Magisk Manager</string>\n    <string name=\"settings_hide_app_summary\">Torna a empaquetar Magisk Manager amb un nom de paquet a l\\'atzar</string>\n    <string name=\"settings_restore_app_title\">Restaurar Magisk Manager</string>\n    <string name=\"settings_restore_app_summary\">Restaura Magisk Manager amb el nom de paquet original</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"system_default\">(Idioma del sistema)</string>\n    <string name=\"settings_check_update_title\">Comprovar Actualitzacions</string>\n    <string name=\"settings_check_update_summary\">Comprovar periòdicament en segon pla si existeixen actualitzacions</string>\n    <string name=\"settings_update_channel_title\">Canal d\\'Actualitzacions</string>\n    <string name=\"settings_update_stable\">Estable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Canal personalitzat</string>\n    <string name=\"settings_update_custom_msg\">Inserta un URL personalitzada</string>\n    <string name=\"settings_hosts_title\">Systemless Hosts</string>\n    <string name=\"settings_hosts_summary\">Suport per aplicacions tipus Adblock fora de la partició del sistema</string>\n    <string name=\"settings_hosts_toast\">Agregat el mòdul Systemless Hosts</string>\n    <string name=\"settings_app_name_hint\">Nou nom</string>\n    <string name=\"settings_app_name_helper\">Es refarà l\\'aplicació amb aquest nom</string>\n    <string name=\"settings_app_name_error\">Format invàlid</string>\n    <string name=\"settings_su_app_adb\">Aplicacions i ADB</string>\n    <string name=\"settings_su_app\">Només aplicacions</string>\n    <string name=\"settings_su_adb\">Només ADB</string>\n    <string name=\"settings_su_disable\">Deshabilitat</string>\n    <string name=\"settings_su_request_10\">10 segons</string>\n    <string name=\"settings_su_request_15\">15 segons</string>\n    <string name=\"settings_su_request_20\">20 segons</string>\n    <string name=\"settings_su_request_30\">30 segons</string>\n    <string name=\"settings_su_request_45\">45 segons</string>\n    <string name=\"settings_su_request_60\">60 segons</string>\n    <string name=\"superuser_access\">Accés de superusuari</string>\n    <string name=\"auto_response\">Resposta automàtica</string>\n    <string name=\"request_timeout\">Temps de petició</string>\n    <string name=\"superuser_notification\">Notificació de superusuari</string>\n    <string name=\"settings_su_reauth_title\">Demanar després d\\'una actualització</string>\n    <string name=\"settings_su_reauth_summary\">Demanar permisos de superusuari novament si una aplicació és actualitzada o reinstal·lada</string>\n    <string name=\"settings_su_tapjack_title\">Activa la protecció contra \\'TapJacking\\'</string>\n    <string name=\"settings_su_tapjack_summary\">El diàleg per donar permisos de superusuari no respondrà mentre estigui ofuscat per alguna altra finestra o superposició</string>\n    <string name=\"settings_customization\">Personalització</string>\n    <string name=\"setting_add_shortcut_summary\">Afegeix una bonica drecera a la pantalla d\\'inici en cas que el nom i la icona siguin difícils de reconèixer després d\\'amagar l\\'aplicació.</string>\n    <string name=\"settings_doh_title\">DNS sobre HTTPS</string>\n    <string name=\"settings_doh_description\">Solució per enverinament de DNS en algunes nacions</string>\n\n    <string name=\"multiuser_mode\">Mode Multiusuari</string>\n    <string name=\"settings_owner_only\">Només Administrador del Dispositiu</string>\n    <string name=\"settings_owner_manage\">Administrador del Dispositiu</string>\n    <string name=\"settings_user_independent\">Usuari Independent</string>\n    <string name=\"owner_only_summary\">Només l\\'administrador té accés d\\'arrel</string>\n    <string name=\"owner_manage_summary\">Només l\\'administrador pot supervisar l\\'accés d\\'arrel i rebre sol·licituds d\\'altres usuaris</string>\n    <string name=\"user_independent_summary\">Tots els usuaris tenen separades les seves pròpies regles d\\'arrel</string>\n\n    <string name=\"mount_namespace_mode\">Muntar Namespace </string>\n    <string name=\"settings_ns_global\">Namespace Global</string>\n    <string name=\"settings_ns_requester\">Heretar Namespace</string>\n    <string name=\"settings_ns_isolate\">Aïllar Namespace</string>\n    <string name=\"global_summary\">Totes les sessions d\\'arrel utilitzen el suport Namespace Global</string>\n    <string name=\"requester_summary\">Les sessions d\\'arrel heretaran les peticions Namespace</string>\n    <string name=\"isolate_summary\">Totes les sessions d\\'arrel tindran la seva pròpia Namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Actualització de Magisk</string>\n    <string name=\"progress_channel\">Notificacions de progrés</string>\n    <string name=\"download_complete\">Baixada completada</string>\n    <string name=\"download_file_error\">Error en baixar l\\'arxiu</string>\n    <string name=\"magisk_update_title\">Actualització de Magisk disponible!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">No</string>\n    <string name=\"repo_install_title\">Instal·lar %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Baixar</string>\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"release_notes\">Notes de llançament</string>\n    <string name=\"flashing\">Arranjament…</string>\n    <string name=\"done\">Fet!</string>\n    <string name=\"failure\">Fallit</string>\n    <string name=\"hide_app_title\">Amagant Magisk Manager…</string>\n    <string name=\"open_link_failed_toast\">No s\\'ha trobat una aplicació per obrir l\\'enllaç</string>\n    <string name=\"complete_uninstall\">Desinstal·lació completa</string>\n    <string name=\"restore_img\">Restaura imatges</string>\n    <string name=\"restore_img_msg\">Restaurant…</string>\n    <string name=\"restore_done\">Restauració feta!</string>\n    <string name=\"restore_fail\">La còpia de seguretat de Estock no existeix!</string>\n    <string name=\"setup_fail\">Instal·lació fallida</string>\n    <string name=\"env_fix_title\">Es requereix instal·lació addicional</string>\n    <string name=\"env_fix_msg\">El teu dispositiu necessita instal·lació addicional per Magisk per funcionar correctament. Es baixarà el ZIP d\\'instal·lació de Magisk, vol procedir a la instal·lació ara?</string>\n    <string name=\"setup_msg\">S\\'està executant la configuració de l\\'entorn…</string>\n    <string name=\"unsupport_magisk_title\">Versió de Magisk incompatible</string>\n    <string name=\"unsupport_magisk_msg\">Aquesta versió de Magisk Manager no suporta versions de Magisk més petites que la %1$s.\\n\\nL\\'aplicació es comportarà com si Magisk no estigués instal·lat, si us plau actualitzi Magisk com més aviat millor.</string>\n    <string name=\"external_rw_permission_denied\">Ha de donar permís d\\'emmagatzematge per activar aquesta funcionalitat</string>\n    <string name=\"add_shortcut_title\">Afegeix una drecera a la pantalla d\\'inici</string>\n    <string name=\"add_shortcut_msg\">Després d\\'amagar Magisk Manager, el seu nom i icona poden ser difícils de reconèixer. Vols afegir una bonica drecera a la teva pantalla d\\'inici?</string>\n    <string name=\"app_not_found\">No s\\'ha trobat una aplicació per emprar aquesta acció</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-cs/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduly</string>\n    <string name=\"superuser\">SuperUser</string>\n    <string name=\"logs\">Protokol</string>\n    <string name=\"settings\">Nastavení</string>\n    <string name=\"install\">Instalovat</string>\n    <string name=\"section_home\">Domů</string>\n    <string name=\"section_theme\">Témata</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Žádné připojení</string>\n    <string name=\"app_changelog\">Seznam změn</string>\n    <string name=\"loading\">Načítání…</string>\n    <string name=\"update\">Aktualizovat</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Skrýt</string>\n    <string name=\"home_package\">Balíček</string>\n    <string name=\"home_app_title\">Aplikace</string>\n\n    <string name=\"home_notice_content\">Stahujte Magisk POUZE z oficiální stránky GitHub. Soubory z neznámých zdrojů mohou být škodlivé!</string>\n    <string name=\"home_support_title\">Podpořte nás</string>\n    <string name=\"home_follow_title\">Sledujte nás</string>\n    <string name=\"home_item_source\">Zdroj</string>\n    <string name=\"home_support_content\">Magisk je a vždy bude svobodný s otevřeným kódem. Můžete nám však zaslat malý dar jako poděkování.</string>\n    <string name=\"home_installed_version\">Nainstalováno</string>\n    <string name=\"home_latest_version\">Poslední</string>\n    <string name=\"invalid_update_channel\">Neplatný kanál aktualizace</string>\n    <string name=\"uninstall_magisk_title\">Odinstalovat Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Všechny moduly budou zakázány/odstraněny!\\nROOT bude odstraněn!\\nPokud jsou data dešifrována, můžou být zašifrována!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Ponechat Force Encryption</string>\n    <string name=\"keep_dm_verity\">Ponechat AVB 2.0/DM-Verity</string>\n    <string name=\"recovery_mode\">Režim Recovery</string>\n    <string name=\"install_options_title\">Možnosti</string>\n    <string name=\"install_method_title\">Způsob</string>\n    <string name=\"install_next\">Další</string>\n    <string name=\"install_start\">Spustit</string>\n    <string name=\"manager_download_install\">Stiskněte pro stažení a instalaci</string>\n    <string name=\"direct_install\">Přímá instalace (doporučeno)</string>\n    <string name=\"install_inactive_slot\">Instalace do druhého slotu (po OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Vaše zařízení bude po restartu VYNUCENĚ spuštěno do aktuálního neaktivního slotu!\\nTuto možnost použijte pouze po dokončení OTA.\\nChcete pokračovat?</string>\n    <string name=\"setup_title\">Další nastavení</string>\n    <string name=\"select_patch_file\">Vybrat a opravit soubor</string>\n    <string name=\"patch_file_msg\">Vyberte soubor RAW (*.img) nebo soubor TAR pro ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Restartování za 5 sekund…</string>\n    <string name=\"flash_screen_title\">Instalace</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Požadavek SuperUser</string>\n    <string name=\"touch_filtered_warning\">Magisk nemůže ověřit Vaši odpověď, protože aplikace zavírá požadavek SuperUser</string>\n    <string name=\"deny\">Zakázat</string>\n    <string name=\"prompt\">Zeptat se</string>\n    <string name=\"grant\">Povolit</string>\n    <string name=\"su_warning\">Povolí plný přístup k Vašemu zařízení.\\nZakažte, pokud si nejste jisti!</string>\n    <string name=\"forever\">Trvale</string>\n    <string name=\"once\">Jednou</string>\n    <string name=\"tenmin\">10 minut</string>\n    <string name=\"twentymin\">20 minut</string>\n    <string name=\"thirtymin\">30 minut</string>\n    <string name=\"sixtymin\">60 minut</string>\n    <string name=\"su_allow_toast\">Pro %1$s bylo oprávnění SuperUser povoleno</string>\n    <string name=\"su_deny_toast\">Pro %1$s bylo oprávnění SuperUser zakázáno</string>\n    <string name=\"su_snack_grant\">SuperUser oprávnění pro %1$s je povoleno</string>\n    <string name=\"su_snack_deny\">SuperUser oprávnění pro %1$s je zakázáno</string>\n    <string name=\"su_snack_notif_on\">Oznámení pro %1$s jsou povolena</string>\n    <string name=\"su_snack_notif_off\">Oznámení pro %1$s jsou zakázána</string>\n    <string name=\"su_snack_log_on\">Protokolování %1$s je povoleno</string>\n    <string name=\"su_snack_log_off\">Protokolování %1$s je zakázáno</string>\n    <string name=\"su_revoke_title\">Smazat?</string>\n    <string name=\"su_revoke_msg\">Potvrzením odeberete %1$s oprávnění SuperUser</string>\n    <string name=\"toast\">Text</string>\n    <string name=\"none\">Žádné</string>\n\n    <string name=\"superuser_toggle_notification\">Oznámení</string>\n    <string name=\"superuser_toggle_revoke\">Smazat</string>\n    <string name=\"superuser_policy_none\">Žádná aplikace nepožádala o oprávnění SuperUser.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nemáte žádné protokoly. Vyzkoušejte nějakou aplikaci vyžadující oprávnění SuperUser</string>\n    <string name=\"log_data_magisk_none\">Protokoly Magisk jsou prázdné. To je zvláštní</string>\n    <string name=\"menuSaveLog\">Uložit protokol</string>\n    <string name=\"menuClearLog\">Smazat protokol</string>\n    <string name=\"logs_cleared\">Protokol byl smazán</string>\n    <string name=\"pid\">PID:%1$d</string>\n    <string name=\"target_uid\">Cílové UID: %1$d</string>\n    <string name=\"target_pid\">Připojit PID cílového ns: %s</string>\n    <string name=\"selinux_context\">SELinux kontext: %s</string>\n    <string name=\"supp_group\">Sekundární skupina: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Zobrazit systémové aplikace</string>\n    <string name=\"show_os_app\">Zobrazit OS aplikace</string>\n    <string name=\"hide_filter_hint\">Filtrovat podle názvu</string>\n    <string name=\"hide_search\">Vyhledávání</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(žádné informace)</string>\n    <string name=\"reboot_userspace\">Restartovat do UserSpace</string>\n    <string name=\"reboot_recovery\">Restartovat do Recovery</string>\n    <string name=\"reboot_bootloader\">Restartovat do Bootloader</string>\n    <string name=\"reboot_download\">Restartovat do Download</string>\n    <string name=\"reboot_edl\">Restartovat do EDL</string>\n    <string name=\"module_version_author\">%1$s od %2$s</string>\n    <string name=\"module_state_remove\">Odstranit</string>\n    <string name=\"module_state_restore\">Obnovit</string>\n    <string name=\"module_action_install_external\">Instalace z úložiště</string>\n    <string name=\"update_available\">Dostupná aktualizace</string>\n    <string name=\"suspend_text_riru\">Modul byl pozastaven, protože je povolen %1$s</string>\n    <string name=\"suspend_text_zygisk\">Modul byl pozastaven, protože %1$s není povoleno</string>\n    <string name=\"zygisk_module_unloaded\">Modul Zygisk nebyl načten z důvodu nekompatibility</string>\n    <string name=\"module_empty\">Není nainstalován žádný modul</string>\n    <string name=\"confirm_install\">Instalovat modul %1$s?</string>\n    <string name=\"confirm_install_title\">Potvrzení instalace</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Režim motivu</string>\n    <string name=\"settings_dark_mode_message\">Vyberte režim, který nejlépe vyhovuje Vašemu stylu!</string>\n    <string name=\"settings_dark_mode_light\">Světlý</string>\n    <string name=\"settings_dark_mode_system\">Podle systému</string>\n    <string name=\"settings_dark_mode_dark\">Tmavý</string>\n    <string name=\"settings_download_path_title\">Složka pro stahování</string>\n    <string name=\"settings_download_path_message\">Soubory budou uloženy do %1$s.</string>\n    <string name=\"settings_hide_app_title\">Skrýt aplikaci Magisk</string>\n    <string name=\"settings_hide_app_summary\">Skryje aplikaci náhodným ID balíčku a vlastním názvem aplikace</string>\n    <string name=\"settings_restore_app_title\">Obnovit aplikaci Magisk</string>\n    <string name=\"settings_restore_app_summary\">Obnoví aplikaci zpět do původního APK</string>\n    <string name=\"language\">Jazyk</string>\n    <string name=\"system_default\">(výchozí podle systému)</string>\n    <string name=\"settings_check_update_title\">Zkontrolovat aktualizace</string>\n    <string name=\"settings_check_update_summary\">Povolí pravidelnou kontrolu aktualizace na pozadí</string>\n    <string name=\"settings_update_channel_title\">Kanál aktualizace</string>\n    <string name=\"settings_update_stable\">Stabilní</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Vlastní</string>\n    <string name=\"settings_update_custom_msg\">Vložte vlastní URL</string>\n    <string name=\"settings_zygisk_summary\">Spustí části Magisku v démonu Zygote</string>\n    <string name=\"settings_denylist_title\">Vynutit DenyList</string>\n    <string name=\"settings_denylist_summary\">Na procesy v Denylistu nebudou aplikovány žádné změny, nebo modifikace způsobené Magiskem</string>\n    <string name=\"settings_denylist_config_title\">Nastavit DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Vyberte procesy, které mají být zahrnuty v DenyListu</string>\n    <string name=\"settings_hosts_title\">Nesystémoví hostitelé</string>\n    <string name=\"settings_hosts_summary\">Přidá modul pro podporu nesystémových hostitelů v aplikaci AdBlock</string>\n    <string name=\"settings_hosts_toast\">Modul nesystémových hostitelů přidán</string>\n    <string name=\"settings_app_name_hint\">Nový název</string>\n    <string name=\"settings_app_name_helper\">Název aplikace bude nahrazen tímto názvem</string>\n    <string name=\"settings_app_name_error\">Neplatný formát</string>\n    <string name=\"settings_su_app_adb\">Aplikace a ADB</string>\n    <string name=\"settings_su_app\">Pouze aplikace</string>\n    <string name=\"settings_su_adb\">Pouze ADB</string>\n    <string name=\"settings_su_disable\">Zakázat</string>\n    <string name=\"settings_su_request_10\">10 sekund</string>\n    <string name=\"settings_su_request_15\">15 sekund</string>\n    <string name=\"settings_su_request_20\">20 sekund</string>\n    <string name=\"settings_su_request_30\">30 sekund</string>\n    <string name=\"settings_su_request_45\">45 sekund</string>\n    <string name=\"settings_su_request_60\">60 sekund</string>\n    <string name=\"superuser_access\">Přístup SuperUser</string>\n    <string name=\"auto_response\">Automatická odezva</string>\n    <string name=\"request_timeout\">Časový limit požadavku</string>\n    <string name=\"superuser_notification\">Oznámení SuperUser</string>\n    <string name=\"settings_su_reauth_title\">Opětovné ověření po aktualizaci</string>\n    <string name=\"settings_su_reauth_summary\">Opětovné ověření oprávnění SuperUser po aktualizaci aplikace.</string>\n    <string name=\"settings_su_tapjack_title\">Povolit ochranu před TapJack</string>\n    <string name=\"settings_su_tapjack_summary\">Okno dialogu SuperUser nebude reagovat na kliknutí v případě, že je zavřené nebo překryté jiným oknem</string>\n    <string name=\"settings_customization\">Přizpůsobit</string>\n    <string name=\"setting_add_shortcut_summary\">Přidá odkaz na domovskou obrazovku v případě, že se po skrytí aplikace její název a ikona těžko rozpoznávají.</string>\n    <string name=\"settings_doh_title\">DNS přes HTTPS</string>\n    <string name=\"settings_doh_description\">Obejde DNS v některých zemích</string>\n\n    <string name=\"multiuser_mode\">Režim více uživatelů</string>\n    <string name=\"settings_owner_only\">Vlastník zařízení</string>\n    <string name=\"settings_owner_manage\">Správce zařízení</string>\n    <string name=\"settings_user_independent\">Všichni uživatelé</string>\n    <string name=\"owner_only_summary\">Pouze vlastník má přístup ROOT</string>\n    <string name=\"owner_manage_summary\">Přístup ROOT má správce zařízení, který přijímá požadavky k přístupu</string>\n    <string name=\"user_independent_summary\">Každý uživatel má svá vlastní pravidla přístupu ROOT</string>\n\n    <string name=\"mount_namespace_mode\">Režim připojení jmenného prostoru</string>\n    <string name=\"settings_ns_global\">Globální jmenný prostor</string>\n    <string name=\"settings_ns_requester\">Odvozený jmenný prostor</string>\n    <string name=\"settings_ns_isolate\">Izolovaný jmenný prostor</string>\n    <string name=\"global_summary\">Všechny relace ROOT používají globální jmenný prostor.</string>\n    <string name=\"requester_summary\">Relace ROOT je odvozena od jmenného prostoru žadatele.</string>\n    <string name=\"isolate_summary\">Každá relace ROOT má svůj vlastní izolovaný jmenný prostor.</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Aktualizace Magisk</string>\n    <string name=\"progress_channel\">Oznámení o průběhu</string>\n    <string name=\"updated_channel\">Aktualizace dokončena</string>\n    <string name=\"download_complete\">Stahování dokončeno</string>\n    <string name=\"download_file_error\">Chyba při stahování souboru</string>\n    <string name=\"magisk_update_title\">Aktualizace Magisk je dostupná!</string>\n    <string name=\"updated_title\">Magisk aktualizován</string>\n    <string name=\"updated_text\">Klepnutím otevřete aplikaci</string>\n\n<!--Toasts, Dialogs-->\n    <string name=\"yes\">Ano</string>\n    <string name=\"no\">Ne</string>\n    <string name=\"repo_install_title\">Instalovat %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Stáhnout</string>\n    <string name=\"reboot\">Restartovat</string>\n    <string name=\"release_notes\">Seznam změn</string>\n    <string name=\"flashing\">Flashuji…</string>\n    <string name=\"done\">Hotovo!</string>\n    <string name=\"failure\">Nepodařilo se!</string>\n    <string name=\"hide_app_title\">Skrývám aplikaci Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nebyla nalezena žádná aplikace pro otevření odkazu</string>\n    <string name=\"complete_uninstall\">Kompletní odinstalace</string>\n    <string name=\"restore_img\">Obnovit obrazy</string>\n    <string name=\"restore_img_msg\">Obnovování…</string>\n    <string name=\"restore_done\">Proces obnovování dokončen!</string>\n    <string name=\"restore_fail\">Původní záloha neexistuje!</string>\n    <string name=\"setup_fail\">Nastavení selhalo</string>\n    <string name=\"env_fix_title\">Vyžadováno dodatečné nastavení</string>\n    <string name=\"env_fix_msg\">Aby mohl Magisk správně fungovat, je třeba zařízení dodatečně nastavit. Chcete pokračovat a restartovat zařízení?</string>\n    <string name=\"env_full_fix_msg\">Aby vaše zařízení správně fungovalo, potřebuje se Magisk znovu flashnout. Nainstalujte znovu Magisk v aplikaci, režim Recovery nemůže získat správné informace o zařízení.</string>\n    <string name=\"setup_msg\">Probíhá nastavení prostředí…</string>\n    <string name=\"unsupport_magisk_title\">Nepodporovaná verze Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Tato verze aplikace nepodporuje Magisk ve verzi nižší než %1$s.\\n\\nAplikace se bude chovat, jako by žádný Magisk nebyl nainstalován. Aktualizujte prosím Magisk co nejdříve.</string>\n    <string name=\"unsupport_general_title\">Abnormální stav</string>\n    <string name=\"unsupport_system_app_msg\">Spuštění této aplikace jako systémové, není podporováno. Změňte prosím aplikaci zpět na uživatelskou.</string>\n    <string name=\"unsupport_other_su_msg\">Byl detekován binární soubor \\\"su\\\", který nepochází z Magisk. Odeberte jakékoli konkurenční ROOT řešení a/nebo přeinstalujte Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk je nainstalován na externím úložišti. Přesuňte prosím aplikaci do interního úložiště.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Skrytá aplikace Magisk nemůže dále fungovat, protože byl ztracen ROOT. Obnovte prosím původní soubor APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Pro povolení této funkce, povolte oprávnění pro přístup k úložišti</string>\n    <string name=\"post_notifications_denied\">Pro povolení této funkce, povolte oznámení</string>\n    <string name=\"install_unknown_denied\">Pro povolení této funkce, povolte \"instalovat aplikace z neznámých zdrojů\"</string>\n    <string name=\"add_shortcut_title\">Přidat zástupce na domovskou obrazovku</string>\n    <string name=\"add_shortcut_msg\">Po skrytí této aplikace může být obtížné rozpoznat její název a ikonu. Chcete přidat na domovskou obrazovku hezkého zástupce?</string>\n    <string name=\"app_not_found\">Nebyla nalezena žádná aplikace, která by tuto akci provedla</string>\n    <string name=\"reboot_apply_change\">Pro použití změn restartujte zařízení</string>\n    <string name=\"restore_app_confirmation\">Tímto skrytou aplikaci obnovíte zpět na původní aplikaci. Opravdu to chcete udělat?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-de/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Module</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Protokolle</string>\n    <string name=\"settings\">Einstellungen</string>\n    <string name=\"install\">Installieren</string>\n    <string name=\"section_home\">Startseite</string>\n    <string name=\"section_theme\">Themen</string>\n    <string name=\"denylist\">Ausnahmeliste</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Keine Verbindung verfügbar</string>\n    <string name=\"app_changelog\">Änderungen</string>\n    <string name=\"loading\">Laden …</string>\n    <string name=\"update\">Aktualisieren</string>\n    <string name=\"not_available\">Nicht verfügbar</string>\n    <string name=\"hide\">Verstecken</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">Laden Sie Magisk NUR von der offiziellen Github Seite herunter. Dateien aus unbekannten Quellen können bösartig sein!</string>\n    <string name=\"home_support_title\">Unterstütze uns</string>\n    <string name=\"home_follow_title\">Folge uns</string>\n    <string name=\"home_item_source\">Quelle</string>\n    <string name=\"home_support_content\">Magisk ist und wird immer frei und quelloffen sein. Du kannst uns jedoch jederzeit mit einer kleinen Spende unterstützen.</string>\n    <string name=\"home_installed_version\">Installiert</string>\n    <string name=\"home_latest_version\">Neueste</string>\n    <string name=\"invalid_update_channel\">Ungültiger Update-Kanal</string>\n    <string name=\"uninstall_magisk_title\">Magisk deinstallieren</string>\n    <string name=\"uninstall_magisk_msg\">Alle Module werden deaktiviert/entfernt!\\nRoot wird entfernt!\\nDeine Daten werden potentiell verschlüsselt, wenn sie nicht bereits verschlüsselt sind!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Erzwungene Verschlüsselung beibehalten</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity beibehalten</string>\n    <string name=\"recovery_mode\">Recovery Modus</string>\n    <string name=\"install_options_title\">Optionen</string>\n    <string name=\"install_method_title\">Methode</string>\n    <string name=\"install_next\">Nächster Schritt</string>\n    <string name=\"install_start\">Los geht\\'s</string>\n    <string name=\"manager_download_install\">Tippen zum Herunterladen und Installieren</string>\n    <string name=\"direct_install\">Direkte Installation (empfohlen)</string>\n    <string name=\"install_inactive_slot\">In inaktiven Slot installieren (nach OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Dein Gerät wird gezwungen, nach einem Neustart in den inaktiven Slot zu booten! Diese Option nur verwenden, wenn OTA abgeschlossen ist.\\nWeiter?</string>\n    <string name=\"setup_title\">Zusätzliche Einstellungen</string>\n    <string name=\"select_patch_file\">Eine Datei auswählen und patchen</string>\n    <string name=\"patch_file_msg\">Ein Raw Image (*.img), eine ODIN Tar-Datei (*.tar) oder eine Payload.bin (*.bin) auswählen</string>\n    <string name=\"reboot_delay_toast\">Neustart in 5 Sekunden …</string>\n    <string name=\"flash_screen_title\">Installieren</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser-Anfrage</string>\n    <string name=\"touch_filtered_warning\">Weil eine App eine Superuser-Anfrage verdeckt, kann Magisk deine Antwort nicht verifizieren</string>\n    <string name=\"deny\">Verweigern</string>\n    <string name=\"prompt\">Fragen</string>\n    <string name=\"grant\">Gewähren</string>\n    <string name=\"su_warning\">Zugriff auf dein Gerät gewähren. Verweigere den Zugriff, wenn du nicht sicher bist!</string>\n    <string name=\"forever\">Für immer</string>\n    <string name=\"once\">Einmalig</string>\n    <string name=\"tenmin\">10 Minuten</string>\n    <string name=\"twentymin\">20 Minuten</string>\n    <string name=\"thirtymin\">30 Minuten</string>\n    <string name=\"sixtymin\">60 Minuten</string>\n    <string name=\"su_allow_toast\">%1$s wurden Superuser-Rechte gewährt</string>\n    <string name=\"su_deny_toast\">%1$s wurden Superuser-Rechte verweigert</string>\n    <string name=\"su_snack_grant\">Superuser-Rechte für %1$s gewährt</string>\n    <string name=\"su_snack_deny\">Superuser-Rechte für %1$s verweigert</string>\n    <string name=\"su_snack_notif_on\">Benachrichtigungen für %1$s sind an</string>\n    <string name=\"su_snack_notif_off\">Benachrichtigungen für %1$s sind aus</string>\n    <string name=\"su_snack_log_on\">Protokollierung für %1$s ist an</string>\n    <string name=\"su_snack_log_off\">Protokollierung für %1$s ist aus</string>\n    <string name=\"su_revoke_title\">Widerrufen?</string>\n    <string name=\"su_revoke_msg\">Bestätigen, dass %1$s-Rechte widerufen werden?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Keine</string>\n\n    <string name=\"superuser_toggle_notification\">Benachrichtigungen</string>\n    <string name=\"superuser_toggle_revoke\">Widerruf</string>\n    <string name=\"superuser_policy_none\">Bisher hat noch keine App um die Erlaubnis des Superusers gebeten.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Du bist protokollfrei, versuche deine SU-fähigen Apps mehr zu nutzen.</string>\n    <string name=\"log_data_magisk_none\">Magisk-Protokolle sind leer, das ist seltsam.</string>\n    <string name=\"menuSaveLog\">Protokoll speichern</string>\n    <string name=\"menuClearLog\">Protokoll jetzt löschen</string>\n    <string name=\"logs_cleared\">Protokoll erfolgreich gelöscht.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Ziel-UID: %1$d</string>\n    <string name=\"target_pid\">NS Target PID einbinden: %s</string>\n    <string name=\"selinux_context\">SELinux Kontext: %s</string>\n    <string name=\"supp_group\">Ergänzende Gruppe: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">System-Apps anzeigen</string>\n    <string name=\"show_os_app\">OS-Apps anzeigen</string>\n    <string name=\"hide_filter_hint\">Nach Namen filtern</string>\n    <string name=\"hide_search\">Suche</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Keine Informationen verfügbar)</string>\n    <string name=\"reboot_userspace\">Sanfter Neustart</string>\n    <string name=\"reboot_recovery\">Neustart zum Recovery</string>\n    <string name=\"reboot_bootloader\">Neustart zum Bootloader</string>\n    <string name=\"reboot_download\">Neustart zum Download</string>\n    <string name=\"reboot_edl\">Neustart zum EDL</string>\n    <string name=\"module_version_author\">%1$s von %2$s</string>\n    <string name=\"module_state_remove\">Entfernen</string>\n    <string name=\"module_state_restore\">Wiederherstellen</string>\n    <string name=\"module_action_install_external\">Aus dem Speicher installieren</string>\n    <string name=\"update_available\">Update verfügbar</string>\n    <string name=\"suspend_text_riru\">Modul ausgesetzt, weil %1$s aktiviert ist</string>\n    <string name=\"suspend_text_zygisk\">Modul ausgesetzt, weil %1$s nicht aktiviert ist</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk-Modul aufgrund von Inkompatibilität nicht geladen</string>\n    <string name=\"module_empty\">Kein Modul installiert</string>\n    <string name=\"confirm_install\">Modul %1$s installieren?</string>\n    <string name=\"confirm_install_title\">Installation bestätigen</string>\n \n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Themen-Modus</string>\n    <string name=\"settings_dark_mode_message\">Modus auswählen, der am besten zu deinem Stil passt!</string>\n    <string name=\"settings_dark_mode_light\">Immer hell</string>\n    <string name=\"settings_dark_mode_system\">System folgen</string>\n    <string name=\"settings_dark_mode_dark\">Immer dunkel</string>\n    <string name=\"settings_download_path_title\">Download-Verzeichnis</string>\n    <string name=\"settings_download_path_message\">Dateien werden in %1$s gespeichert</string>\n    <string name=\"settings_hide_app_title\">Magisk-App verstecken</string>\n    <string name=\"settings_hide_app_summary\">Proxy-App mit zufälliger Paket-ID und benutzerdefiniertem App-Label installieren</string>\n    <string name=\"settings_restore_app_title\">Magisk-App wiederherstellen</string>\n    <string name=\"settings_restore_app_summary\">App wieder einblenden und original APK wiederherstellen</string>\n    <string name=\"language\">Sprache</string>\n    <string name=\"system_default\">Systemstandard</string>\n    <string name=\"settings_check_update_title\">Auf Updates prüfen</string>\n    <string name=\"settings_check_update_summary\">Regelmäßig im Hintergrund auf Aktualisierungen prüfen</string>\n    <string name=\"settings_update_channel_title\">Aktualisierungs-Kanal</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Benutzerdefiniert</string>\n    <string name=\"settings_update_custom_msg\">Benutzerdefinierte URL einfügen</string>\n    <string name=\"settings_zygisk_summary\">Teile von Magisk im Zygoten-Daemon ausführen</string>\n    <string name=\"settings_denylist_title\">Ausnahmeliste erzwingen</string>\n    <string name=\"settings_denylist_summary\">Bei Prozessen, die auf der Ausnahmeliste stehen, werden alle Magisk-Änderungen rückgängig gemacht.</string>\n    <string name=\"settings_denylist_config_title\">Ausnahmeliste konfigurieren</string>\n    <string name=\"settings_denylist_config_summary\">Auswahl der Prozesse, die in die Ausnahmeliste aufgenommen werden sollen</string>\n    <string name=\"settings_hosts_title\">Systemlose Hosts-Datei</string>\n    <string name=\"settings_hosts_summary\">Systemlose Unterstützung für Werbeblocker</string>\n    <string name=\"settings_hosts_toast\">Systemloses Hosts-Modul hinzugefügt</string>\n    <string name=\"settings_app_name_hint\">Neuer Name</string>\n    <string name=\"settings_app_name_helper\">App wird unter diesem Namen neu gepackt</string>\n    <string name=\"settings_app_name_error\">Ungültiges Format</string>\n    <string name=\"settings_su_app_adb\">Apps und ADB</string>\n    <string name=\"settings_su_app\">Nur Apps</string>\n    <string name=\"settings_su_adb\">Nur ADB</string>\n    <string name=\"settings_su_disable\">Deaktiviert</string>\n    <string name=\"settings_su_request_10\">10 Sekunden</string>\n    <string name=\"settings_su_request_15\">15 Sekunden</string>\n    <string name=\"settings_su_request_20\">20 Sekunden</string>\n    <string name=\"settings_su_request_30\">30 Sekunden</string>\n    <string name=\"settings_su_request_45\">45 Sekunden</string>\n    <string name=\"settings_su_request_60\">60 Sekunden</string>\n    <string name=\"superuser_access\">Superuser-Zugriff</string>\n    <string name=\"auto_response\">Automatisch beantworten</string>\n    <string name=\"request_timeout\">Zeitlimit für Anfrage</string>\n    <string name=\"superuser_notification\">Superuser-Benachrichtigung</string>\n    <string name=\"settings_su_reauth_title\">Nach Aktualisierung erneut zertifizieren</string>\n    <string name=\"settings_su_reauth_summary\">Superuser-Berechtigungen nach App-Aktualisierung erneut authentifizieren</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking-Schutz aktivieren</string>\n    <string name=\"settings_su_tapjack_summary\">Das Dialogfeld der Superuser-Eingabeaufforderung reagiert nicht auf Eingaben, wenn es durch ein anderes Fenster oder Überlagerung verdeckt wird</string>\n    <string name=\"settings_su_auth_title\">Benutzerauthentifizierung</string>\n    <string name=\"settings_su_auth_summary\">Nachfrage nach Benutzerauthentifizierung bei Superuser-Anfragen</string>\n    <string name=\"settings_su_auth_insecure\">Auf dem Gerät ist keine Authentifizierungsmethode konfiguriert</string>>\n    <string name=\"settings_customization\">Personalisierung</string>\n    <string name=\"setting_add_shortcut_summary\">Hinzufügen einer hübschen Startbildschirm-Verknüpfung, falls der Name und das Symbol nach dem Ausblenden der App schwer zu erkennen sind</string>\n    <string name=\"settings_doh_title\">DNS über HTTPS</string>\n    <string name=\"settings_doh_description\">Umgehung des DNS-Poisoning in einigen Ländern</string>\n\n    <string name=\"multiuser_mode\">Mehrbenutzermodus</string>\n    <string name=\"settings_owner_only\">Nur der Gerätebesitzer</string>\n    <string name=\"settings_owner_manage\">Durch Gerätebesitzer verwaltet</string>\n    <string name=\"settings_user_independent\">Benutzerunabhängig</string>\n    <string name=\"owner_only_summary\">Nur der Besitzer hat Root-Zugriff</string>\n    <string name=\"owner_manage_summary\">Nur der Besitzer verwaltet den Root-Zugriff und erhält Zugriffsanfragen</string>\n    <string name=\"user_independent_summary\">Jeder Nutzer hat seine eigenen Root-Regeln</string>\n\n    <string name=\"mount_namespace_mode\">Mount Namespace Modus</string>\n    <string name=\"settings_ns_global\">Globaler Namespace</string>\n    <string name=\"settings_ns_requester\">Geerbter Namespace</string>\n    <string name=\"settings_ns_isolate\">Isolierter Namespace</string>\n    <string name=\"global_summary\">Alle Root-Sitzungen benutzen den global angelegten Namespace</string>\n    <string name=\"requester_summary\">Root-Sitzungen erben den Namespace des Abfragenden</string>\n    <string name=\"isolate_summary\">Jede Root-Sitzung hat ihren isolierten Namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk Updates</string>\n    <string name=\"progress_channel\">Fortschrittsmitteilungen</string>\n    <string name=\"updated_channel\">Update abgeschlossen</string>\n    <string name=\"download_complete\">Download abgeschlossen</string>\n    <string name=\"download_file_error\">Fehler beim Herunterladen der Datei</string>\n    <string name=\"magisk_update_title\">Magisk Update verfügbar!</string>\n    <string name=\"updated_title\">Magisk aktualisiert</string>\n    <string name=\"updated_text\">Tippen, um die App zu öffnen</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nein</string>\n    <string name=\"repo_install_title\">%1$s installieren %2$s(%3$d)</string>\n    <string name=\"download\">Herunterladen</string>\n    <string name=\"reboot\">Neustart</string>\n    <string name=\"release_notes\">Anmerkungen zur Veröffentlichung</string>\n    <string name=\"flashing\">Flashen …</string>\n    <string name=\"done\">Fertig!</string>\n    <string name=\"failure\">Fehlgeschlagen</string>\n    <string name=\"hide_app_title\">Magisk-App verstecken …</string>\n    <string name=\"open_link_failed_toast\">Keine App zum öffnen des Links gefunden</string>\n    <string name=\"complete_uninstall\">Vollständig deinstallieren</string>\n    <string name=\"restore_img\">Image wiederherstellen</string>\n    <string name=\"restore_img_msg\">Wiederherstellen …</string>\n    <string name=\"restore_done\">Wiederherstellen fertig!</string>\n    <string name=\"restore_fail\">Stock-Sicherung existiert nicht!</string>\n    <string name=\"setup_fail\">Setup fehlgeschlagen</string>\n    <string name=\"env_fix_title\">Zusätzliche Einrichtung erforderlich</string>\n    <string name=\"env_fix_msg\">Magisk braucht ein zusätzliches Setup, um auf deinem Gerät zu funktionieren. Willst du fortfahren und dein Gerät neustarten?</string>\n    <string name=\"env_full_fix_msg\">Dein Gerät benötigt einen Reflash von Magisk, um richtig zu funktionieren. Bitte Magisk innerhalb der App neu installieren, der Wiederherstellungsmodus kann keine korrekten Geräteinformationen abrufen.</string>\n    <string name=\"setup_msg\">Laufzeitumgebung einrichten …</string>\n    <string name=\"unsupport_magisk_title\">Nicht unterstützte Magisk-Version</string>\n    <string name=\"unsupport_magisk_msg\">Diese App-Version unterstützt keine Magisk-Version kleiner als %1$s.\\n\\nDie App verhält sich so, als ob kein Magisk installiert wäre. Bitte Magisk so bald wie möglich aktualisieren.</string>\n    <string name=\"unsupport_general_title\">Abnormaler Zustand</string>\n    <string name=\"unsupport_system_app_msg\">Ausführung dieser App als System-App wird nicht unterstützt. App bitte in eine Benutzer-App umwandeln.</string>\n    <string name=\"unsupport_other_su_msg\">Es wurde ein \\\"su\\\"-Befehl erkannt, der nicht zu Magisk gehört. Das andere nicht unterstützte \\\"su\\\" bitte entfernen.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk ist auf einem externen Speicher installiert. Die App bitte in internen Speicher verschieben.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">App kann im versteckten Zustand nicht weiter funktionieren, da Root verloren gegangen ist. Ursprüngliche APK bitte wiederherstellen.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Speichererlaubnis erteilen, um diese Funktion zu aktivieren</string>\n    <string name=\"install_unknown_denied\">Unbekannte Apps installieren erlauben, um diese Funktion zu aktivieren</string>\n    <string name=\"post_notifications_denied\">Benachrichtigungen die Berechtigung erteilen, diese Funktion zu aktivieren</string>\n    <string name=\"add_shortcut_title\">Verknüpfung zum Startbildschirm hinzufügen</string>\n    <string name=\"add_shortcut_msg\">Nachdem du diese App versteckst, wird der Name und das Symbol eventuell schwer zu erkennen sein. Möchtest du zum Startbildschirm eine schöne Verknüpfung hinzufügen?</string>\n    <string name=\"app_not_found\">Keine App gefunden, die diese Aktion verarbeitet</string>\n    <string name=\"reboot_apply_change\">Neustart zum Änderungen übernehmen</string>\n    <string name=\"restore_app_confirmation\">Dadurch wird die versteckte App wieder zur ursprünglichen App. Willst du das wirklich tun?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-el/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Επεκτάσεις</string>\n    <string name=\"superuser\">Υπερχρήστης</string>\n    <string name=\"logs\">Αρχείο Καταγραφής</string>\n    <string name=\"settings\">Ρυθμίσεις</string>\n    <string name=\"install\">Εγκατάσταση</string>\n    <string name=\"section_home\">Αρχική</string>\n    <string name=\"section_theme\">Θέματα</string>\n    <string name=\"denylist\">Λίστα Απορρίψεων</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Δεν υπάρχει διαθέσιμη σύνδεση</string>\n    <string name=\"app_changelog\">Καταγραφή αλλαγών εφαρμογής</string>\n    <string name=\"loading\">Φόρτωση…</string>\n    <string name=\"update\">Ενημέρωση</string>\n    <string name=\"not_available\">Μη διαθέσιμο</string>\n    <string name=\"hide\">Απόκρυψη</string>\n    <string name=\"home_package\">Πακέτο</string>\n    <string name=\"home_app_title\">Εφαρμογή</string>\n\n    <string name=\"home_notice_content\">Κάντε λήψη του Magisk ΜΟΝΟ από την επίσημη σελίδα του GitHub. Τα αρχεία από άγνωστες πηγές μπορεί να είναι κακόβουλα!</string>\n    <string name=\"home_support_title\">Υποστήριξη</string>\n    <string name=\"home_follow_title\">Ακολούθησε μας</string>\n    <string name=\"home_item_source\">Πηγή</string>\n    <string name=\"home_support_content\">Το Magisk είναι, και θα είναι για πάντα, δωρεάν και ανοιχτού κώδικα. Μπορείτε ωστόσο να μας δείξετε ότι ενδιαφέρεστε στέλνοντας μια μικρή δωρεά.</string>\n    <string name=\"home_installed_version\">Τρέχουσα έκδοση</string>\n    <string name=\"home_latest_version\">Διαθέσιμη έκδοση</string>\n    <string name=\"invalid_update_channel\">Λανθασμένο κανάλι ενημέρωσης</string>\n    <string name=\"uninstall_magisk_title\">Απεγκατάσταση Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Όλα τα modules θα απενεργοποιηθούν/αφαιρεθούν. Το root θα αφαιρεθεί και ενδέχεται να κρυπτογραφηθούν τα δεδομένα σας, εάν δεν είναι κρυπτογραφημένα!</string>\n\n    <!--Install-->\n\t<string name=\"keep_force_encryption\">Διατήρηση επιβεβλημένης κρυπτογράφησης</string>\n    <string name=\"keep_dm_verity\">Διατήρηση AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Μέσω Recovery mode</string>\n    <string name=\"install_options_title\">Επιλογές</string>\n    <string name=\"install_method_title\">Μέθοδος</string>\n    <string name=\"install_next\">Επόμενο</string>\n    <string name=\"install_start\">Εκκίνηση</string>\n    <string name=\"manager_download_install\">Πιέστε για λήψη και εγκατάσταση</string>\n    <string name=\"direct_install\">Απευθείας Εγκατάσταση (Προτείνεται)</string>\n    <string name=\"install_inactive_slot\">Εγκατάσταση στο ανενεργό Slot (Μετά από OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Η συσκευή σας θα αναγκαστεί να εκκινήσει στο τρέχον ανενεργό Slot μετά από επανεκκίνηση!\\nΧρησιμοποιήστε αυτήν την επιλογή μόνο αφού ολοκληρωθεί το OTA.\\nΣυνέχεια;</string>\n    <string name=\"setup_title\">Πρόσθετη εγκατάσταση</string>\n    <string name=\"select_patch_file\">Επέλεξε και κάνε Patch ένα αρχείο</string>\n    <string name=\"patch_file_msg\">Επέλεξε μία raw εικόνα (*.img) ή ένα ODIN tarfile (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Επανεκκίνηση σε 5 δευτερόλεπτα…</string>\n    <string name=\"flash_screen_title\">Εγκατάσταση</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Αίτημα υπερχρήστη</string>\n    <string name=\"touch_filtered_warning\">Επειδή μια εφαρμογή αποκρύπτει ένα αίτημα υπερχρήστη, το Magisk δεν μπορεί να επαληθεύσει την απάντησή σας</string>\n    <string name=\"deny\">Άρνηση</string>\n    <string name=\"prompt\">Προτροπή</string>\n    <string name=\"grant\">Αποδοχή</string>\n    <string name=\"su_warning\">Δίνει πλήρη πρόσβαση στη συσκευή σας.\\nΑρνηθείτε αν δεν είστε σίγουρος/η!</string>\n    <string name=\"forever\">Μόνιμα</string>\n    <string name=\"once\">Μία φορά</string>\n    <string name=\"tenmin\">10 λεπτά</string>\n    <string name=\"twentymin\">20 λεπτά</string>\n    <string name=\"thirtymin\">30 λεπτά</string>\n    <string name=\"sixtymin\">60 λεπτά</string>\n    <string name=\"su_allow_toast\">Παραχωρήθηκαν δικαιώματα υπερχρήστη στο %1$s</string>\n    <string name=\"su_deny_toast\">Απορρίφθηκαν τα δικαιώματα υπερχρήστη του %1$s</string>\n    <string name=\"su_snack_grant\">Παραχορούνται δικαιώματα υπερχρήστη στο %1$s</string>\n    <string name=\"su_snack_deny\">Δεν παραχορούνται δικαιώματα υπερχρήστη στο %1$s</string>\n    <string name=\"su_snack_notif_on\">Οι ειδοποιήσεις του %1$s είναι ενεργοποιημένες</string>\n    <string name=\"su_snack_notif_off\">Οι ειδοποιήσεις του %1$s είναι απενεργοποιημένες</string>\n    <string name=\"su_snack_log_on\">Η καταγραφή του %1$s είναι ενεργοποιημένη</string>\n    <string name=\"su_snack_log_off\">Η καταγραφή του %1$s είναι απενεργοποιημένη</string>\n    <string name=\"su_revoke_title\">Ανάκληση;</string>\n    <string name=\"su_revoke_msg\">Επιβεβαίωση για ανάκληση δικαιωμάτων %1$s;</string>\n    <string name=\"toast\">Αναδυόμενο παράθυρο</string>\n    <string name=\"none\">Κανένα</string>\n\n    <string name=\"superuser_toggle_notification\">Ειδοποιήσεις</string>\n    <string name=\"superuser_toggle_revoke\">Ανακάλεσε</string>\n    <string name=\"superuser_policy_none\">Καμία εφαρμογή δεν έχει ζητήσει άδεια υπερχρήστη.</string>\n\t\n    <!--Logs-->\n    <string name=\"log_data_none\">Δεν υπάρχουν αρχεία καταγραφής, δοκιμάστε να χρησιμοποιήσετε περισσότερο τις εφαρμογές με δυνατότητα SU</string>\n    <string name=\"log_data_magisk_none\">Τα αρχεία καταγραφής του Magisk είναι κενά, αυτό είναι περίεργο</string>\n    <string name=\"menuSaveLog\">Αποθήκευση καταγραφής</string>\n    <string name=\"menuClearLog\">Εκκαθάριση αρχείου καταγραφής</string>\n    <string name=\"logs_cleared\">Το αρχείο καταγραφής εκκαθαρίστηκε επιτυχώς</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID Στόχος: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Εμφάνιση εφαρμογών συστήματος</string>\n    <string name=\"show_os_app\">Εμφάνιση εφαρμογών OS</string>\n    <string name=\"hide_filter_hint\">Φιλτράρισμα</string>\n    <string name=\"hide_search\">Αναζήτηση</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Δεν δόθηκαν πληροφορίες)</string>\n    <string name=\"reboot_userspace\">Soft επανεκκίνηση</string>\n    <string name=\"reboot_recovery\">Επανεκκίνηση στο Recovery</string>\n    <string name=\"reboot_bootloader\">Επανεκκίνηση στο Bootloader</string>\n    <string name=\"reboot_download\">Επανεκκίνηση για λήψη</string>\n    <string name=\"reboot_edl\">Επανεκκίνηση σε EDL</string>\n    <string name=\"module_version_author\">%1$s από %2$s</string>\n    <string name=\"module_state_remove\">Αφαίρεση</string>\n    <string name=\"module_state_restore\">Επαναφορά</string>\n    <string name=\"module_action_install_external\">Εγκατάσταση από χώρο αποθήκευσης</string>\n    <string name=\"update_available\">Διαθέσιμη Ενημέρωση</string>\n    <string name=\"suspend_text_riru\">Η επέκταση έχει ανασταλεί επειδή το %1$s είναι ενεργοποιημένο</string>\n    <string name=\"suspend_text_zygisk\">Η επέκταση έχει ανασταλεί επειδή το %1$s δεν είναι ενεργοποιημένο</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk module not loaded due to incompatibility</string>\n    <string name=\"module_empty\">Δεν υπάρχουν εγκατεστημένες επεκτάσεις</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Λειτουργία θέματος</string>\n    <string name=\"settings_dark_mode_message\">Επιλέξτε τη λειτουργία που ταιριάζει καλύτερα στο στυλ σας!</string>\n    <string name=\"settings_dark_mode_light\">Πάντα Light</string>\n    <string name=\"settings_dark_mode_system\">Ακολούθησε το σύστημα</string>\n    <string name=\"settings_dark_mode_dark\">Πάντα Dark</string>\n    <string name=\"settings_download_path_title\">Διαδρομή λήψης</string>\n    <string name=\"settings_download_path_message\">Τα αρχεία θα αποθηκευτούν στο %1$s</string>\n    <string name=\"settings_hide_app_title\">Απόκρυψη της εφαρμογής Magisk</string>\n    <string name=\"settings_hide_app_summary\">Εγκαταστήστε μια εφαρμογή με τυχαίο αναγνωριστικό πακέτου και προσαρμοσμένη ετικέτα εφαρμογής</string>\n    <string name=\"settings_restore_app_title\">Επαναφέρετε την εφαρμογή Magisk</string>\n    <string name=\"settings_restore_app_summary\">Καταργήστε την απόκρυψη της εφαρμογής και επαναφέρετέ την στο αρχικό APK</string>\n    <string name=\"language\">Γλώσσα</string>\n    <string name=\"system_default\">(Προεπιλογή Συστήματος)</string>\n    <string name=\"settings_check_update_title\">Έλεγχος Ενημερώσεων</string>\n    <string name=\"settings_check_update_summary\">Περιοδικά ελέγχετε για ενημερώσεις στο παρασκήνιο</string>\n    <string name=\"settings_update_channel_title\">Κανάλι Ενημερώσεων</string>\n    <string name=\"settings_update_stable\">Σταθερό</string>\n    <string name=\"settings_update_beta\">Δοκιμαστικό</string>\n    <string name=\"settings_update_custom\">Προσαρμοσμένο</string>\n    <string name=\"settings_update_custom_msg\">Εισαγωγή ενός custom URL</string>\n    <string name=\"settings_zygisk_summary\">Εκτέλεση μέρους του Magisk μέσα στο zygote daemon</string>\n    <string name=\"settings_denylist_title\">Επιβολή Λίστας Απορρίψεων</string>\n    <string name=\"settings_denylist_summary\">Επαναφορά όλων των τροποποιήσεων του Magisk για της διεργασίες στην λίστα απορρίψεων</string>\n    <string name=\"settings_denylist_config_title\">Τροποποίηση της Λίστας Απορρίψεων</string>\n    <string name=\"settings_denylist_config_summary\">Επιλογή διεργασίας για να συμπεριληφθεί στη λίστα απορρίψεων</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Υποστήριξη Systemless hosts για εφαρμογές Adblock</string>\n    <string name=\"settings_hosts_toast\">Προσθήκη αρθρώματος systemless hosts</string>\n    <string name=\"settings_app_name_hint\">Νέο όνομα</string>\n    <string name=\"settings_app_name_helper\">Η εφαρμογή θα επανασυσκευαστεί με αυτό το όνομα</string>\n    <string name=\"settings_app_name_error\">Μη έγκυρη μορφή</string>\n    <string name=\"settings_su_app_adb\">Εφαρμογές και ADB</string>\n    <string name=\"settings_su_app\">Εφαρμογές μόνο</string>\n    <string name=\"settings_su_adb\">ADB μόνο</string>\n    <string name=\"settings_su_disable\">Απενεργοποιημένο</string>\n    <string name=\"settings_su_request_10\">10 δευτερόλεπτα</string>\n    <string name=\"settings_su_request_15\">15 δευτερόλεπτα</string>\n    <string name=\"settings_su_request_20\">20 δευτερόλεπτα</string>\n    <string name=\"settings_su_request_30\">30 δευτερόλεπτα</string>\n    <string name=\"settings_su_request_45\">45 δευτερόλεπτα</string>\n    <string name=\"settings_su_request_60\">60 δευτερόλεπτα</string>\n    <string name=\"superuser_access\">Πρόσβαση Υπερχρήστη</string>\n    <string name=\"auto_response\">Αυτόματη Απόκριση</string>\n    <string name=\"request_timeout\">Χρονικό όριο Αιτήματος</string>\n    <string name=\"superuser_notification\">Ειδοποίηση Υπερχρήστη</string>\n    <string name=\"settings_su_reauth_title\">Επαναπιστοποίηση μετά από αναβάθμιση</string>\n    <string name=\"settings_su_reauth_summary\">Επαναπιστοποίηση αδειών υπερχρήστη μετά την αναβάθμιση μίας εφαρμογής</string>\n    <string name=\"settings_su_tapjack_title\">Ενεργοποίηση προστασίας Tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">Το παράθυρο διαλόγου προτροπής του υπερχρήστη δεν αποκρίνεται στην είσοδο ενώ κρύβεται από οποιοδήποτε άλλο παράθυρο</string>\n    <string name=\"settings_customization\">Προσαρμογή</string>\n    <string name=\"setting_add_shortcut_summary\">Προσθέστε μια όμορφη συντόμευση στην αρχική οθόνη σε περίπτωση που το όνομα και το εικονίδιο είναι δύσκολο να αναγνωριστούν αφού κρύψετε την εφαρμογή</string>\n    <string name=\"settings_doh_title\">DNS μέσω HTTPS</string>\n    <string name=\"settings_doh_description\">Λύση DNS poisoning για μερικά έθνη</string>\n\n    <string name=\"multiuser_mode\">Λειτουργία Πολλών Χρηστών</string>\n    <string name=\"settings_owner_only\">Μόνο Ιδιοκτήτης Συσκευής</string>\n    <string name=\"settings_owner_manage\">Διαχειριζόμενη από τον Ιδιοκτήτη</string>\n    <string name=\"settings_user_independent\">Ανεξάρτητη από τον χρήστη</string>\n    <string name=\"owner_only_summary\">Μόνο ο ιδιοκτήτης έχει πρόσβαση root</string>\n    <string name=\"owner_manage_summary\">Μόνο ο ιδιοκτήτης μπορεί να διαχειριστεί την πρόσβαση root και να δεχτεί προτροπές αίτημάτων</string>\n    <string name=\"user_independent_summary\">Κάθε χρήστης έχει τους δικούς του ξεχωριστούς κανόνες root</string>\n\n    <string name=\"mount_namespace_mode\">Λειτουργία προσάρτησης χώρου ονομάτων</string>\n    <string name=\"settings_ns_global\">Καθολικός Χώρος Ονομάτων</string>\n    <string name=\"settings_ns_requester\">Κληρονόμησε Χώρο Ονομάτων</string>\n    <string name=\"settings_ns_isolate\">Απομονωμένος Χώρος Ονομάτων</string>\n    <string name=\"global_summary\">Όλες οι συνεδρίες root χρησιμοποιούν τον καθολικό χώρο oνομάτων προσάρτησης</string>\n    <string name=\"requester_summary\">Οι συνεδρίες root θα κληρονομούν το χώρο ονομάτων του αιτούντα τους</string>\n    <string name=\"isolate_summary\">Κάθε συνεδρία root θα έχει το δικό της απομονωμένο χώρο ονομάτων</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Ενημερώσεις Magisk</string>\n    <string name=\"progress_channel\">Ειδοποιήσεις προόδου</string>\n    <string name=\"updated_channel\">Η Αναβάθμηση Ολοκληρώθηκε</string>\n    <string name=\"download_complete\">Η λήψη ολοκληρώθηκε</string>\n    <string name=\"download_file_error\">Σφάλμα στη λήψη του αρχείου</string>\n    <string name=\"magisk_update_title\">Νέα Ενημέρωση Magisk Διαθέσιμη!</string>\n    <string name=\"updated_title\">Το Magisk Ενημερώθηκε</string>\n    <string name=\"updated_text\">Πατήστε για άνοιγμα εφαρμογής</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ναι</string>\n    <string name=\"no\">Όχι</string>\n    <string name=\"repo_install_title\">Εγκατάσταση %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Λήψη</string>\n    <string name=\"reboot\">Επανεκκίνηση</string>\n    <string name=\"release_notes\">Σημειώσεις έκδοσης</string>\n    <string name=\"flashing\">Προγραμματισμός σε εξέλιξη</string>\n    <string name=\"done\">Ολοκληρώθηκε</string>\n    <string name=\"failure\">Αποτυχία</string>\n    <string name=\"hide_app_title\">Απόκρυψη της εφαρμογής Magisk…</string>\n    <string name=\"open_link_failed_toast\">Δεν βρέθηκε εφαρμογή για άνοιγμα του συνδέσμου</string>\n    <string name=\"complete_uninstall\">Πλήρης απεγκατάσταση</string>\n    <string name=\"restore_img\">Επαναφορά Images</string>\n    <string name=\"restore_img_msg\">Επαναφορά…</string>\n    <string name=\"restore_done\">Η επαναφορά πραγματοποιήθηκε</string>\n    <string name=\"restore_fail\">Δεν υπάρχει αντίγραφο ασφαλείας!</string>\n    <string name=\"setup_fail\">Η εγκατάσταση απέτυχε</string>\n    <string name=\"env_fix_title\">Απαιτείται πρόσθετη εγκατάσταση</string>\n    <string name=\"env_fix_msg\">Η συσκευή σας χρειάζεται πρόσθετη ρύθμιση για να λειτουργεί σωστά το Magisk. Θέλετε να συνεχίσετε και να επανεκκινήσετε;</string>\n    <string name=\"setup_msg\">Τρέχει η εγκατάσταση περιβάλλοντος…</string>\n    <string name=\"unsupport_magisk_title\">Μη υποστηριζόμενη έκδοση Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Αυτή η έκδοση της εφαρμογής δεν υποστηρίζει έκδοση Magisk χαμηλότερη από %1$s.\\n\\nΗ εφαρμογή θα συμπεριφέρεται σαν να μην έχει εγκατασταθεί το Magisk, αναβαθμίστε το Magisk το συντομότερο δυνατό.</string>\n    <string name=\"unsupport_general_title\">Μη Φυσιολογική Κατάσταση</string>\n    <string name=\"unsupport_system_app_msg\">Η εκτέλεση αυτής της εφαρμογής ως εφαρμογής συστήματος δεν υποστηρίζεται. Επαναφέρετε την εφαρμογή σε εφαρμογή χρήστη.</string>\n    <string name=\"unsupport_other_su_msg\">Εντοπίστηκε ένα \\\"su\\\" binary που δεν είναι από το Magisk. Καταργήστε οποιαδήποτε άλλη root μέθοδο και/ή επανεγκαταστήστε το Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Το Magisk είναι εγκατεστημένο σε εξωτερικό χώρο αποθήκευσης. Μετακινήστε την εφαρμογή στον εσωτερικό χώρο αποθήκευσης.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Η κρυφή εφαρμογή Magisk δεν μπορεί να συνεχίσει να λειτουργεί επειδή χάθηκε το root. Παρακαλώ επαναφέρετε το αρχικό APK.</string>\n    \n\t<string name=\"external_rw_permission_denied\">Παραχωρήστε άδεια αποθήκευσης για να ενεργοποιήσετε αυτήν τη λειτουργία</string>\n    <string name=\"install_unknown_denied\">Επιτρέψτε την \"εγκατάσταση άγνωστων εφαρμογών\" για να ενεργοποιήσετε αυτήν τη λειτουργία</string>\n    <string name=\"add_shortcut_title\">Προσθέστε συντόμευση στην αρχική οθόνη</string>\n    <string name=\"add_shortcut_msg\">Μετά την απόκρυψη αυτής της εφαρμογής, το όνομα και το εικονίδιο της ενδέχεται να είναι δύσκολο να αναγνωριστούν. Θέλετε να προσθέσετε μια όμορφη συντόμευση στην αρχική οθόνη;</string>\n    <string name=\"app_not_found\">Δεν βρέθηκε εφαρμογή που να χειρίζεται αυτήν την ενέργεια</string>\n    <string name=\"reboot_apply_change\">Επανεκκινήστε για να εφαρμόσετε της αλλαγές</string>\n    <string name=\"restore_app_confirmation\">Αυτό θα επαναφέρει την κρυφή εφαρμογή πίσω στην αρχική εφαρμογή. Θέλετε πραγματικά να το κάνετε αυτό;</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-es/strings.xml",
    "content": "<resources>\n\n    <!--Secciones-->\n    <string name=\"modules\">Módulos</string>\n    <string name=\"superuser\">Superusuario</string>\n    <string name=\"logs\">Registros</string>\n    <string name=\"settings\">Ajustes</string>\n    <string name=\"install\">Instalar</string>\n    <string name=\"section_home\">Inicio</string>\n    <string name=\"section_theme\">Temas</string>\n    <string name=\"denylist\">Lista de denegación</string>\n\n    <!--Inicio/Pantalla principal-->\n    <string name=\"no_connection\">Sin conexión disponible</string>\n    <string name=\"app_changelog\">Registro de cambios</string>\n    <string name=\"loading\">Cargando…</string>\n    <string name=\"update\">Actualizar</string>\n    <string name=\"not_available\">No disponible</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"home_package\">Nombre de paquete:</string>\n    <string name=\"home_app_title\">App</string>\n    <string name=\"home_notice_content\">Descarga Magisk únicamente desde la página oficial de GitHub. ¡Las descargas desde fuentes desconocidas pueden ser maliciosas!</string>\n    <string name=\"home_support_title\">Apóyanos</string>\n    <string name=\"home_follow_title\">Síguenos</string>\n    <string name=\"home_item_source\">Código fuente</string>\n    <string name=\"home_support_content\">Magisk es y siempre será gratis y de código abierto. Sin embargo, puedes mostrarnos tu apoyo mediante una donación.</string>\n    <string name=\"home_installed_version\">Versión instalada:</string>\n    <string name=\"home_latest_version\">Última versión disponible:</string>\n    <string name=\"invalid_update_channel\">Canal de actualización inválido</string>\n    <string name=\"uninstall_magisk_title\">Desinstalar Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Antes de desinstalar Magisk, ten en cuenta lo siguiente:\\n• Todos los módulos serán desinstalados\\n• Perderás el acceso root\\n• Tu almacenamiento interno será reencriptado en caso de que no lo esté</string>\n\n    <!--Instalación-->\n    <string name=\"keep_force_encryption\">Mantener FDE (Full-Disk Encryption)</string>\n    <string name=\"keep_dm_verity\">Mantener AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Modo recovery</string>\n    <string name=\"install_options_title\">Opciones de instalación:</string>\n    <string name=\"install_method_title\">Método de instalación:</string>\n    <string name=\"install_next\">Siguiente</string>\n    <string name=\"install_start\">Instalar</string>\n    <string name=\"manager_download_install\">Pulsa para descargar e instalar</string>\n    <string name=\"direct_install\">Instalación directa (recomendado)</string>\n    <string name=\"install_inactive_slot\">Instalar en el slot inactivo (tras una OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Tu dispositivo será forzado a bootear en el slot inactivo actual después de un reinicio\\nUsa esta opción solo después de que la actualización OTA se complete.\\n¿Quieres continuar?</string>\n    <string name=\"setup_title\">Configuración adicional</string>\n    <string name=\"select_patch_file\">Seleccionar y parchar un archivo</string>\n    <string name=\"patch_file_msg\">Selecciona una imagen raw (.img) o un archivo comprimido que la contenga (.tar), en caso de que quieras usar Odin (Samsung)</string>\n    <string name=\"reboot_delay_toast\">Reiniciando en 5 segundos…</string>\n    <string name=\"flash_screen_title\">Instalación</string>\n\n    <!--Superusuario-->\n    <string name=\"su_request_title\">Solicitud de superusuario</string>\n    <string name=\"touch_filtered_warning\">Hay una app superponiéndose a la solicitud de superusuario, por lo que Magisk no puede verificar tu respuesta.</string>\n    <string name=\"deny\">Denegar</string>\n    <string name=\"prompt\">Preguntar</string>\n    <string name=\"grant\">Conceder</string>\n    <string name=\"su_warning\">Le concedes a esta app acceso completo a tu dispositivo.\\n¡Rechaza la solicitud si desconfías de ella!</string>\n    <string name=\"forever\">Siempre</string>\n    <string name=\"once\">Una vez</string>\n    <string name=\"tenmin\">10 minutos</string>\n    <string name=\"twentymin\">20 minutos</string>\n    <string name=\"thirtymin\">30 minutos</string>\n    <string name=\"sixtymin\">1 hora</string>\n    <string name=\"su_allow_toast\">Le concediste privilegios de superusuario a %1$s</string>\n    <string name=\"su_deny_toast\">Le denegaste privilegios de superusuario a %1$s</string>\n    <string name=\"su_snack_grant\">Privilegios de superusuario para %1$s concedidos</string>\n    <string name=\"su_snack_deny\">Privilegios de superusuario para %1$s denegados</string>\n    <string name=\"su_snack_notif_on\">Notificaciones de %1$s habilitadas</string>\n    <string name=\"su_snack_notif_off\">Notificaciones de %1$s deshabilitadas</string>\n    <string name=\"su_snack_log_on\">Registros de %1$s habilitados</string>\n    <string name=\"su_snack_log_off\">Registros de %1$s deshabilitados</string>\n    <string name=\"su_revoke_title\">Revocar privilegios</string>\n    <string name=\"su_revoke_msg\">Confirma para revocarle los privilegios de superusuario a %1$s</string>\n    <string name=\"toast\">Mensaje emergente</string>\n    <string name=\"none\">Ninguno</string>\n    <string name=\"superuser_toggle_notification\">Notificaciones</string>\n    <string name=\"superuser_toggle_revoke\">Revocar</string>\n    <string name=\"superuser_policy_none\">Ninguna app ha solicitado privilegios de superusuario aún.</string>\n\n    <!--Registros-->\n    <string name=\"log_data_none\">No hay registros de apps disponibles</string>\n    <string name=\"log_data_magisk_none\">No hay registros de Magisk disponibles</string>\n    <string name=\"menuSaveLog\">Guardar registro</string>\n    <string name=\"menuClearLog\">Limpiar registro</string>\n    <string name=\"logs_cleared\">Registros limpiados correctamente</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Objetivo UID: %1$d</string>\n    <string name=\"target_pid\">Montar ns objetivo PID: %s</string>\n    <string name=\"selinux_context\">Contexto SELinux: %s</string>\n    <string name=\"supp_group\">Grupo suplementario: %s</string>\n\n    <!--SafetyNet/PlayIntegrity-->\n\n    <!--Denylist-->\n    <string name=\"show_system_app\">Mostrar apps del sistema</string>\n    <string name=\"show_os_app\">Mostrar apps del sistema operativo</string>\n    <string name=\"hide_filter_hint\">Nombre de la app o del paquete</string>\n    <string name=\"hide_search\">Buscar</string>\n\n    <!--Módulos-->\n    <string name=\"no_info_provided\">El creador del módulo no proporcionó ninguna descripción.</string>\n    <string name=\"reboot_userspace\">Reinicio suave</string>\n    <string name=\"reboot_recovery\">Modo Recovery</string>\n    <string name=\"reboot_bootloader\">Modo Bootloader</string>\n    <string name=\"reboot_download\">Modo Download</string>\n    <string name=\"reboot_edl\">Modo EDL</string>\n    <string name=\"module_version_author\">Versión %1$s, por %2$s</string>\n    <string name=\"module_state_remove\">Desinstalar</string>\n    <string name=\"module_state_restore\">Reinstalar</string>\n    <string name=\"module_action_install_external\">Instalar desde almacenamiento</string>\n    <string name=\"update_available\">Actualización disponible</string>\n    <string name=\"suspend_text_riru\">Módulo suspendido porque %1$s está activado</string>\n    <string name=\"suspend_text_zygisk\">Módulo suspendido porque %1$s está desactivado</string>\n    <string name=\"zygisk_module_unloaded\">El módulo Zygisk no se cargó por problemas de compatibilidad</string>\n    <string name=\"module_empty\">No hay módulos instalados</string>\n    <string name=\"confirm_install\">¿Quieres instalar el módulo %1$s?</string>\n    <string name=\"confirm_install_title\">Confirmar instalación</string>\n\n    <!--Ajustes-->\n    <string name=\"settings_dark_mode_title\">Modo del tema</string>\n    <string name=\"settings_dark_mode_message\">¡Elige el modo que más se adapte a tu estilo!</string>\n    <string name=\"settings_dark_mode_light\">Claro</string>\n    <string name=\"settings_dark_mode_dark\">Oscuro</string>\n    <string name=\"settings_dark_mode_system\">Predeterminado del sistema</string>\n    <string name=\"settings_download_path_title\">Ruta de descarga</string>\n    <string name=\"settings_download_path_message\">Los archivos serán guardados en la siguiente ruta:\\n%1$s</string>\n    <string name=\"settings_hide_app_title\">Ocultar la app de Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instalar una versión proxy de la app con un nombre de paquete aleatorio y una etiqueta personalizada</string>\n    <string name=\"settings_restore_app_title\">Restaurar la app</string>\n    <string name=\"settings_restore_app_summary\">Desoculta la app y la reemplaza por la original</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"system_default\">Predeterminado del sistema</string>\n    <string name=\"settings_check_update_title\">Buscar actualizaciones</string>\n    <string name=\"settings_check_update_summary\">Busca actualizaciones periódicamente en segundo plano</string>\n    <string name=\"settings_update_channel_title\">Canal de actualización</string>\n    <string name=\"settings_update_stable\">Estable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Personalizado</string>\n    <string name=\"settings_update_custom_msg\">Ingresa la URL del canal personalizado</string>\n    <string name=\"settings_zygisk_summary\">Ejecuta Magisk en el proceso Zygote de Android</string>\n    <string name=\"settings_denylist_title\">Aplicar lista de denegación</string>\n    <string name=\"settings_denylist_summary\">Las apps en la lista de denegación no serán modificadas por Magisk</string>\n    <string name=\"settings_denylist_config_title\">Configurar lista de denegación</string>\n    <string name=\"settings_denylist_config_summary\">Selecciona los procesos de cada app que se incluirán en la lista</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Prepara al archivo hosts para que los bloqueadores de anuncios puedan modificarlo sin alterar el sistema</string>\n    <string name=\"settings_hosts_toast\">El módulo se instaló correctamente, reinicia para aplicar los cambios.</string>\n    <string name=\"settings_app_name_hint\">Nuevo nombre de la app</string>\n    <string name=\"settings_app_name_helper\">La app de Magisk será reempaquetada con este nombre</string>\n    <string name=\"settings_app_name_error\">El nombre es inválido</string>\n    <string name=\"settings_su_app_adb\">Permitir tanto a las apps como a ADB</string>\n    <string name=\"settings_su_app\">Permitir solo a las apps</string>\n    <string name=\"settings_su_adb\">Permitir solo a ADB</string>\n    <string name=\"settings_su_disable\">Deshabilitado</string>\n    <string name=\"settings_su_request_10\">10 segundos</string>\n    <string name=\"settings_su_request_15\">15 segundos</string>\n    <string name=\"settings_su_request_20\">20 segundos</string>\n    <string name=\"settings_su_request_30\">30 segundos</string>\n    <string name=\"settings_su_request_45\">45 segundos</string>\n    <string name=\"settings_su_request_60\">60 segundos</string>\n    <string name=\"superuser_access\">Acceso de superusuario</string>\n    <string name=\"auto_response\">Respuesta predeterminada</string>\n    <string name=\"request_timeout\">Duración de la solicitud</string>\n    <string name=\"superuser_notification\">Notificación de superusuario</string>\n    <string name=\"settings_su_reauth_title\">Reautenticar después de una actualización</string>\n    <string name=\"settings_su_reauth_summary\">Las apps volverán a solicitar privilegios de superusuario tras actualizarse</string>\n    <string name=\"settings_su_tapjack_title\">Protección contra tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">No podrás interactuar con las solicitudes de superusuario mientras estén tapadas por cualquier otra ventana o superposición</string>\n    <string name=\"settings_su_auth_title\">Autenticación de usuario</string>\n    <string name=\"settings_su_auth_summary\">Preguntar por la autenticación de usuario durante las peticiones de Superusuario</string>\n    <string name=\"settings_su_auth_insecure\">No hay método de autenticación configurado en el dispositivo</string>\n    <string name=\"settings_customization\">Personalización</string>\n    <string name=\"setting_add_shortcut_summary\">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>\n    <string name=\"settings_doh_title\">DNS sobre HTTPS</string>\n    <string name=\"settings_doh_description\">Proporciona una solución alternativa (workaround) contra el envenenamiento de DNS en algunos países</string>\n    <string name=\"multiuser_mode\">Modo multiusuario</string>\n    <string name=\"settings_owner_only\">Propietario del dispositivo</string>\n    <string name=\"settings_owner_manage\">Administrado por el propietario del dispositivo</string>\n    <string name=\"settings_user_independent\">Acceso independiente</string>\n    <string name=\"owner_only_summary\">Solo el administrador tiene acceso root</string>\n    <string name=\"owner_manage_summary\">Solo el administrador puede manipular y recibir solicitudes de superusuario</string>\n    <string name=\"user_independent_summary\">Cada usuario tiene sus propias reglas y acceso al root</string>\n    <string name=\"mount_namespace_mode\">Modo del namespace de montaje</string>\n    <string name=\"settings_ns_global\">Namespace global</string>\n    <string name=\"settings_ns_requester\">Namespace heredado</string>\n    <string name=\"settings_ns_isolate\">Namespace aislado</string>\n    <string name=\"global_summary\">Todas las sesiones de superusuario usarán el namespace de montaje global</string>\n    <string name=\"requester_summary\">Las sesiones de superusuario heredarán el namespace de montaje de quien las solicita</string>\n    <string name=\"isolate_summary\">Cada sesión de superusuario tendrá su propio namespace de montaje aislado</string>\n\n    <!--Notificaciones-->\n    <string name=\"update_channel\">Actualizaciones de Magisk</string>\n    <string name=\"progress_channel\">Notificaciones de progreso</string>\n    <string name=\"updated_channel\">Actualización finalizada</string>\n    <string name=\"download_complete\">Descarga finalizada</string>\n    <string name=\"download_file_error\">Ocurrió un error en la descarga</string>\n    <string name=\"magisk_update_title\">¡Actualización de Magisk disponible!</string>\n    <string name=\"updated_title\">Se actualizó Magisk</string>\n    <string name=\"updated_text\">Pulsa para abrir la app</string>\n\n    <!--Alertas y diálogos-->\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">No</string>\n    <string name=\"repo_install_title\">Instalar %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Descargar</string>\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"release_notes\">Notas de lanzamiento</string>\n    <string name=\"flashing\">Flasheando…</string>\n    <string name=\"done\">¡Listo!</string>\n    <string name=\"failure\">¡Ocurrió un error!</string>\n    <string name=\"hide_app_title\">Ocultando la app de Magisk…</string>\n    <string name=\"open_link_failed_toast\">No se encontró ninguna app que pueda abrir este enlace</string>\n    <string name=\"complete_uninstall\">Desinstalación completa</string>\n    <string name=\"restore_img\">Solo restaurar las imágenes</string>\n    <string name=\"restore_img_msg\">Restaurando…</string>\n    <string name=\"restore_done\">¡Restauración completa!</string>\n    <string name=\"restore_fail\">Oops... no se encontró una copia de seguridad de las imágenes originales</string>\n    <string name=\"setup_fail\">Ocurrió un error en la configuración</string>\n    <string name=\"env_fix_title\">Configuración adicional</string>\n    <string name=\"env_fix_msg\">Tu dispositivo necesita una configuración adicional para que Magisk funcione correctamente. ¿Quieres continuar y reiniciar?</string>\n    <string name=\"env_full_fix_msg\">Tu dispositivo necesita reinstalar Magisk para que este funcione correctamente. Por favor, reinstala Magisk desde la app, el modo recovery no puede obtener la información correcta del dispositivo.</string>\n    <string name=\"setup_msg\">Ejecutando configuración de entorno…</string>\n    <string name=\"unsupport_magisk_title\">Versión de Magisk no soportada</string>\n    <string name=\"unsupport_magisk_msg\">La versión actual de la app no soporta versiones de Magisk inferiores a la %1$s.\\n\\nLa app se comportará como si Magisk no estuviese instalado, por favor, actualízalo tan pronto como puedas</string>\n    <string name=\"unsupport_general_title\">Estado anormal</string>\n    <string name=\"unsupport_system_app_msg\">La ejecución de esta app como app del sistema no está soportada. Por favor, vuelve a instalarla como usuario</string>\n    <string name=\"unsupport_other_su_msg\">Se detectó un binario \\\"su\\\" ajeno a Magisk. Por favor, desinstala cualquier solución root de la competencia y/o reinstala Magisk</string>\n    <string name=\"unsupport_external_storage_msg\">La app de Magisk está instalada en un almacenamiento externo, por favor, muévela al almacenamiento interno.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">La app de Magisk oculta no puede seguir funcionando porque se perdió el acceso root. Por favor, restaura el APK original</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Permite el acceso al almacenamiento para activar esta funcionalidad</string>\n    <string name=\"post_notifications_denied\">Permite mostrar notificaciones para activar esta funcionalidad</string>\n    <string name=\"install_unknown_denied\">Permite la instalación de apps desconocidas para activar esta funcionalidad</string>\n    <string name=\"add_shortcut_title\">Añadir un atajo a la pantalla de inicio</string>\n    <string name=\"add_shortcut_msg\">Añade un atajo a la app con el ícono de Magisk a la pantalla de inicio en caso de que te resulte difícil reconocerla tras haberle cambiado el nombre y el ícono</string>\n    <string name=\"app_not_found\">No se encontró ninguna app que pueda realizar esta acción</string>\n    <string name=\"reboot_apply_change\">Reinicia para aplicar los cambios</string>\n    <string name=\"restore_app_confirmation\">Se restaurará la app oculta de vuelta a la original. ¿Quieres continuar?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-et/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moodulid</string>\n    <string name=\"superuser\">Superkasutaja</string>\n    <string name=\"logs\">Logid</string>\n    <string name=\"settings\">Seaded</string>\n    <string name=\"install\">Installi</string>\n    <string name=\"section_home\">Kodu</string>\n    <string name=\"section_theme\">Teemad</string>\n    <string name=\"denylist\">Keeluloend</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Ühendus puudub</string>\n    <string name=\"app_changelog\">Muudatuste logi</string>\n    <string name=\"loading\">Laadimine…</string>\n    <string name=\"update\">Uuenda</string>\n    <string name=\"not_available\">teadmata</string>\n    <string name=\"hide\">Peida</string>\n    <string name=\"home_package\">Pakett</string>\n    <string name=\"home_app_title\">Rakendus</string>\n\n    <string name=\"home_notice_content\">Laadi Magisk alla vaid ametlikult GitHubi lehelt. Tundmatutest allikatest laaditud failid võivad olla pahatahtlikud!</string>\n    <string name=\"home_support_title\">Toeta meid</string>\n    <string name=\"home_item_source\">Lähtekood</string>\n    <string name=\"home_support_content\">Magisk on ja jääb alati tasuta ning avatud lähtekoodiga kättesaadavaks. Siiski, sa võid meile väikese annetuse näol toetust üles näidata.</string>\n    <string name=\"home_installed_version\">Installitud</string>\n    <string name=\"home_latest_version\">Viimatine</string>\n    <string name=\"invalid_update_channel\">Sobimatu uuenduste kanal</string>\n    <string name=\"uninstall_magisk_title\">Eemalda Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Kõik moodulid keelatakse/eemaldatakse!\\nJuurkasutaja eemaldatakse!\\nMistahes sisemälu failid, millelt eemaldati Magiski tarbeks krüpteering, krüpteeritakse taas!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Säilita sunnitud krüpteering</string>\n    <string name=\"keep_dm_verity\">Säilita AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Taastusrežiim</string>\n    <string name=\"install_options_title\">Valikud</string>\n    <string name=\"install_method_title\">Meetod</string>\n    <string name=\"install_next\">Edasi</string>\n    <string name=\"install_start\">Läksime</string>\n    <string name=\"manager_download_install\">Vajuta allalaadimiseks ja installimiseks</string>\n    <string name=\"direct_install\">Otsene install (soovitatud)</string>\n    <string name=\"install_inactive_slot\">Installi ebaaktiivsesse lahtrisse (pärast üle-õhu uuendust)</string>\n    <string name=\"install_inactive_slot_msg\">Pärast taaskäivitust SUNNITAKSE sinu seade käivituma praegusesse ebaaktiivsesse lahtrisse!\\nKasuta seda valikut vaid peale üle-õhu uuenduse teostamist.\\nJätkad?</string>\n    <string name=\"setup_title\">Lisaseadistus</string>\n    <string name=\"select_patch_file\">Vali ja paika fail</string>\n    <string name=\"patch_file_msg\">Vali toortõmmis (*.img) või ODIN tar-fail (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Taaskäivitamine 5 sekundi pärast…</string>\n    <string name=\"flash_screen_title\">Installimine</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superkasutaja taotlus</string>\n    <string name=\"touch_filtered_warning\">Kuna rakendus varjab superkasutaja päringut, ei saa Magisk sinu vastust kinnitada</string>\n    <string name=\"deny\">Keela</string>\n    <string name=\"prompt\">Küsi</string>\n    <string name=\"grant\">Luba</string>\n    <string name=\"su_warning\">Annab täieliku ligipääsu sinu seadmele.\\nKui sa pole kindel, keela!</string>\n    <string name=\"forever\">Igavesti</string>\n    <string name=\"once\">Üks kord</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">Rakendusele %1$s anti superkasutaja õigused</string>\n    <string name=\"su_deny_toast\">Rakendusel %1$s keelati superkasutaja õigused</string>\n    <string name=\"su_snack_grant\">Superkasutaja õigused antud rakendusele %1$s</string>\n    <string name=\"su_snack_deny\">Superkasutaja õigused keelatud rakendusele %1$s</string>\n    <string name=\"su_snack_notif_on\">Teavitused lubatud rakendusele %1$s</string>\n    <string name=\"su_snack_notif_off\">Teavitused keelatud rakendusele %1$s</string>\n    <string name=\"su_snack_log_on\">Logimine lubatud rakendusele %1$s</string>\n    <string name=\"su_snack_log_off\">Logimine keelatud rakendusele %1$s</string>\n    <string name=\"su_revoke_title\">Eemaldad?</string>\n    <string name=\"su_revoke_msg\">Kinnitad rakenduse %1$s õiguste eemaldamise?</string>\n    <string name=\"toast\">Hüpik</string>\n    <string name=\"none\">Puudub</string>\n\n    <string name=\"superuser_toggle_notification\">Teavitused</string>\n    <string name=\"superuser_toggle_revoke\">Eemalda</string>\n    <string name=\"superuser_policy_none\">Ükski rakendus ei ole veel superkasutaja õigusi küsinud.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Sa oled logivaba, proovi oma superkasutaja õigustega rakendusi rohkem kasutada</string>\n    <string name=\"log_data_magisk_none\">Magiski logid on tühjad, see on imelik</string>\n    <string name=\"menuSaveLog\">Salvesta logi</string>\n    <string name=\"menuClearLog\">Tühjenda logi nüüd</string>\n    <string name=\"logs_cleared\">Logi edukalt tühjendatud.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Siht-UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Kuva uuendatavad süsteemirakendused</string>\n    <string name=\"show_os_app\">Kuva fikseeritud süsteemirakendused</string>\n    <string name=\"hide_filter_hint\">Filtreeri nime järgi</string>\n    <string name=\"hide_search\">Otsing</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Info puudub)</string>\n    <string name=\"reboot_userspace\">Pehme taaskäivitus</string>\n    <string name=\"reboot_recovery\">Taaskäivita taastusesse</string>\n    <string name=\"reboot_bootloader\">Taaskäivita käivitushaldurisse</string>\n    <string name=\"reboot_download\">Taaskäivita allalaadimisrežiimi</string>\n    <string name=\"reboot_edl\">Taaskäivita EDLi</string>\n    <string name=\"module_version_author\">%1$s autorilt %2$s</string>\n    <string name=\"module_state_remove\">Eemalda</string>\n    <string name=\"module_state_restore\">Taasta</string>\n    <string name=\"module_action_install_external\">Installi sisemälust</string>\n    <string name=\"update_available\">Uuendus saadaval</string>\n    <string name=\"suspend_text_riru\">Moodul keelatud %1$s lubamise tõttu</string>\n    <string name=\"suspend_text_zygisk\">Moodul keelatud %1$s mittelubamise tõttu</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk-moodulit ei laaditud ühildumatuse tõttu</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Teema režiim</string>\n    <string name=\"settings_dark_mode_message\">Vali režiim, mis ühtib sinu stiiliga!</string>\n    <string name=\"settings_dark_mode_light\">Alati hele</string>\n    <string name=\"settings_dark_mode_system\">Järgi süsteemi</string>\n    <string name=\"settings_dark_mode_dark\">Alati tume</string>\n    <string name=\"settings_download_path_title\">Allalaadimise failitee</string>\n    <string name=\"settings_download_path_message\">Failid salvestatakse kausta %1$s</string>\n    <string name=\"settings_hide_app_title\">Peida Magiski rakendus</string>\n    <string name=\"settings_hide_app_summary\">Paigalda juhusliku paketi-ID ja kohandatud nimega puhverrakendus</string>\n    <string name=\"settings_restore_app_title\">Taasta Magiski rakendus</string>\n    <string name=\"settings_restore_app_summary\">Too rakendus peidust välja, taastades originaalse APK</string>\n    <string name=\"language\">Keel</string>\n    <string name=\"system_default\">(Süsteemi vaikesäte)</string>\n    <string name=\"settings_check_update_title\">Kontrolli uuendusi</string>\n    <string name=\"settings_check_update_summary\">Kontrolli taustal perioodiliselt uuendusi</string>\n    <string name=\"settings_update_channel_title\">Uuenduste kanal</string>\n    <string name=\"settings_update_stable\">Stabiilne</string>\n    <string name=\"settings_update_beta\">Beeta</string>\n    <string name=\"settings_update_custom\">Kohandatud</string>\n    <string name=\"settings_update_custom_msg\">Sisesta kohandatud kanali URL</string>\n    <string name=\"settings_zygisk_summary\">Käivita teatud Magiski osi zygote-protsessis</string>\n    <string name=\"settings_denylist_title\">Jõusta keeluloend</string>\n    <string name=\"settings_denylist_summary\">Keeluloendis olevatel protsessidel tühistatakse kõik Magiski muudatused</string>\n    <string name=\"settings_denylist_config_title\">Seadista keeluloendit</string>\n    <string name=\"settings_denylist_config_summary\">Vali keeluloendisse lisatavad protsessid</string>\n    <string name=\"settings_hosts_title\">Süsteemivaba hosts</string>\n    <string name=\"settings_hosts_summary\">Süsteemivaba hosts-tugi reklaamiblokeerijatest rakendustele</string>\n    <string name=\"settings_hosts_toast\">Süsteemivaba hostsi moodul lisatud</string>\n    <string name=\"settings_app_name_hint\">Uus nimi</string>\n    <string name=\"settings_app_name_helper\">Rakendus taaspakitakse selle nimega</string>\n    <string name=\"settings_app_name_error\">Sobimatu vorming</string>\n    <string name=\"settings_su_app_adb\">Rakendused ja ADB</string>\n    <string name=\"settings_su_app\">Ainult rakendused</string>\n    <string name=\"settings_su_adb\">Ainult ADB</string>\n    <string name=\"settings_su_disable\">Keelatud</string>\n    <string name=\"settings_su_request_10\">10 sekundit</string>\n    <string name=\"settings_su_request_15\">15 sekundit</string>\n    <string name=\"settings_su_request_20\">20 sekundit</string>\n    <string name=\"settings_su_request_30\">30 sekundit</string>\n    <string name=\"settings_su_request_45\">45 sekundit</string>\n    <string name=\"settings_su_request_60\">60 sekundit</string>\n    <string name=\"superuser_access\">Superkasutaja ligipääs</string>\n    <string name=\"auto_response\">Automaatne vastus</string>\n    <string name=\"request_timeout\">Taotluse ajalõpp</string>\n    <string name=\"superuser_notification\">Superkasutaja teavitus</string>\n    <string name=\"settings_su_reauth_title\">Taas-autendi peale uuendust</string>\n    <string name=\"settings_su_reauth_summary\">Pärast rakenduste uuendamist küsi superkasutaja luba uuesti</string>\n    <string name=\"settings_su_tapjack_title\">Nupu varjamise kaitse</string>\n    <string name=\"settings_su_tapjack_summary\">Superkasutaja taotluse hüpik ei reageeri vajutusele, kui seda katab mõni teine aken või ülekate</string>\n    <string name=\"settings_customization\">Kohandamine</string>\n    <string name=\"setting_add_shortcut_summary\">Lisa avakuvale ilus otsetee juhuks, kui nime ja ikooni on pärast rakenduse peitmist raske tuvastada</string>\n    <string name=\"settings_doh_title\">DNS üle HTTPSi</string>\n    <string name=\"settings_doh_description\">Väldi mõnes riigis DNSi mürgitamist</string>\n\n    <string name=\"multiuser_mode\">Mitmikkasutaja režiim</string>\n    <string name=\"settings_owner_only\">Ainult seadme omanik</string>\n    <string name=\"settings_owner_manage\">Seadme omaniku hallatud</string>\n    <string name=\"settings_user_independent\">Kasutajast sõltumatu</string>\n    <string name=\"owner_only_summary\">Ainult omanikul on juurkasutaja õigused</string>\n    <string name=\"owner_manage_summary\">Ainult omanik saab hallata juurkasutaja ligipääsu ja saada taotlusküsimusi</string>\n    <string name=\"user_independent_summary\">Igal kasutajal on oma isiklikud juurkasutaja reeglid.</string>\n\n    <string name=\"mount_namespace_mode\">Nimeruumi monteerimisrežiim</string>\n    <string name=\"settings_ns_global\">Globaalne nimeruum</string>\n    <string name=\"settings_ns_requester\">Võta nimeruum üle</string>\n    <string name=\"settings_ns_isolate\">Isoleeritud nimeruum</string>\n    <string name=\"global_summary\">Kõik juurkasutaja sessioonid kasutavad globaalset monteerimise nimeruumi</string>\n    <string name=\"requester_summary\">Juurkasutaja sessioonid võtavad üle selle taotleja nimeruumi</string>\n    <string name=\"isolate_summary\">Iga juurkasutaja sessioon saab oma isoleeritud nimeruumi</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magiski uuendused</string>\n    <string name=\"progress_channel\">Edenemise teated</string>\n    <string name=\"download_complete\">Allalaadimine valmis</string>\n    <string name=\"download_file_error\">Faili allalaadimisel esines viga</string>\n    <string name=\"magisk_update_title\">Magiski uuendus on saadaval!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Jah</string>\n    <string name=\"no\">Ei</string>\n    <string name=\"repo_install_title\">Installi %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Allalaadimine</string>\n    <string name=\"reboot\">Taaskäivita</string>\n    <string name=\"release_notes\">Väljalaskemärkmed</string>\n    <string name=\"flashing\">Välgutamine…</string>\n    <string name=\"done\">Valmis!</string>\n    <string name=\"failure\">Ebaõnnestus</string>\n    <string name=\"hide_app_title\">Magiski rakenduse peitmine…</string>\n    <string name=\"open_link_failed_toast\">Lingi avamiseks sobivat rakendust ei leitud.</string>\n    <string name=\"complete_uninstall\">Täielik eemaldus</string>\n    <string name=\"restore_img\">Taasta tõmmised</string>\n    <string name=\"restore_img_msg\">Taastamine…</string>\n    <string name=\"restore_done\">Taastamine valmis!</string>\n    <string name=\"restore_fail\">Originaalne varundus puudub!</string>\n    <string name=\"setup_fail\">Seadistus ebaõnnnestus</string>\n    <string name=\"env_fix_title\">Vajab lisaseadistust</string>\n    <string name=\"env_fix_msg\">Sinu seade vajab Magiski korralikuks toimimiseks lisaseadistust. Kas soovid jätkata ning seadme taaskäivitada?</string>\n    <string name=\"setup_msg\">Käivitan keskkonnaseadistust…</string>\n    <string name=\"unsupport_magisk_title\">Mittetoetatud Magiski versioon</string>\n    <string name=\"unsupport_magisk_msg\">See rakenduse versioon ei toeta Magiski versioone, mis on vanemad kui %1$s.\\n\\nRakendus käitub nii, nagu Magisk ei olekski installitud, palun täienda Magiskit esimesel võimalusel.</string>\n    <string name=\"unsupport_general_title\">Ebanormaalne seisund</string>\n    <string name=\"unsupport_system_app_msg\">Selle rakenduse käitamine süsteemirakendusena ei ole toetatud. Palun taasta see kasutajarakenduseks.</string>\n    <string name=\"unsupport_other_su_msg\">Tuvastati \\\"su\\\"-binaar, mida ei installinud Magisk. Palun eemalda mistahes konkureeriv juurkasutaja paigaldus ja/või taasinstalli Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk on paigaldatud välismällu. Palun liiguta rakendus sisemällu.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Peidetud Magiski rakendus ei saa enam töötada, kuna juurkasutaja on kadunud. Palun taasta originaalne APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Selle funktsionaalsuse lubamiseks anna mäluruumi haldamise luba</string>\n    <string name=\"add_shortcut_title\">Lisa avakuvale otsetee</string>\n    <string name=\"add_shortcut_msg\">Pärast selle rakenduse peitmist võib selle nimi ja ikoon olla raskesti tuvastatav. Kas soovid avakuvale lisada ilusa otsetee?</string>\n    <string name=\"app_not_found\">Selle tegevuse teostamiseks ei leitud ühtegi rakendust</string>\n    <string name=\"reboot_apply_change\">Muudatuste rakendamiseks taaskäivita</string>\n    <string name=\"restore_app_confirmation\">See taastab peidetud rakenduse uuesti originaalseks rakenduseks. Kas soovid tõesti seda teha?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-fa/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">ماژول ها</string>\n    <string name=\"superuser\">کاربر روت</string>\n    <string name=\"logs\">لاگ ها</string>\n    <string name=\"settings\">تنظیمات</string>\n    <string name=\"install\">نصب</string>\n    <string name=\"section_home\">خانه</string>\n    <string name=\"section_theme\">تم ها</string>\n    <string name=\"denylist\">لیست منع</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">هیچ اتصالی وجود ندارد</string>\n    <string name=\"app_changelog\">تغییرات</string>\n    <string name=\"loading\">در حال بارگذاری…</string>\n    <string name=\"update\">بروزرسانی کردن</string>\n    <string name=\"not_available\">غیر/قابل دسترسی</string>\n    <string name=\"hide\">پنهان کردن</string>\n    <string name=\"home_package\">پکیج</string>\n    <string name=\"home_app_title\">برنامه</string>\n    <string name=\"home_notice_content\">Magisk را فقط از صفحه رسمی GitHub دانلود کنید. فایل‌ها از منابع ناشناس می‌توانند مخرب باشند!</string>\n    <string name=\"home_follow_title\">ما را دنبال کنید</string>\n    <string name=\"home_support_title\">حمایت ما</string>\n    <string name=\"home_item_source\">منبع</string>\n    <string name=\"home_support_content\">این برنامه (Magisk) رایگان و متن باز است و همیشه خواهد ماند. اگرچه شما میتواند با دونیت خود نشان دهد که به ما اهمیت می دهید.</string>\n    <string name=\"home_installed_version\">نصب شده</string>\n    <string name=\"home_latest_version\">آخرین نسخه</string>\n    <string name=\"invalid_update_channel\">کانال بروزرسانی نامعتبر است</string>\n    <string name=\"uninstall_magisk_title\">حذف Magisk</string>\n    <string name=\"uninstall_magisk_msg\">همه ماژول ها غیرفعال/حذف می شوند!\\nروت حذف می شود!\\nداده های شما اگر از قبل رمزگذارری نشده بودند رمز نگاری میشوند!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">حفظ رمزگذاری اجباری</string>\n    <string name=\"keep_dm_verity\">حفظ AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">حالت ریکاوری</string>\n    <string name=\"install_options_title\">گزینه ها</string>\n    <string name=\"install_method_title\">روش</string>\n    <string name=\"install_next\">بعدی</string>\n    <string name=\"install_start\">بزن بریم</string>\n    <string name=\"manager_download_install\">برای دانلود و نصب فشار دهید</string>\n    <string name=\"direct_install\">نصب مستقیم (توصیه می شود)</string>\n    <string name=\"install_inactive_slot\">نصب در حافظه غیر فعال (بعد OTA)</string>\n    <string name=\"install_inactive_slot_msg\">بعد از راه اندازی مجدد دستگاه شما مجبور به راه اندازی در حافظه غیرفعال فعلی می شود! \\nفقط پس از انجام OTA از این گزینه استفاده کنید. \\nادامه می دهید؟</string>\n    <string name=\"setup_title\">تنظیمات اضافی</string>\n    <string name=\"select_patch_file\">یک فایل را انتخاب و پچ کنید</string>\n    <string name=\"patch_file_msg\">یک تصویر خام (*.img) یا یک فایل (ODIN (*.tar انتخاب کنید</string>\n    <string name=\"reboot_delay_toast\">راه اندازی مجدد در 5 ثانیه…</string>\n    <string name=\"flash_screen_title\">نصب و راه اندازی</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">درخواست کاربر روت</string>\n    <string name=\"touch_filtered_warning\">به دلیل اینکه یک برنامه در حال پوشاندن درخواست Superuser است، Magisk نمی‌تواند پاسخ شما را تأیید کند.</string>\n    <string name=\"deny\">رد کردن</string>\n    <string name=\"prompt\">درخواست کردن</string>\n    <string name=\"restrict\">محدود کردن</string>\n    <string name=\"grant\">اجازه دادن</string>\n    <string name=\"su_warning\">دسترسی کامل به دستگاه شما را اعطا می کند. \\nاگر مطمئن نیستید رد کنید!</string>\n    <string name=\"forever\">همیشه</string>\n    <string name=\"once\">یکبار</string>\n    <string name=\"tenmin\">10 دقیقه</string>\n    <string name=\"twentymin\">20 دقیقه</string>\n    <string name=\"thirtymin\">30 دقیقه</string>\n    <string name=\"sixtymin\">60 دقیقه</string>\n    <string name=\"su_allow_toast\">%1$s اجازه دسترسی به روت داده شد</string>\n    <string name=\"su_deny_toast\">%1$s اجازه دسترسی به روت رد شد</string>\n    <string name=\"su_snack_grant\">دسترسی روت به %1$s داده شده است</string>\n    <string name=\"su_snack_deny\">دسترسی روت به %1$s داده نشده است</string>\n    <string name=\"su_snack_notif_on\">اعلان های %1$s فعال است</string>\n    <string name=\"su_snack_notif_off\">اعلان های %1$s غییر فعال است</string>\n    <string name=\"su_snack_log_on\">ورود به سیستم از %1$s فعال است</string>\n    <string name=\"su_snack_log_off\">ورود به سیستم از %1$s غییر فعال است</string>\n    <string name=\"su_revoke_title\">باطل بشه؟</string>\n    <string name=\"su_revoke_msg\">تایید کنید که %1$s باطل بشه؟</string>\n    <string name=\"toast\">پیام کوتاه</string>\n    <string name=\"none\">هیچ کدام</string>\n    <string name=\"superuser_toggle_notification\">اعلان ها</string>\n    <string name=\"superuser_toggle_revoke\">ابطال</string>\n    <string name=\"superuser_policy_none\">هنوز هیچ برنامه ای مجوز روت درخواست نکرده است.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">شما هیچ لاگی ندارید, سعی کنید برنامه های که به روت دسترسی میگیرند را استفاده کنید.</string>\n    <string name=\"log_data_magisk_none\">لاگ های مربوط به Magisk خالی است.</string>\n    <string name=\"menuSaveLog\">ذخیره کردن لاگ</string>\n    <string name=\"menuClearLog\">پاک کردن لاگ</string>\n    <string name=\"logs_cleared\">لاگ با موفقیت پاک شد.</string>\n    <string name=\"pid\">شناسه پردازش: %1$d</string>\n    <string name=\"target_uid\">شناسه کاربر هدف: %1$d</string>\n    <string name=\"target_pid\">شناسه پردازش هدف: %s</string>\n    <string name=\"selinux_context\">متن SELinux: %s</string>\n    <string name=\"supp_group\">گروه تکمیلی: %s</string>\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">نشان دادن برنامه های سیستمی</string>\n    <string name=\"show_os_app\">نمایش برنامه‌های سیستم عامل</string>\n    <string name=\"hide_filter_hint\">فیلتر کردن با نام</string>\n    <string name=\"hide_search\">سرچ کردن</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(هیچ اطلاعاتی ارائه نشده است)</string>\n    <string name=\"reboot_userspace\">راه اندازی مججد</string>\n    <string name=\"reboot_recovery\">راه اندازی مججد برای رفتن به ریکاوری</string>\n    <string name=\"reboot_bootloader\">راه اندازی مججد برای رفتن به بوت لودر</string>\n    <string name=\"reboot_download\">راه اندازی مججد برای دانلود کردن</string>\n    <string name=\"reboot_edl\">راه اندازی مججد برای رفتن به EDL</string>\n    <string name=\"reboot_safe_mode\">راه اندازی مججد برای رفتن به حالت امن</string>\n    <string name=\"module_version_author\">%1$s با %2$s</string>\n    <string name=\"module_state_remove\">حذف کردن</string>\n    <string name=\"module_state_restore\">بازگرداندن</string>\n    <string name=\"module_action_install_external\">نصب از حافظه</string>\n    <string name=\"update_available\">بروزرسانی در دسترس است</string>\n    <string name=\"suspend_text_riru\">ماژول به دلیل فعال بودن %1$s متوقف شد</string>\n    <string name=\"suspend_text_zygisk\">ماژول به دلیل غیرفعال بودن %1$s متوقف شد</string>\n    <string name=\"zygisk_module_unloaded\">ماژول Zygisk به دلیل ناسازگاری بارگذاری نشد</string>\n    <string name=\"module_empty\">هیچ ماژولی نصب نشده است</string>\n    <string name=\"confirm_install\">نصب ماژول %1$s؟</string>\n    <string name=\"confirm_install_title\">تأیید نصب</string>\n\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">حالت تم</string>\n    <string name=\"settings_dark_mode_message\">حالت متناسب با سبک زندگی خود را انتخاب کنید!</string>\n    <string name=\"settings_dark_mode_light\">همیشه روشن</string>\n    <string name=\"settings_dark_mode_system\">با توجه به سیستم</string>\n    <string name=\"settings_dark_mode_dark\">همیشه تاریک</string>\n    <string name=\"settings_download_path_title\">مسیر دانلود</string>\n    <string name=\"settings_download_path_message\">فایل ها در %1$s ذخیره خواهند شد.</string>\n    <string name=\"settings_hide_app_title\">مخفی کردن برنامه Magisk</string>\n    <string name=\"settings_hide_app_summary\">نصب یک برنامه پروکسی با شناسه بسته تصادفی و برچسب سفارشی</string>\n    <string name=\"settings_restore_app_title\">بازگردانی برنامه Magisk</string>\n    <string name=\"settings_restore_app_summary\">آشکار کردن برنامه و بازگرداندن APK اصلی</string>\n    <string name=\"language\">زبان</string>\n    <string name=\"system_default\">(پیش فرض سیستم)</string>\n    <string name=\"settings_check_update_title\">چک کردن بروز رسانی ها</string>\n    <string name=\"settings_check_update_summary\"> برسی کردن به صورت دوره ای برای به روزرسانی در پس زمینه</string>\n    <string name=\"settings_update_channel_title\">کانال بروزرسانی</string>\n    <string name=\"settings_update_stable\">پایدار</string>\n    <string name=\"settings_update_beta\">آزمایشی</string>\n    <string name=\"settings_update_debug\">اشکال‌زدایی</string>\n    <string name=\"settings_update_custom\">شخصی سازی شده</string>\n    <string name=\"settings_update_custom_msg\">اضافه کردن یک URL سفارشی</string>\n    <string name=\"settings_zygisk_summary\">اجرای بخش‌هایی از Magisk در سرویس Zygote</string>\n    <string name=\"settings_denylist_title\">اعمال لیست منع</string>\n    <string name=\"settings_denylist_summary\">فرآیندهای موجود در لیست منع تمام تغییرات Magisk را از دست خواهند داد</string>\n    <string name=\"settings_denylist_config_title\">پیکربندی لیست منع</string>\n    <string name=\"settings_denylist_config_summary\">انتخاب فرآیندهایی که باید در لیست منع قرار گیرند</string>\n    <string name=\"settings_hosts_title\">نصب بدون حذف یا تغییر در فایل ها</string>\n    <string name=\"settings_hosts_summary\">نصب بدون حذف یا تغییر در فایل ها رای ساپورت از برنامه های Adblock</string>\n    <string name=\"settings_hosts_toast\">ماژول نصب بدون حذف یا تغییر در فایل ها اضافه شد</string>\n    <string name=\"settings_app_name_hint\">اسم جدید</string>\n    <string name=\"settings_app_name_helper\">برنامه به این نام تغییر خواهد کرد</string>\n    <string name=\"settings_app_name_error\">فرمت نامعتبر</string>\n    <string name=\"settings_su_app_adb\">برنامه ها و ADB</string>\n    <string name=\"settings_su_app\">فقط برنامه ها</string>\n    <string name=\"settings_su_adb\">ADB فقط</string>\n    <string name=\"settings_su_disable\">غییرفعال شده</string>\n    <string name=\"settings_su_request_10\">10 ثانیه</string>\n    <string name=\"settings_su_request_15\">15 ثانیه</string>\n    <string name=\"settings_su_request_20\">20 ثانیه</string>\n    <string name=\"settings_su_request_30\">30 ثانیه</string>\n    <string name=\"settings_su_request_45\">45 ثانیه</string>\n    <string name=\"settings_su_request_60\">60 ثانیه</string>\n    <string name=\"superuser_access\">دسترسی روت</string>\n    <string name=\"auto_response\">پاسخ خودکار</string>\n    <string name=\"request_timeout\">مهلت زمانی درخواست</string>\n    <string name=\"superuser_notification\">اعلان روت</string>\n    <string name=\"settings_su_reauth_title\">احراز هویت دوباره پس از بروز رسانی</string>\n    <string name=\"settings_su_reauth_summary\">تأیید کردندوباره مجوزهای روت پس از ارتقاء برنامه</string>\n    <string name=\"settings_su_tapjack_title\">محافظت در برابر Tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">پنجره درخواست Superuser زمانی که توسط پنجره یا لایه دیگری پوشانده شود، به ورودی پاسخ نخواهد داد</string>\n    <string name=\"settings_su_auth_title\">احراز هویت کاربر</string>\n    <string name=\"settings_su_auth_summary\">درخواست احراز هویت کاربر هنگام درخواست Superuser</string>\n    <string name=\"settings_su_auth_insecure\">هیچ روش احراز هویتی روی دستگاه پیکربندی نشده است</string>\n    <string name=\"settings_su_restrict_title\">محدود کردن دسترسی روت</string>\n    <string name=\"settings_su_restrict_summary\">به طور پیش‌فرض برنامه‌های Superuser جدید را محدود می‌کند. هشدار: این کار بیشتر برنامه‌ها را از کار می‌اندازد. فقط اگر دقیقاً می‌دانید چه می‌کنید آن را فعال کنید.</string>\n    <string name=\"settings_customization\">سفارشی سازی</string>\n    <string name=\"setting_add_shortcut_summary\">اضافه کردن یک میانبر زیبا را در صفحه اصلی در صورت شناسایی نام و نماد پس از پنهان کردن برنامه</string>\n    <string name=\"settings_doh_title\">DNS روی HTTPS</string>\n    <string name=\"settings_doh_description\">دور زدن مسمومیت DNS در برخی کشورها</string>\n    <string name=\"settings_random_name_title\">تغییر تصادفی نام خروجی</string>\n    <string name=\"settings_random_name_description\">تغییر تصادفی نام فایل خروجی تصاویر و فایل‌های tar پچ‌شده برای جلوگیری از شناسایی</string>\n    <string name=\"multiuser_mode\">حالت چند کاربره</string>\n    <string name=\"settings_owner_only\">فقط صاحب دستگاه</string>\n    <string name=\"settings_owner_manage\">صاحب دستگاه مدیریت شود</string>\n    <string name=\"settings_user_independent\">مستقل از کاربر</string>\n    <string name=\"owner_only_summary\">فقط مالک دسترسی روت دارد</string>\n    <string name=\"owner_manage_summary\">فقط مالک می تواند دسترسی روت را مدیریت کرده و درخواست های پرامپت را دریافت کند</string>\n    <string name=\"user_independent_summary\">هر کاربر قوانین روت جداگانه خود را دارد</string>\n    <string name=\"mount_namespace_mode\">نصب کردن Namespace Mode</string>\n    <string name=\"settings_ns_global\">سراسری Namespace</string>\n    <string name=\"settings_ns_requester\">وراثتی Namespace</string>\n    <string name=\"settings_ns_isolate\">منزوی Namespace</string>\n    <string name=\"global_summary\">همه سشن های روت از global namespace نصب شده استفاده میکنند</string>\n    <string name=\"requester_summary\">سشن های روت namespace دراخواست کننده را به ارث می برند</string>\n    <string name=\"isolate_summary\">هر سشن روت namespace جداگانه خود را دارد</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk بروزرسانی های</string>\n    <string name=\"progress_channel\">اعلان پیشرفت</string>\n    <string name=\"updated_channel\">به‌روزرسانی کامل شد</string>\n    <string name=\"download_complete\">دانلود کامل شد</string>\n    <string name=\"download_file_error\">خطا در دانلود فایل</string>\n    <string name=\"magisk_update_title\">بروزرسانی Magisk در دسترس است!</string>\n    <string name=\"updated_title\">Magisk به‌روزرسانی شد</string>\n    <string name=\"updated_text\">برای باز کردن برنامه لمس کنید</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">بله</string>\n    <string name=\"no\">نه</string>\n    <string name=\"repo_install_title\">نصب %1$s %2$s(%3$d)</string>\n    <string name=\"download\">دانلود کردن</string>\n    <string name=\"reboot\">راه اندازی مجدد</string>\n    <string name=\"close\">بستن</string>\n    <string name=\"release_notes\">نکته های نسخه</string>\n    <string name=\"flashing\">ر حال فلش کردن…</string>\n    <string name=\"running\">در حال اجرا…</string>\n    <string name=\"done\">تمام!</string>\n    <string name=\"done_action\">انجام عملیات %1$s به پایان رسید</string>\n    <string name=\"failure\">ناموفق</string>\n    <string name=\"hide_app_title\">در حال مخفی کردن برنامه Magisk…</string>\n    <string name=\"open_link_failed_toast\">هیچ برنامه ای برای باز کردن لینک یافت نشد</string>\n    <string name=\"complete_uninstall\">کامل کردن حذف</string>\n    <string name=\"restore_img\">بازیابی تصاویر</string>\n    <string name=\"restore_img_msg\">درحال بازیابی…</string>\n    <string name=\"restore_done\">بازیابی انجام شد!</string>\n    <string name=\"restore_fail\">نسخه پشتیبان استک موجود نیست!</string>\n    <string name=\"setup_fail\">نصب انجام نشد</string>\n    <string name=\"env_fix_title\">به تنظیمات اضافی نیاز دارد</string>\n    <string name=\"env_fix_msg\">دستگاه شما به پیکربندی اضافی نیاز دارد تا Magisk به درستی کار کند. آیا می‌خواهید ادامه دهید و راه‌اندازی مجدد انجام شود؟</string>\n    <string name=\"env_full_fix_msg\">دستگاه شما نیاز به نصب دوباره Magisk دارد تا به درستی کار کند. لطفاً Magisk را از داخل برنامه دوباره نصب کنید، حالت Recovery نمی‌تواند اطلاعات دستگاه را به درستی بگیرد.</string>\n    <string name=\"setup_msg\">راه اندازی محیط نصب…</string>\n    <string name=\"unsupport_magisk_title\">نسخه پشتیبانی نشده Magisk</string>\n    <string name=\"unsupport_magisk_msg\">این نسخه از برنامه از نسخه‌های Magisk پایین‌تر از %1$s پشتیبانی نمی‌کند.\\n\\nبرنامه طوری رفتار می‌کند که انگار Magisk نصب نشده است. لطفاً هرچه سریع‌تر Magisk را به‌روزرسانی کنید.</string>\n    <string name=\"unsupport_general_title\">وضعیت غیرعادی</string>\n    <string name=\"unsupport_system_app_msg\">اجرای این برنامه به عنوان برنامه سیستمی پشتیبانی نمی‌شود. لطفاً آن را به برنامه کاربری بازگردانید.</string>\n    <string name=\"unsupport_other_su_msg\">یک باینری \"su\" غیر از Magisk شناسایی شد. لطفاً هر راهکار روت دیگری را حذف کنید و/یا Magisk را دوباره نصب کنید.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk روی حافظه خارجی نصب شده است. لطفاً برنامه را به حافظه داخلی منتقل کنید.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">برنامه مخفی Magisk نمی‌تواند ادامه دهد زیرا دسترسی روت از بین رفته است. لطفاً APK اصلی را بازگردانید.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">برای فعال کردن این قابلیت ، اجازه دسترسی به حافظه بدهید</string>\n    <string name=\"post_notifications_denied\">برای فعال‌سازی این قابلیت، مجوز اعلان‌ها را بدهید</string>\n    <string name=\"install_unknown_denied\">برای فعال‌سازی این قابلیت، «نصب برنامه‌های ناشناخته» را مجاز کنید</string>\n    <string name=\"add_shortcut_title\">اضافه کردن میانبر را به صفحه</string>\n    <string name=\"add_shortcut_msg\">بعد از مخفی کردن این برنامه، ممکن است نام و آیکون آن سخت قابل شناسایی شود. آیا می‌خواهید یک میانبر زیبا به صفحه اصلی اضافه کنید؟</string>\n    <string name=\"app_not_found\">هیچ برنامه‌ای برای انجام این عملیات یافت نشد</string>\n    <string name=\"reboot_apply_change\">برای اعمال تغییرات، دستگاه را دوباره راه‌اندازی کنید</string>\n    <string name=\"restore_app_confirmation\">این کار برنامه مخفی شده را به نسخه اصلی بازمی‌گرداند. آیا واقعاً می‌خواهید این کار را انجام دهید؟</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-fr/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modules</string>\n    <string name=\"superuser\">Super‑utilisateur</string>\n    <string name=\"logs\">Journaux</string>\n    <string name=\"settings\">Paramètres</string>\n    <string name=\"install\">Installer</string>\n    <string name=\"section_home\">Accueil</string>\n    <string name=\"section_theme\">Thèmes</string>\n    <string name=\"denylist\">Liste Refus</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Aucune connexion disponible</string>\n    <string name=\"app_changelog\">Journal des modifications</string>\n    <string name=\"loading\">Chargement…</string>\n    <string name=\"update\">Mettre à jour</string>\n    <string name=\"not_available\">Non disponible</string>\n    <string name=\"hide\">Masquer</string>\n    <string name=\"home_package\">Paquet</string>\n    <string name=\"home_app_title\">Application</string>\n\n    <string name=\"home_notice_content\">Ne téléchargez Magisk qu’uniquement depuis la page GitHub officielle. Les fichiers provenant de sources inconnues peuvent être malveillants !</string>\n    <string name=\"home_support_title\">Soutenez‑nous</string>\n    <string name=\"home_follow_title\">Suivez-nous</string>\n    <string name=\"home_item_source\">Sources</string>\n    <string name=\"home_support_content\">Magisk est, et sera toujours, libre et open source. Vous pouvez cependant nous témoigner de votre soutien en envoyant un petit don.</string>\n    <string name=\"home_installed_version\">Version installée</string>\n    <string name=\"home_latest_version\">Dernière version</string>\n    <string name=\"invalid_update_channel\">Canal de mise à jour invalide</string>\n    <string name=\"uninstall_magisk_title\">Désinstaller Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Tous les modules seront désactivés ou supprimés !\\nL’accès super‑utiliateur sera perdu !\\nVos données seront potentiellement chiffrées si elles ne le sont pas déjà.</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Conserver le chiffrement forcé</string>\n    <string name=\"keep_dm_verity\">Conserver AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mode récupération</string>\n    <string name=\"install_options_title\">Options</string>\n    <string name=\"install_method_title\">Méthode</string>\n    <string name=\"install_next\">Suivant</string>\n    <string name=\"install_start\">C’est parti</string>\n    <string name=\"manager_download_install\">Appuyez pour le télécharger et l’installer</string>\n    <string name=\"direct_install\">Installation directe (recommandée)</string>\n    <string name=\"install_inactive_slot\">Installer dans l’espace inactif (après mise à jour OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Votre appareil sera obligatoirement réamorcé à partir de l’espace (slot) actuellement inactif après son redémarrage !\\nN’utilisez uniquement cette option qu’après que la mise à jour OTA a été effectuée.\\nVoulez‑vous continuer ?</string>\n    <string name=\"setup_title\">Configuration supplémentaire</string>\n    <string name=\"select_patch_file\">Sélectionnez le fichier cible du correctif</string>\n    <string name=\"patch_file_msg\">Sélectionnez une image brute (*.img) ou une archive TAR ODIN (*.tar) ou un fichier payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Redémarrage dans 5 secondes…</string>\n    <string name=\"flash_screen_title\">Installation</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Demande d’accès super‑utilisateur</string>\n    <string name=\"touch_filtered_warning\">Magisk ne peut vérifier votre réponse parce qu’une application masque une requête de super‑utilisateur</string>\n    <string name=\"deny\">Refuser</string>\n    <string name=\"prompt\">Demander</string>\n    <string name=\"grant\">Accorder</string>\n    <string name=\"su_warning\">Autoriser l’accès complet à votre appareil.\\nRefusez en cas de doute !</string>\n    <string name=\"forever\">Toujours</string>\n    <string name=\"once\">Une fois</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s a obtenu les droits de super‑utilisateur</string>\n    <string name=\"su_deny_toast\">%1$s s’est vu refuser les droits de super‑utilisateur</string>\n    <string name=\"su_snack_grant\">Les droits de super‑utilisateur sont accordés à %1$s</string>\n    <string name=\"su_snack_deny\">Les droits de super‑utilisateur sont refusés à %1$s</string>\n    <string name=\"su_snack_notif_on\">Les notifications sont activées pour %1$s</string>\n    <string name=\"su_snack_notif_off\">Les notifications sont désactivées pour %1$s</string>\n    <string name=\"su_snack_log_on\">La journalisation est activée pour %1$s</string>\n    <string name=\"su_snack_log_off\">La journalisation est désactivée pour %1$s</string>\n    <string name=\"su_revoke_title\">Révoquer ?</string>\n    <string name=\"su_revoke_msg\">Confirmez‑vous la révocation des droits accordés à %1$s ?</string>\n    <string name=\"toast\">Bulle de notification</string>\n    <string name=\"none\">Aucune</string>\n\n    <string name=\"superuser_toggle_notification\">Notifications</string>\n    <string name=\"superuser_toggle_revoke\">Révoquer</string>\n    <string name=\"superuser_policy_none\">Aucune application n’a encore demandé de droits super‑utilisateur.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Aucune information n’est journalisée, essayez d’utiliser davantage vos applications sollicitant des droits de super‑utilisateur.</string>\n    <string name=\"log_data_magisk_none\">Les journaux de Magisk sont vides, c’est étrange.</string>\n    <string name=\"menuSaveLog\">Enregistrer le journal</string>\n    <string name=\"menuClearLog\">Effacer le journal maintenant</string>\n    <string name=\"logs_cleared\">Le journal a été effacé.</string>\n    <string name=\"pid\">PID : %1$d</string>\n    <string name=\"target_uid\">UID cible : %1$d</string>\n    <string name=\"target_pid\">Montage ns PID cible: %s</string>\n    <string name=\"selinux_context\">Contexte SELinux: %s</string>\n    <string name=\"supp_group\">Groupe supplémentaire: %s</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Afficher les applications système</string>\n    <string name=\"show_os_app\">Afficher les applications intégrées au système</string>\n    <string name=\"hide_filter_hint\">Filtrer par nom</string>\n    <string name=\"hide_search\">Rechercher</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(aucune info transmise)</string>\n    <string name=\"reboot_userspace\">Redémarrage à chaud</string>\n    <string name=\"reboot_recovery\">Redémarrer en mode récupération</string>\n    <string name=\"reboot_bootloader\">Redémarrer en mode amorçage</string>\n    <string name=\"reboot_download\">Redémarrer en mode téléchargement</string>\n    <string name=\"reboot_edl\">Redémarrer en mode secours (EDL)</string>\n    <string name=\"reboot_safe_mode\">Mode sans échec</string>\n    <string name=\"module_version_author\">%1$s, par %2$s</string>\n    <string name=\"module_state_remove\">Supprimer</string>\n    <string name=\"module_state_restore\">Restaurer</string>\n    <string name=\"module_action_install_external\">Installer depuis le stockage</string>\n    <string name=\"update_available\">Mise à jour disponible</string>\n    <string name=\"suspend_text_riru\">Module suspendu car %1$s est actif</string>\n    <string name=\"suspend_text_zygisk\">Module suspendu car %1$s n’est pas actif</string>\n    <string name=\"zygisk_module_unloaded\">Module Zygisk non chargé en raison d’une incompatibilité</string>\n    <string name=\"module_empty\">Aucun module installé</string>\n    <string name=\"confirm_install\">Installer le module %1$s?</string>\n    <string name=\"confirm_install_title\">Confirmer l’installation</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">Mode du thème</string>\n    <string name=\"settings_dark_mode_message\">Choisissez le mode qui correspond le mieux à votre style !</string>\n    <string name=\"settings_dark_mode_light\">Toujours clair</string>\n    <string name=\"settings_dark_mode_system\">Identique au système</string>\n    <string name=\"settings_dark_mode_dark\">Toujours sombre</string>\n    <string name=\"settings_download_path_title\">Répertoire de téléchargement</string>\n    <string name=\"settings_download_path_message\">Les fichiers seront enregistrés sous %1$s</string>\n    <string name=\"settings_hide_app_title\">Masquer l’application Magisk</string>\n    <string name=\"settings_hide_app_summary\">Installer une application intermédiaire avec un identifiant de paquet aléatoire et un nom personnalisé</string>\n    <string name=\"settings_restore_app_title\">Restaurer l’application Magisk</string>\n    <string name=\"settings_restore_app_summary\">Démasquer l’application et la restaurer comme son APK d’origine.</string>\n    <string name=\"language\">Langue</string>\n    <string name=\"system_default\">(langue par défaut du système)</string>\n    <string name=\"settings_check_update_title\">Vérifier les mises à jour</string>\n    <string name=\"settings_check_update_summary\">Vérifier périodiquement en tâche de fond l’existence d’une mise à jour.</string>\n    <string name=\"settings_update_channel_title\">Canal de mise à jour</string>\n    <string name=\"settings_update_stable\">Stable</string>\n    <string name=\"settings_update_beta\">Bêta</string>\n    <string name=\"settings_update_custom\">Canal personnalisé</string>\n    <string name=\"settings_update_custom_msg\">Saisissez une URL personnalisée</string>\n    <string name=\"settings_zygisk_summary\">Exécuter des parties de Magisk dans le service zygote</string>\n    <string name=\"settings_denylist_title\">Imposer Liste Exclusion</string>\n    <string name=\"settings_denylist_summary\">Les processus figurant sur la liste des exclusions verront toutes les modifications de Magisk annulées.</string>\n    <string name=\"settings_denylist_config_title\">Configurer Liste Exclusion</string>\n    <string name=\"settings_denylist_config_summary\">Sélectionner le processus à inclure dans la liste d’exclusion</string>\n    <string name=\"settings_hosts_title\">Fichier d’hôtes hors partition système</string>\n    <string name=\"settings_hosts_summary\">Utilisation d’un fichier d’hôtes hors de la partition système pour les applications de blocage de publicité.</string>\n    <string name=\"settings_hosts_toast\">Ajout d’un module pour fichier hosts hors système</string>\n    <string name=\"settings_app_name_hint\">Nouveau nom</string>\n    <string name=\"settings_app_name_helper\">L’application sera réempaquetée sous ce nom</string>\n    <string name=\"settings_app_name_error\">Format incorrect</string>\n    <string name=\"settings_su_app_adb\">Applications et ADB</string>\n    <string name=\"settings_su_app\">Applications uniquement</string>\n    <string name=\"settings_su_adb\">ADB uniquement</string>\n    <string name=\"settings_su_disable\">Désactivé</string>\n    <string name=\"settings_su_request_10\">10 secondes</string>\n    <string name=\"settings_su_request_15\">15 secondes</string>\n    <string name=\"settings_su_request_20\">20 secondes</string>\n    <string name=\"settings_su_request_30\">30 secondes</string>\n    <string name=\"settings_su_request_45\">45 secondes</string>\n    <string name=\"settings_su_request_60\">60 secondes</string>\n    <string name=\"superuser_access\">Accès super‑utilisateur</string>\n    <string name=\"auto_response\">Réponse automatique</string>\n    <string name=\"request_timeout\">Délai d’expiration de la demande</string>\n    <string name=\"superuser_notification\">Notification super‑utilisateur</string>\n    <string name=\"settings_su_reauth_title\">S’authentifier à nouveau après la mise à niveau</string>\n    <string name=\"settings_su_reauth_summary\">Redemander une authentification pour autoriser l’accès en super‑utilisateur après une mise à jour de l’application</string>\n    <string name=\"settings_su_tapjack_title\">Activer la protection contre le détournement du tapotement</string>\n    <string name=\"settings_su_tapjack_summary\">Le dialogue d’invite du super‑utilisateur ne répondra pas à la saisie lorsqu’il est masqué par une autre fenêtre ou une surcouche</string>\n    <string name=\"settings_su_auth_title\">Authentification utilisateur</string>\n    <string name=\"settings_su_auth_summary\">Demander l’authentification utilisateur lors de la requête super-utilisateur</string>\n    <string name=\"settings_su_auth_insecure\">Aucun méthode d’authentification utilisateur n’est configurée sur le périphérique</string>\n    <string name=\"settings_customization\">Personnalisation</string>\n    <string name=\"setting_add_shortcut_summary\">Ajouter un joli raccourci dans l’écran d’accueil au cas où le nom et l’icône seraient difficiles à reconnaître après avoir masqué l’application</string>\n    <string name=\"settings_doh_title\">DNS sur HTTPS</string>\n    <string name=\"settings_doh_description\">Contournement de la censure du DNS dans certains pays</string>\n    <string name=\"settings_random_name_title\">Nom de sortie aléatoire</string>\n    <string name=\"settings_random_name_description\">Randomiser le nom du fichier de sortie des images modifiées et des fichiers tar afin d’éviter la détection.</string>\n\n    <string name=\"multiuser_mode\">Mode multi‑utilisateur</string>\n    <string name=\"settings_owner_only\">Propriétaire de l’appareil uniquement</string>\n    <string name=\"settings_owner_manage\">Géré par le propriétaire de l’appareil</string>\n    <string name=\"settings_user_independent\">Indépendant de l’utilisateur</string>\n    <string name=\"owner_only_summary\">Seul le propriétaire possède un accès super‑utilisateur.</string>\n    <string name=\"owner_manage_summary\">Seul le propriétaire peut gérer l’accès super‑utilisateur et recevoir les demandes d’accès.</string>\n    <string name=\"user_independent_summary\">Chaque utilisateur a ses propres règles d’accès super‑utilisateur distinctes.</string>\n\n    <string name=\"mount_namespace_mode\">Mode d’espace de noms du montage</string>\n    <string name=\"settings_ns_global\">Espace de noms global</string>\n    <string name=\"settings_ns_requester\">Hériter de l’espace de noms</string>\n    <string name=\"settings_ns_isolate\">Espace de noms isolé</string>\n    <string name=\"global_summary\">Toutes les sessions super‑utilisateur utilisent l’espace de noms global du montage.</string>\n    <string name=\"requester_summary\">Les sessions super‑utilisateur hériteront de l’espace de noms de leur demandeur.</string>\n    <string name=\"isolate_summary\">Chaque session super‑utilisateur aura son propre espace de noms isolé.</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Mises à jour de Magisk</string>\n    <string name=\"progress_channel\">Notifications de progression</string>\n    <string name=\"updated_channel\">Mise à jour Terminée</string>\n    <string name=\"download_complete\">Téléchargement terminé</string>\n    <string name=\"download_file_error\">Erreur lors du téléchargement du fichier</string>\n    <string name=\"magisk_update_title\">Une mise à jour de Magisk est disponible !</string>\n    <string name=\"updated_title\">Magisk Mis à jour</string>\n    <string name=\"updated_text\">Appuyer pour ouvrir l’application</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Oui</string>\n    <string name=\"no\">Non</string>\n    <string name=\"repo_install_title\">Installer %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Télécharger</string>\n    <string name=\"reboot\">Redémarrer</string>\n    <string name=\"release_notes\">Notes de version</string>\n    <string name=\"flashing\">Écriture en mémoire Flash…</string>\n    <string name=\"done\">Terminé !</string>\n    <string name=\"failure\">Erreur</string>\n    <string name=\"hide_app_title\">Masquage de l’application Magisk…</string>\n    <string name=\"open_link_failed_toast\">Aucune application permettant d’ouvrir le lien n’a été trouvée</string>\n    <string name=\"complete_uninstall\">Désinstallation complète</string>\n    <string name=\"restore_img\">Restauration des images</string>\n    <string name=\"restore_img_msg\">Restauration…</string>\n    <string name=\"restore_done\">Restauration effectuée !</string>\n    <string name=\"restore_fail\">La sauvegarde par défaut n’existe pas !</string>\n    <string name=\"setup_fail\">Échec de l’installation</string>\n    <string name=\"env_full_fix_msg\">Votre périphérique nécessite de réinstaller Magisk pour fonctionner correctement. Merci de réinstaller Magisk par l’application, le mode de récupération ne pouvant pas récupérer les informations correctement.</string>\n    <string name=\"env_fix_title\">Installation supplémentaire requise</string>\n    <string name=\"env_fix_msg\">Votre appareil a besoin d’une installation supplémentaire afin que Magisk fonctionne correctement. Voulez‑vous effectuer cette installation maintenant puis redémarrer ?</string>\n    <string name=\"setup_msg\">Configuration de l’environnement en cours…</string>\n    <string name=\"unsupport_magisk_title\">Version de Magisk non prise en charge</string>\n    <string name=\"unsupport_magisk_msg\">Cette version de Magisk Manager ne prend pas en charge les versions de Magisk inférieures à %1$s.\\n\\nL’application se comportera comme si Magisk n’était pas installé. Veuillez mettre à jour Magisk aussi vite que possible.</string>\n    <string name=\"unsupport_general_title\">État anormal</string>\n    <string name=\"unsupport_system_app_msg\">Cette application ne peut être installée en tant qu’application système. Veuillez la restaurer en tant qu’application utilisateur.</string>\n    <string name=\"unsupport_other_su_msg\">Une commande « su » n’appartenant pas à Magisk a été détectée. Merci de supprimer cet autre « su » qui n’est pas pris en charge.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk Manager est installé sur le stockage externe. Veuillez déplacer l’application sur le stockage interne.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Cette application ne peut continuer à fonctionner en étant masquée car l’accès en tant que super‑utilisateur a été perdu. Merci de restaurer l’APK originel.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Permettre l’accès au stockage pour activer cette fonctionnalité</string>\n    <string name=\"post_notifications_denied\">Permettre l’accès aux notifications pour activer cette fonctionnalité</string>\n    <string name=\"install_unknown_denied\">Autoriser \"installation d’applications inconnues\" pour activer cette fonctionnalité</string>\n    <string name=\"add_shortcut_title\">Ajouter un raccourci à l’écran d’accueil</string>\n    <string name=\"add_shortcut_msg\">Après avoir masqué Magisk, son nom et son icône peuvent devenir difficiles à reconnaître. Voulez‑vous ajouter un joli raccourci vers l’écran d’accueil ?</string>\n    <string name=\"app_not_found\">Aucune application n’a été trouvée pour gérer cette action</string>\n    <string name=\"reboot_apply_change\">Redémarrer pour appliquer les changements</string>\n    <string name=\"restore_app_confirmation\">Cela restaurera l’application cachée en application originale. Vous voulez vraiment faire ça ?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-hi/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">मॉड्यूल</string>\n    <string name=\"superuser\">सुपर यूज़र</string>\n    <string name=\"logs\">लॉग्स</string>\n    <string name=\"settings\">सेटिंग्स</string>\n    <string name=\"install\">इंस्टॉल</string>\n    <string name=\"section_home\">होम</string>\n    <string name=\"section_theme\">थीम</string>\n    <string name=\"denylist\">अस्वीकार सूची</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">कोई कनेक्शन उपलब्ध नहीं है</string>\n    <string name=\"app_changelog\">बदलाव</string>\n    <string name=\"loading\">लोड हो रहा है…</string>\n    <string name=\"update\">अद्यतन करें</string>\n    <string name=\"not_available\">उपलब्ध नहीं है</string>\n    <string name=\"hide\">छिपाएँ</string>\n    <string name=\"home_package\">पैकेज</string>\n    <string name=\"home_app_title\">ऐप</string>\n\n    <string name=\"home_notice_content\">मैजिस्क केवल आधिकारिक गिटहब पृष्ठ पेज से डाउनलोड करें। अज्ञात स्रोतों से फ़ाइलें दुर्भावनापूर्ण हो सकती हैं!</string>\n    <string name=\"home_support_title\">हमें सहयोग दीजिये</string>\n    <string name=\"home_follow_title\">अनुसरण करें</string>\n    <string name=\"home_item_source\">स्रोत</string>\n    <string name=\"home_support_content\">मैजिस्क है, और हमेशा मुफ्त और ओपन-सोर्स रहेगा। हालाँकि आप एक छोटा सा दान भेजकर हमारी मदद कर सकते है।</string>\n    <string name=\"home_installed_version\">इंस्टॉल्ड</string>\n    <string name=\"home_latest_version\">नवीनतम</string>\n    <string name=\"invalid_update_channel\">अमान्य अपडेट चैनल</string>\n    <string name=\"uninstall_magisk_title\">मैजिस्क को अनइंस्टॉल करें</string>\n    <string name=\"uninstall_magisk_msg\">सभी मॉड्यूल्स को निष्क्रिय/हटा दिया जाएगा!\\n रूट को हटा दिया जाएगा!\\n आपका डेटा संभावित रूप से एन्क्रिप्ट किया गया है यदि पहले से नहीं है!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">फ़ोर्स एन्क्रिप्शन को संरक्षित रखें</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity को संरक्षित रखें</string>\n    <string name=\"recovery_mode\">रिकवरी मोड</string>\n    <string name=\"install_options_title\">विकल्प</string>\n    <string name=\"install_method_title\">तरीका</string>\n    <string name=\"install_next\">आगे</string>\n    <string name=\"install_start\">तो चलिए शुरू करते हैं!</string>\n    <string name=\"manager_download_install\">डाउनलोड और इंस्टॉल करने के लिए दबाएं</string>\n    <string name=\"direct_install\">प्रत्यक्ष इंस्टॉल (अनुशंसित)</string>\n    <string name=\"install_inactive_slot\">निष्क्रिय स्लॉट में इंस्टॉल करें (OTA के बाद)</string>\n    <string name=\"install_inactive_slot_msg\">आपका डिवाइस रीबूट होने के बाद वर्तमान निष्क्रिय स्लॉट में बूट करने के लिए मजबूर किया जाएगा!\\n इस विकल्प का उपयोग सिर्फ OTA हो जाने के बाद करें।\\n जारी रखें?</string>\n    <string name=\"setup_title\">अतिरिक्त सेटअप</string>\n    <string name=\"select_patch_file\">एक फ़ाइल का चयन और पैच करें</string>\n    <string name=\"patch_file_msg\">एक रॉ छवि (* .img) या एक ODIN tarfile (* .tar) का चयन करें</string>\n    <string name=\"reboot_delay_toast\">5 सेकंड में रिबूट हो रहा है...</string>\n    <string name=\"flash_screen_title\">इंस्टॉलेशन</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">सुपरयूज़र अनुरोध</string>\n    <string name=\"touch_filtered_warning\">चूंकि एक ऐप सुपरयूज़र अनुरोध को अस्पष्ट कर रहा है, इसलिए मैजिस्क आपकी प्रतिक्रिया को सत्यापित नहीं कर सकता</string>\n    <string name=\"deny\">इंकार करें</string>\n    <string name=\"prompt\">संकेत दिखाएँ</string>\n    <string name=\"grant\">अनुमति दें</string>\n    <string name=\"su_warning\">यह आपके डिवाइस की पूरी पहुँच की अनुमति देगा,\\n यदि आप सुनिश्चित नहीं हैं तो इंकार करें!</string>\n    <string name=\"forever\">सदैव</string>\n    <string name=\"once\">एक बार</string>\n    <string name=\"tenmin\">10 मिनिट</string>\n    <string name=\"twentymin\">20 मिनट</string>\n    <string name=\"thirtymin\">30 मिनट</string>\n    <string name=\"sixtymin\">60 मिनट</string>\n    <string name=\"su_allow_toast\"> %1$s को सुपरयूज़र अधिकार प्रदान किया गया</string>\n    <string name=\"su_deny_toast\"> %1$s को सुपरयूज़र अधिकार से इंकार किया गया</string>\n    <string name=\"su_snack_grant\"> %1$s को सुपरयूज़र अधिकार प्रदान किए गए </string>\n    <string name=\"su_snack_deny\"> %1$s के सुपरयूज़र अधिकारों को अस्वीकार कर दिया गया है</string>\n    <string name=\"su_snack_notif_on\"> %1$s की सूचनाएं सक्षम हैं</string>\n    <string name=\"su_snack_notif_off\"> %1$s की सूचनाएं अक्षम हैं</string>\n    <string name=\"su_snack_log_on\"> %1$s की लॉगिंग सक्षम है</string>\n    <string name=\"su_snack_log_off\"> %1$s की लॉगिंग अक्षम है</string>\n    <string name=\"su_revoke_title\">अधिकार वापस लें?</string>\n    <string name=\"su_revoke_msg\"> %1$s के अधिकारों को रद्द करने की पुष्टि करें?</string>\n    <string name=\"toast\">पॉप-अप सूचना</string>\n    <string name=\"none\">कोई नहीं</string>\n\n    <string name=\"superuser_toggle_notification\">सूचनाएं</string>\n    <string name=\"superuser_toggle_revoke\">अधिकार वापस लें</string>\n    <string name=\"superuser_policy_none\">किसी भी ऐप ने अभी तक सुपरयूज़र की अनुमति नहीं मांगी है।</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">कोई लॉग नहीं, अपने सुपरयूज़र सक्षम एप्लिकेशन का अधिक उपयोग करने का प्रयास करें।</string>\n    <string name=\"log_data_magisk_none\">मैजिस्क लॉग खाली हैं, यह अजीब है।</string>\n    <string name=\"menuSaveLog\">लॉग सहेजे</string>\n    <string name=\"menuClearLog\">लॉग साफ़ करें</string>\n    <string name=\"logs_cleared\">लॉग सफलतापूर्वक साफ़ किया गया।</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">लक्षित UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">सिस्टम ऐप्स दिखाएं</string>\n    <string name=\"show_os_app\">OS एप्लिकेशन दिखाएं</string>\n    <string name=\"hide_filter_hint\">नाम से फ़िल्टर करें</string>\n    <string name=\"hide_search\">खोजें</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(कोई जानकारी नहीं दी गई)</string>\n    <string name=\"reboot_userspace\">सॉफ्ट रिबूट</string>\n    <string name=\"reboot_recovery\">रिकवरी रिबूट</string>\n    <string name=\"reboot_bootloader\">बूटलोडर रिबूट</string>\n    <string name=\"reboot_download\">डाउनलोड मोड रिबूट</string>\n    <string name=\"reboot_edl\">EDL रिबूट</string>\n    <string name=\"module_version_author\"> %2$s के द्वारा %1$s</string>\n    <string name=\"module_state_remove\">हटाएँ</string>\n    <string name=\"module_state_restore\">रिस्टोर करें</string>\n    <string name=\"module_action_install_external\">स्टोरेज से इनस्टॉल करें</string>\n    <string name=\"update_available\">अपडेट उपलब्ध</string>\n    <string name=\"suspend_text_riru\">%1$s सक्रिय होने के कारण मॉड्यूल निलंबित</string>\n    <string name=\"suspend_text_zygisk\">%1$s अक्षम होने के कारण मॉड्यूल निलंबित</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk मॉड्यूल असंगतता के कारण लोड होने में असमर्थ</string>\n    <string name=\"module_empty\">कोई मॉड्यूल इंस्टॉल्ड नहीं</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">थीम मोड</string>\n    <string name=\"settings_dark_mode_message\">ऐसी थीम का चयन करें जो आपकी शैली के अनुकूल हो!</string>\n    <string name=\"settings_dark_mode_light\">हमेशा सफेद रंग वाली</string>\n    <string name=\"settings_dark_mode_system\">सिस्टम के अनुसार</string>\n    <string name=\"settings_dark_mode_dark\">डार्क मोड</string>\n    <string name=\"settings_download_path_title\">डाउनलोड पथ</string>\n    <string name=\"settings_download_path_message\">फ़ाइलों को %1$s में सहेजा जाएगा</string>\n    <string name=\"settings_hide_app_title\">मैजिस्क ऐप को छिपाएं</string>\n    <string name=\"settings_hide_app_summary\">रैंडम पैकेज आईडी और कस्टम ऐप लेबल के साथ प्रॉक्सी ऐप इंस्टॉल करें</string>\n    <string name=\"settings_restore_app_title\">मैजिस्क ऐप को पुनर्स्थापित करें</string>\n    <string name=\"settings_restore_app_summary\">ऐप को उनहीदे करें और मूल एपीके को पुनर्स्थापित करें</string>\n    <string name=\"language\">भाषा</string>\n    <string name=\"system_default\">(सिस्टम डिफ़ॉल्ट)</string>\n    <string name=\"settings_check_update_title\">अपडेट जांचें</string>\n    <string name=\"settings_check_update_summary\">समय-समय पर बैकग्राउंड में अपडेट की जांच करते रहें</string>\n    <string name=\"settings_update_channel_title\">अपडेट चैनल</string>\n    <string name=\"settings_update_stable\">स्टेबल</string>\n    <string name=\"settings_update_beta\">बीटा</string>\n    <string name=\"settings_update_custom\">कस्टम चैनल</string>\n    <string name=\"settings_update_custom_msg\">एक कस्टम URL डालें</string>\n    <string name=\"settings_zygisk_summary\">मैजिस्क को जाइगोट डेमॉन के कुछ हिस्सों को चलाएं</string>\n    <string name=\"settings_denylist_title\">अस्वीकार सूची लागू करें</string>\n    <string name=\"settings_denylist_summary\">अस्वीकृत सूची की प्रक्रियाओं में सभी मैजिस्क संशोधन पूर्ववत हो जाएंगे</string>\n    <string name=\"settings_denylist_config_title\">अस्वीकार सूची कॉन्फ़िगर करें</string>\n    <string name=\"settings_denylist_config_summary\">अस्वीकार सूची में शामिल की जाने वाली प्रक्रियाओं का चयन करें</string>\n    <string name=\"settings_hosts_title\">सिस्टमलेस होस्ट</string>\n    <string name=\"settings_hosts_summary\">विज्ञापन ब्लॉक ऐप्स के लिए सिस्टमलेस होस्ट समर्थन:</string>\n    <string name=\"settings_hosts_toast\">सिस्टमलेस होस्ट मॉड्यूल जोड़ा गया</string>\n    <string name=\"settings_app_name_hint\">नया नाम</string>\n    <string name=\"settings_app_name_helper\">इस नाम से ऐप को फिर से इंस्टॉल किया जाएगा</string>\n    <string name=\"settings_app_name_error\">अवैध प्रारूप</string>\n    <string name=\"settings_su_app_adb\">ऐप्स और एडीबी</string>\n    <string name=\"settings_su_app\">केवल ऐप्स</string>\n    <string name=\"settings_su_adb\">केवल एडीबी</string>\n    <string name=\"settings_su_disable\">बंद है</string>\n    <string name=\"settings_su_request_10\">10 सेकंड</string>\n    <string name=\"settings_su_request_15\">15 सेकंड</string>\n    <string name=\"settings_su_request_20\">20 सेकंड</string>\n    <string name=\"settings_su_request_30\">30 सेकंड</string>\n    <string name=\"settings_su_request_45\">45 सेकंड</string>\n    <string name=\"settings_su_request_60\">60 सेकंड</string>\n    <string name=\"superuser_access\">सुपरयूज़र एक्सेस</string>\n    <string name=\"auto_response\">स्वचालित प्रतिक्रिया</string>\n    <string name=\"request_timeout\">अनुरोध की समय समाप्ति सीमा</string>\n    <string name=\"superuser_notification\">सुपरयूज़र सूचना</string>\n    <string name=\"settings_su_reauth_title\">अपग्रेड के बाद प्रमाणीकरण</string>\n    <string name=\"settings_su_reauth_summary\">एक ऐप अपडेट होने के बाद सुपरयूज़र अनुमति प्रमाणित करें</string>\n    <string name=\"settings_su_tapjack_title\">टैपजैकिंग सुरक्षा</string>\n    <string name=\"settings_su_tapjack_summary\">सुपरयुसर प्रॉम्प्ट डायलॉग किसी अन्य विंडो या ओवरले द्वारा अस्पष्ट होने पर इनपुट का जवाब नहीं देगा</string>\n    <string name=\"settings_customization\">कस्टमाईजेशन</string>\n    <string name=\"setting_add_shortcut_summary\">ऐप को छिपाने के बाद नाम और आइकन को पहचानना मुश्किल है, तो होम स्क्रीन में एक सुंदर शॉर्टकट जोड़ें</string>\n    <string name=\"settings_doh_title\">HTTPS पर DNS</string>\n    <string name=\"settings_doh_description\">कुछ राष्ट्रों में चल रही DNS विषाक्तता का समाधान</string>\n\n    <string name=\"multiuser_mode\">बहु उपयोगकर्ता मोड</string>\n    <string name=\"settings_owner_only\">केवल डिवाइस का मालिक</string>\n    <string name=\"settings_owner_manage\">डिवाइस मालिक द्वारा प्रबंधित</string>\n    <string name=\"settings_user_independent\"> स्वत्रंत उपयोगकर्ता</string>\n    <string name=\"owner_only_summary\">केवल मालिक के पास ही रूट की पहुँच है</string>\n    <string name=\"owner_manage_summary\">केवल मालिक रूट एक्सेस का प्रबंधन कर सकते हैं और अनुरोध संकेत प्राप्त कर सकते हैं</string>\n    <string name=\"user_independent_summary\">प्रत्येक उपयोगकर्ता का अपना अलग रूट नियम होता है</string>\n\n    <string name=\"mount_namespace_mode\">माउंट नेमस्पेस मोड</string>\n    <string name=\"settings_ns_global\">ग्लोबल नेमस्पेस</string>\n    <string name=\"settings_ns_requester\">इनहेरिट नेमस्पेस</string>\n    <string name=\"settings_ns_isolate\">आइसोलेटेड नेमस्पेस</string>\n    <string name=\"global_summary\">सभी रूट सत्र ग्लोबल नेमस्पेस का उपयोग करते हैं</string>\n    <string name=\"requester_summary\">रूट सत्रों को उनके अनुरोधकर्ताओं के नेमस्पेस विरासत में मिलेंगे</string>\n    <string name=\"isolate_summary\">प्रत्येक रूट सत्र का अपना अलग आइसोलेटेड नेमस्पेस होगा</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">मैजिस्क अपडेट</string>\n    <string name=\"progress_channel\">प्रगति सूचनाएँ</string>\n    <string name=\"updated_channel\">अपडेट पूरा हुआ</string>\n    <string name=\"download_complete\">डाउनलोड सम्पन्न हुआ</string>\n    <string name=\"download_file_error\">फ़ाइल डाउनलोड करने में त्रुटि</string>\n    <string name=\"magisk_update_title\">मैजिस्क अपडेट उपलब्ध है!</string>\n    <string name=\"updated_title\">मैजिस्क अपडेट पूरा हुआ</string>\n    <string name=\"updated_text\">ऐप खोलने के लिए टैप करें</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">हाँ</string>\n    <string name=\"no\">नहीं</string>\n    <string name=\"repo_install_title\">इंस्टॉल %1$s %2$s(%3$d)</string>\n    <string name=\"download\">डाउनलोड</string>\n    <string name=\"reboot\">रीबूट</string>\n    <string name=\"release_notes\">रिलीज नोट्स</string>\n    <string name=\"flashing\">फ़्लैश हो रहा है...</string>\n    <string name=\"done\">सफल हुआ!</string>\n    <string name=\"failure\">असफल हुआ !</string>\n    <string name=\"hide_app_title\">मैजिस्क ऐप अब छिप रहा है…</string>\n    <string name=\"open_link_failed_toast\">लिंक खोलने के लिए कोई एप्लिकेशन नहीं मिला</string>\n    <string name=\"complete_uninstall\">पूर्ण रूप से अनइंस्टॉल करें</string>\n    <string name=\"restore_img\">इमेज रिस्टोर करें</string>\n    <string name=\"restore_img_msg\">रिस्टोर हो रहा है...</string>\n    <string name=\"restore_done\">रिस्टोर सफल हुआ!</string>\n    <string name=\"restore_fail\">स्टॉक बैकअप मौजूद नहीं है!</string>\n    <string name=\"setup_fail\">सेटअप असफल हुआ</string>\n    <string name=\"env_fix_title\">अतिरिक्त सेटअप की आवश्यकता</string>\n    <string name=\"env_fix_msg\">मैजिस्क के ठीक से काम करने के लिए आपके डिवाइस को अतिरिक्त सेटअप की आवश्यकता है। क्या आप आगे बढ़ना और रिबूट करना चाहते हैं?</string>\n    <string name=\"setup_msg\">वातावरण सेटअप चल रहा है...</string>\n    <string name=\"unsupport_magisk_title\">असमर्थित मैजिस्क संस्करण</string>\n    <string name=\"unsupport_magisk_msg\">ऐप का यह संस्करण %1$s से कम के मैजिस्क संस्करणों का समर्थन नहीं करता है।\\n\\nऐप ऐसा व्यवहार करेगा जैसे कोई मैजिस्क इंस्टॉल नहीं है, कृपया जितनी जल्दी हो सके मैजिस्क को अपग्रेड करें।</string>\n    <string name=\"unsupport_general_title\">असामान्य अवस्था</string>\n    <string name=\"unsupport_system_app_msg\">इस ऐप को सिस्टम ऐप के रूप में चलाना समर्थित नहीं है। कृपया ऐप को उपयोगकर्ता ऐप पर वापस लाएं।</string>\n    <string name=\"unsupport_other_su_msg\">एक \\\"su\\\" बाइनरी का पता चला है जो मैजिस्क से नहीं है। कृपया कोई प्रतिस्पर्धी मूल समाधान निकालें और/या मैजिस्क को फिर से स्थापित करें।</string>\n    <string name=\"unsupport_external_storage_msg\">मैजिस्क बाहरी संग्रहण में स्थापित है। कृपया ऐप को इंटरनल स्टोरेज में ले जाएं।</string>\n    <string name=\"unsupport_nonroot_stub_msg\">छिपा हुआ मैजिक ऐप काम करना जारी नहीं रख सकता क्योंकि रूट खो गया था। कृपया मूल APK को पुनर्स्थापित करें।</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">इसका उपयोग करने के लिए स्टोरेज अनुमति प्रदान करें</string>\n    <string name=\"install_unknown_denied\">इस कार्यक्षमता को सक्षम करने के लिए \"अज्ञात ऐप्स इंस्टॉल करें\" की अनुमति दें</string>\n    <string name=\"add_shortcut_title\">होम स्क्रीन पर शॉर्टकट जोड़ें</string>\n    <string name=\"add_shortcut_msg\">इस ऐप को छिपाने के बाद इसका नाम और आइकन पहचानना मुश्किल हो सकता है। क्या आप होम स्क्रीन पर एक सुंदर शॉर्टकट जोड़ना चाहते हैं?</string>\n    <string name=\"app_not_found\">इस एक्शन को संभालने के लिए कोई एप्लिकेशन नहीं मिली</string>\n    <string name=\"reboot_apply_change\">परिवर्तन लागू करने के लिए रीबूट करें</string>\n    <string name=\"restore_app_confirmation\">यह छिपे हुए ऐप को मूल ऐप पर वापस बहाल कर देगा। क्या आप वाकई ऐसा करना चाहते हैं?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-hn/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modules</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Logs</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"install\">Install</string>\n    <string name=\"section_home\">Home</string>\n    <string name=\"section_theme\">Themes</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Net nahi chal rha hai</string>\n    <string name=\"app_changelog\">Kya naga hai</string>\n    <string name=\"loading\">Loading ho rha hai…</string>\n    <string name=\"update\">Update</string>\n    <string name=\"not_available\">Available nahi hai</string>\n    <string name=\"hide\">Chhupao</string>\n    <string name=\"home_package\">Package</string>\n    <string name=\"home_app_title\">App</string>\n    <string name=\"home_notice_content\">Hamesha Magisk ko uske official github release source se download karein. Unofficial source ki file khatarnak ho sakti hai.</string>\n    <string name=\"home_support_title\">Humein Support karo</string>\n    <string name=\"home_follow_title\">Humein Karo</string>\n    <string name=\"home_item_source\">Source</string>\n    <string name=\"home_support_content\">Magisk hamesha free aur open source rahega. Agar aap support karna chahte ho, toh donation de sakte ho.</string>\n    <string name=\"home_installed_version\">Jo version install hai</string>\n    <string name=\"home_latest_version\">Latest</string>\n    <string name=\"invalid_update_channel\">Galat update channel</string>\n    <string name=\"uninstall_magisk_title\">Magisk uninstall karo</string>\n    <string name=\"uninstall_magisk_msg\">Saare modules disable ya remove ho jayenge!\\nRoot khatam ho jayega!\\nAgar Magisk ne storage ko decrypt kiya tha toh wo phir se encrypt ho jayega!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Force encryption ko preserve karo</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity ko preserve karo</string>\n    <string name=\"recovery_mode\">Recovery mode</string>\n    <string name=\"install_options_title\">Options</string>\n    <string name=\"install_method_title\">Method</string>\n    <string name=\"install_next\">Aage badho</string>\n    <string name=\"install_start\">Chalo shuru karein</string>\n    <string name=\"manager_download_install\">Download aur install karne ke liye dabao</string>\n    <string name=\"direct_install\">Direct install (Recommended)</string>\n    <string name=\"install_inactive_slot\">Inactive slot par install karo (OTA ke baad)</string>\n    <string name=\"install_inactive_slot_msg\">Reboot ke baad device ko inactive slot se boot karna padega!\\nSirf tab use karo jab OTA complete ho chuka ho.\\nAage badhna hai?</string>\n    <string name=\"setup_title\">Extra setup</string>\n    <string name=\"select_patch_file\">File select karke patch karo</string>\n    <string name=\"patch_file_msg\">Raw image (*.img), ODIN tar file (*.tar), ya payload.bin (*.bin) select karo</string>\n    <string name=\"reboot_delay_toast\">Phone 5 second mein reboot hoga…</string>\n    <string name=\"flash_screen_title\">Installation ho rahi hai</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">ROOT access maang rha hai</string>\n    <string name=\"touch_filtered_warning\">Ek app Superuser request ko cover kar raha hai, isliye Magisk aapka response verify nahi kar sakta.</string>\n    <string name=\"deny\">Nahi</string>\n    <string name=\"prompt\">Poocho</string>\n    <string name=\"restrict\">Restrict</string>\n    <string name=\"grant\">Haan Theek hai</string>\n    <string name=\"su_warning\">Yeh full access dega device ko.\\nAgar sure nahi ho toh deny kar do!</string>\n    <string name=\"forever\">Hamesha ke liye</string>\n    <string name=\"once\">Ek baar ke liye</string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">%1$s ko ROOT access de diya gaya</string>\n    <string name=\"su_deny_toast\">%1$s ko ROOT access nahi diya gaya</string>\n    <string name=\"su_snack_grant\">%1$s ko ROOT access mila</string>\n    <string name=\"su_snack_deny\">%1$s ka ROOT access deny hua</string>\n    <string name=\"su_snack_notif_on\">%1$s ke notifications ON hain</string>\n    <string name=\"su_snack_notif_off\">%1$s ke notifications OFF hain</string>\n    <string name=\"su_snack_log_on\">%1$s ka logging ON hai</string>\n    <string name=\"su_snack_log_off\">%1$s ka logging OFF hai</string>\n    <string name=\"su_revoke_title\">Access wapas lena hai?</string>\n    <string name=\"su_revoke_msg\">Kya aap confirm karte ho ki %1$s ka ROOT access hata diya jaye?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Kuch nahi</string>\n    <string name=\"superuser_toggle_notification\">Notifications</string>\n    <string name=\"superuser_toggle_revoke\">Wapas lelo</string>\n    <string name=\"superuser_policy_none\">Ab tak kisi bhi app ne ROOT access nahi manga hai</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Koi logs nahi mile. Shayad tumhe root apps ka zyada use krna chahiye.</string>\n    <string name=\"log_data_magisk_none\">Ajeeb baat hai, Yaha toh logs hain hi nahi.</string>\n    <string name=\"menuSaveLog\">Log save karo</string>\n    <string name=\"menuClearLog\">Log clear karo</string>\n    <string name=\"logs_cleared\">Logs clear ho gaye</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Target UID: %1$d</string>\n    <string name=\"target_pid\">Target PID: %s</string>\n    <string name=\"selinux_context\">SELinux context: %s</string>\n    <string name=\"supp_group\">Supplementary group: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">System apps dikhao</string>\n    <string name=\"show_os_app\">OS apps dikhao</string>\n    <string name=\"hide_filter_hint\">Naam ke hisaab se filter karo</string>\n    <string name=\"hide_search\">Search karo</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Koi info nahi di gayi)</string>\n    <string name=\"reboot_userspace\">Soft reboot</string>\n    <string name=\"reboot_recovery\">Recovery mode mein reboot karo</string>\n    <string name=\"reboot_bootloader\">Bootloader mode mein reboot karo</string>\n    <string name=\"reboot_download\">Download mode mein reboot karo</string>\n    <string name=\"reboot_edl\">EDL mode mein reboot karo</string>\n    <string name=\"reboot_safe_mode\">Safe mode</string>\n    <string name=\"module_version_author\">%1$s ko %2$s ne banaya hai</string>\n    <string name=\"module_state_remove\">Delete karo</string>\n    <string name=\"module_action\">Action</string>\n    <string name=\"module_state_restore\">Wapas laao</string>\n    <string name=\"module_action_install_external\">Storage se install karo</string>\n    <string name=\"update_available\">Naya update available hai</string>\n    <string name=\"suspend_text_riru\">Module suspend kiya gaya kyunki %1$s enabled hai</string>\n    <string name=\"suspend_text_zygisk\">Bhai %1$s ON kr tabb ye module chalega</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk module compatible nahi tha, isliye load nahi hua</string>\n    <string name=\"module_empty\">Koi module install nahi hai</string>\n    <string name=\"confirm_install\">>%1$s module install karna hai?</string>\n    <string name=\"confirm_install_title\">Ek baar firse soch lo</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Theme mode</string>\n    <string name=\"settings_dark_mode_message\">Apni hisaab se mode choose karo!</string>\n    <string name=\"settings_dark_mode_light\">Hamesha light theme</string>\n    <string name=\"settings_dark_mode_system\">System ke saath follow karo</string>\n    <string name=\"settings_dark_mode_dark\">Hamesha dark theme</string>\n    <string name=\"settings_download_path_title\">Download path</string>\n    <string name=\"settings_download_path_message\">Files yahan save hongi: %1$s</string>\n    <string name=\"settings_hide_app_title\">Magisk app ko hide karo</string>\n    <string name=\"settings_hide_app_summary\">Magisk app ka name aur package ID change kro</string>\n    <string name=\"settings_restore_app_title\">Magisk app ko unhide karo</string>\n    <string name=\"settings_restore_app_summary\">App ko unhide karo aur original APK wapas lao</string>\n    <string name=\"language\">Language</string>\n    <string name=\"system_default\">(System ki default)</string>\n    <string name=\"settings_check_update_title\">Updates check karo</string>\n    <string name=\"settings_check_update_summary\">Background mein updates auto check honge</string>\n    <string name=\"settings_update_channel_title\">Update channel</string>\n    <string name=\"settings_update_stable\">Stable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Custom</string>\n    <string name=\"settings_update_custom_msg\">Apna custom channel URL daaloL</string>\n    <string name=\"settings_zygisk_summary\">Magisk ke kuch parts ko Zygote daemon mein run karo</string>\n    <string name=\"settings_denylist_title\">DenyList enforce karo</string>\n    <string name=\"settings_denylist_summary\">DenyList mein jo processes hain, unpe Magisk ka effect hata diya jayega</string>\n    <string name=\"settings_denylist_config_title\">DenyList set karo</string>\n    <string name=\"settings_denylist_config_summary\">Kaunse process DenyList mein daalne hain, select karo</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Ad-blocking apps ke liye systemless hosts support</string>\n    <string name=\"settings_hosts_toast\">Systemless hosts module add kar diya gaya</string>\n    <string name=\"settings_app_name_hint\">Naya naam</string>\n    <string name=\"settings_app_name_helper\">App is naam ke saath repack hoga</string>\n    <string name=\"settings_app_name_error\">Naam ka format galat hai</string>\n    <string name=\"settings_su_app_adb\">Apps aur ADB</string>\n    <string name=\"settings_su_app\">Sirf apps</string>\n    <string name=\"settings_su_adb\">Sirf ADB</string>\n    <string name=\"settings_su_disable\">Disable kiya gaya</string>\n    <string name=\"settings_su_request_10\">10 seconds</string>\n    <string name=\"settings_su_request_15\">15 seconds</string>\n    <string name=\"settings_su_request_20\">20 seconds</string>\n    <string name=\"settings_su_request_30\">30 seconds</string>\n    <string name=\"settings_su_request_45\">45 seconds</string>\n    <string name=\"settings_su_request_60\">60 seconds</string>\n    <string name=\"superuser_access\">ROOT access</string>\n    <string name=\"auto_response\">Automatic response</string>\n    <string name=\"request_timeout\">Request timeout</string>\n    <string name=\"superuser_notification\">ROOT notification</string>\n    <string name=\"settings_su_reauth_title\">Upgrade ke baad dobara permission puchho</string>\n    <string name=\"settings_su_reauth_summary\">App upgrade hone ke baad Superuser permission firse maangna</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking se protection</string>\n    <string name=\"settings_su_tapjack_summary\">Agar Superuser prompt kisi aur window ya overlay ke neeche chhup gaya ho, toh uspar tap kaam nahi karega</string>\n    <string name=\"settings_su_auth_title\">User authentication</string>\n    <string name=\"settings_su_auth_summary\">Superuser request ke time user se authentication maango</string>\n    <string name=\"settings_su_auth_insecure\">Device mein koi bhi authentication method set nahi hai</string>\n    <string name=\"settings_su_restrict_title\">Root access ko limit karo</string>\n    <string name=\"settings_su_restrict_summary\">Nayeapps ko ROOT access maangne se block karega. Warning: Isse zyada tarr apps kaam karna band kar denge. Sirf tab enable karo jab aapko pata ho aap kya kar rahe ho.</string>\n    <string name=\"settings_customization\">Customization</string>\n    <string name=\"setting_add_shortcut_summary\">Agar app hide karne ke baad uska naam ya icon samajhne mein dikkat ho rahi ho, toh home screen pe ek shortcut add kar lo</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">DNS poisoning se bachne ke liye (kuch countries mein zaroori padti hai)</string>\n    <string name=\"settings_random_name_title\">Output file ka naam random karo</string>\n    <string name=\"settings_random_name_description\">Patched images aur tar files ka naam random bana ke detection se bachao</string>\n    <string name=\"multiuser_mode\">Multiuser mode</string>\n    <string name=\"settings_owner_only\">Sirf device owner</string>\n    <string name=\"settings_owner_manage\">Device owner manage karega</string>\n    <string name=\"settings_user_independent\">Har user ke liye alag</string>\n    <string name=\"owner_only_summary\">Sirf device owner ko root access milega</string>\n    <string name=\"owner_manage_summary\">Sirf owner root access manage kar sakta hai aur requests receive karega</string>\n    <string name=\"user_independent_summary\">Har user ke liye alag root rules honge</string>\n    <string name=\"mount_namespace_mode\">Mount namespace mode</string>\n    <string name=\"settings_ns_global\">Global namespace</string>\n    <string name=\"settings_ns_requester\">Inherit namespace</string>\n    <string name=\"settings_ns_isolate\">Isolated namespace</string>\n    <string name=\"global_summary\">Sabhi root sessions ek hi global mount namespace use karenge</string>\n    <string name=\"requester_summary\">Root session apne requester ka namespace inherit karega</string>\n    <string name=\"isolate_summary\">Har root session ka apna alag isolated namespace hoga</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk updates</string>\n    <string name=\"progress_channel\">Progress notifications</string>\n    <string name=\"updated_channel\">Update ho gaya</string>\n    <string name=\"download_complete\">Download ho gaya</string>\n    <string name=\"download_file_error\">File download karne mein error aaya</string>\n    <string name=\"magisk_update_title\">Magisk ka naya update available hai!</string>\n    <string name=\"updated_title\">Magisk update ho gaya</string>\n    <string name=\"updated_text\">App open karne ke liye tap karo</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Haan</string>\n    <string name=\"no\">Nahi</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) Install karo</string>\n    <string name=\"download\">Download karo</string>\n    <string name=\"reboot\">Reboot karo</string>\n    <string name=\"close\">Close karo</string>\n    <string name=\"release_notes\">Release notes</string>\n    <string name=\"flashing\">Flash ho raha hai…</string>\n    <string name=\"running\">Chal raha hai…</string>\n    <string name=\"done\">Ho gaya!</string>\n    <string name=\"done_action\">%1$s ka action complete ho gaya</string>\n    <string name=\"failure\">Fail ho gaya!</string>\n    <string name=\"hide_app_title\">Magisk app ko chhupa rahe hain…</string>\n    <string name=\"open_link_failed_toast\">Link kholne ke liye koi app nahi mila</string>\n    <string name=\"complete_uninstall\">Sab kuch uninstall karo</string>\n    <string name=\"restore_img\">Images restore karo</string>\n    <string name=\"restore_img_msg\">Restore kiya ja raha hai…</string>\n    <string name=\"restore_done\">Restore complete ho gaya!</string>\n    <string name=\"restore_fail\">Stock backup available nahi hai!</string>\n    <string name=\"setup_fail\">Setup fail ho gaya</string>\n    <string name=\"env_fix_title\">Iske liye thoda extra setup chahiye</string>\n    <string name=\"env_fix_msg\">Magisk ko sahi se chalane ke liye thoda aur setup karna padega. Kya aap proceed karke device reboot karna chahte ho?</string>\n    <string name=\"env_full_fix_msg\">Magisk ko properly chalane ke liye aapko usse dubara flash karna padega. App ke andar se Magisk reinstall karo, kyunki Recovery mode sahi device info nahi de pata.</string>\n    <string name=\"setup_msg\">Environment setup chal raha hai…</string>\n    <string name=\"unsupport_magisk_title\">Magisk version supported nahi hain</string>\n    <string name=\"unsupport_magisk_msg\">Is app version ko %1$s se purani Magisk versions support nahi karti.\\n\\nApp aise behave karega jaise Magisk install hi nahi hai. Jaldi se Magisk ko update karo.</string>\n    <string name=\"unsupport_general_title\">System thoda alag behave kar raha hai</string>\n    <string name=\"unsupport_system_app_msg\">App ko system app bana ke chalana supported nahi hai. Please app ko wapas user app bana do.</string>\n    <string name=\"unsupport_other_su_msg\">Pehle doosri root method hatao ya Magisk dobara install karo.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk external storage pe installed hai. App ko internal storage mein move karo.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Hidden Magisk app ab kaam nahi karega kyunki root chala gaya hai. Please original APK restore karo.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Iss feature ko enable karne ke liye storage permission allow karo</string>\n    <string name=\"post_notifications_denied\">Is feature ke liye notifications permission allow karo</string>\n    <string name=\"install_unknown_denied\">\\\"Install unknown apps\\\" ki permission allow karo taaki ye feature kaam kar sakey</string>\n    <string name=\"add_shortcut_title\">Shortcut home screen pe add karo</string>\n    <string name=\"add_shortcut_msg\">App ko hide karne ke baad agar uska icon ya naam pehchanna mushkil ho, toh ek shortcut home screen pe add kar lein?</string>\n    <string name=\"app_not_found\">Is action ko handle karne ke liye koi app nahi mila</string>\n    <string name=\"reboot_apply_change\">Changes apply karne ke liye reboot krna zaroori hai</string>\n    <string name=\"restore_app_confirmation\">Ye action hidden app ko original version mein wapas laayega. Kya aap sach mein ye karna chahte ho?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-hr/strings.xml",
    "content": "<resources>\n    <!--Universal-->\n\n    <!--Welcome Activity-->\n    <string name=\"modules\">Moduli</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Zapisnik događaja</string>\n    <string name=\"settings\">Postavke</string>\n    <string name=\"install\">Instaliranje</string>\n    <string name=\"section_home\">Početna</string>\n    <string name=\"section_theme\">Teme</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nema dostupne veze</string>\n    <string name=\"app_changelog\">Popis izmjena</string>\n    <string name=\"loading\">Učitavanje…</string>\n    <string name=\"update\">Ažuriraj</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Sakrij</string>\n    <string name=\"home_package\">Paket</string>\n\n    <string name=\"home_support_title\">Podržite nas</string>\n    <string name=\"home_item_source\">Izvor</string>\n    <string name=\"home_support_content\">Magisk je i uvijek će biti besplatan i otvoren. Međutim, možete nam pokazati da vam je stalo slanjem male donacije.</string>\n    <string name=\"home_installed_version\">Instalirana</string>\n    <string name=\"home_latest_version\">Najnovija</string>\n    <string name=\"invalid_update_channel\">Nevažeći kanal ažuriranja</string>\n    <string name=\"uninstall_magisk_title\">Deinstaliraj Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Svi će moduli biti onemogućeni/uklonjeni!\\nRoot će biti uklonjen!\\nVaši su podaci potencijalno šifrirani, ako već nisu!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Zadrži prisilno šifirannje</string>\n    <string name=\"keep_dm_verity\">Zadrži AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Recovery način</string>\n    <string name=\"install_options_title\">Opcije</string>\n    <string name=\"install_method_title\">Metoda</string>\n    <string name=\"install_next\">Dalje</string>\n    <string name=\"install_start\">Krenimo</string>\n    <string name=\"manager_download_install\">Dodirnite za preuzimanje i instalaciju</string>\n    <string name=\"direct_install\">Izravna instalacija (preporučeno)</string>\n    <string name=\"install_inactive_slot\">Instaliraj u neaktivni utor (Nakon OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Vaš će se uređaj prisiliti pokrenuti s trenutnog neaktivnog mjesta nakon ponovnog pokretanja!\\nKoristite tu opciju samo nakon što je OTA dovršena.\\nContinue?</string>\n    <string name=\"setup_title\">Dodatno postavljanje</string>\n    <string name=\"select_patch_file\">Odaberite patch datoteku</string>\n    <string name=\"patch_file_msg\">Odaberite raw (*.img) ili ODIN tarfile (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Ponovno pokretanje za 5 sekundi…</string>\n    <string name=\"flash_screen_title\">Instalacija</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser zahtjev</string>\n    <string name=\"deny\">Odbij</string>\n    <string name=\"prompt\">Upit</string>\n    <string name=\"grant\">Odobri</string>\n    <string name=\"su_warning\">Odobren puni pristup vašem uređaju.\\nOdbijte ako niste sigurni!</string>\n    <string name=\"forever\">Zauvijek</string>\n    <string name=\"once\">Jednom</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s su dodijeljena Superuser prava</string>\n    <string name=\"su_deny_toast\">%1$s su odbijena Superuser prava</string>\n    <string name=\"su_snack_grant\">Superuser prava su dodijeljena za %1$s</string>\n    <string name=\"su_snack_deny\">Superuser prava su odbijena za %1$s</string>\n    <string name=\"su_snack_notif_on\">Obavijesti za %1$s su odobrene</string>\n    <string name=\"su_snack_notif_off\">Obavijesti za %1$s su odbijene</string>\n    <string name=\"su_snack_log_on\">Zapisnik događaja omogućen je za %1$s</string>\n    <string name=\"su_snack_log_off\">Zapisnik događaja onemogućen je za %1$s</string>\n    <string name=\"su_revoke_title\">Opozvati?</string>\n    <string name=\"su_revoke_msg\">Potvrdite da biste opozvali prava za %1$s?</string>\n    <string name=\"toast\">Pop-up</string>\n    <string name=\"none\">Nijedan</string>\n\n    <string name=\"superuser_toggle_notification\">Obavijesti</string>\n    <string name=\"superuser_toggle_revoke\">Opozovi</string>\n    <string name=\"superuser_policy_none\">Još nijedna aplikacija nije zatražila Superuser prava.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nemate zapisnika, pokušajte više upotrebljavati svoje aplikacije s omogućenim SU.</string>\n    <string name=\"log_data_magisk_none\">Zapisnici Magiska su prazni, to je čudno.</string>\n    <string name=\"menuSaveLog\">Spremi zapisnik</string>\n    <string name=\"menuClearLog\">Izbriši zapisnik</string>\n    <string name=\"logs_cleared\">Zapisnik uspješno izbrisan.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Ciljani UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Prikaži sistemske aplikacije</string>\n    <string name=\"hide_filter_hint\">Filtriraj prema nazivu</string>\n    <string name=\"hide_search\">Traži</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(Nema podataka)</string>\n    <string name=\"reboot_recovery\">Ponovo pokreni u Recovery</string>\n    <string name=\"reboot_bootloader\">Ponovo pokreni u Bootloader</string>\n    <string name=\"reboot_download\">Ponovo pokreni u Download</string>\n    <string name=\"reboot_edl\">Ponovo pokreni u EDL</string>\n    <string name=\"module_version_author\">%1$s od %2$s</string>\n    <string name=\"module_state_remove\">Ukloni</string>\n    <string name=\"module_state_restore\">Vrati</string>\n    <string name=\"module_action_install_external\">Instaliraj iz pohrane</string>\n    <string name=\"update_available\">Ažuriranje dostupno</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">Teme</string>\n    <string name=\"settings_dark_mode_message\">Odaberite temu koja najbolje odgovara vašem stilu!</string>\n    <string name=\"settings_dark_mode_light\">Uvijek svijetla</string>\n    <string name=\"settings_dark_mode_system\">Prati sustav</string>\n    <string name=\"settings_dark_mode_dark\">Uvijek crna</string>\n    <string name=\"settings_download_path_title\">Putanja preuzimanja</string>\n    <string name=\"settings_download_path_message\">Datoteke će biti spremljene u %1$s</string>\n    <string name=\"language\">Jezik</string>\n    <string name=\"system_default\">(Zadano sustavom)</string>\n    <string name=\"settings_check_update_title\">Provjeri za ažuriranja</string>\n    <string name=\"settings_check_update_summary\">Povremeno provjeravaj ima li ažuriranja u pozadini</string>\n    <string name=\"settings_update_channel_title\">Kanal ažuriranja</string>\n    <string name=\"settings_update_stable\">Stabilna</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Vlastiti kanal</string>\n    <string name=\"settings_update_custom_msg\">Umetnite prilagođeni URL</string>\n    <string name=\"settings_hosts_title\">Hostovi bez sistema</string>\n    <string name=\"settings_hosts_summary\">Podrška za hostove bez sistema za Adblock aplikacije</string>\n    <string name=\"settings_hosts_toast\">Dodan je modul hosta bez sistema</string>\n    <string name=\"settings_app_name_hint\">Novi naziv</string>\n    <string name=\"settings_app_name_helper\">Aplikacija će se prepakirati na ovaj naziv</string>\n    <string name=\"settings_app_name_error\">Nevažeći format</string>\n    <string name=\"settings_su_app_adb\">Aplikacije i ADB</string>\n    <string name=\"settings_su_app\">Samo aplikacije</string>\n    <string name=\"settings_su_adb\">Samo ADB</string>\n    <string name=\"settings_su_disable\">Onemogućeno</string>\n    <string name=\"settings_su_request_10\">10 sekundi</string>\n    <string name=\"settings_su_request_15\">15 sekundi</string>\n    <string name=\"settings_su_request_20\">20 sekundi</string>\n    <string name=\"settings_su_request_30\">30 sekundi</string>\n    <string name=\"settings_su_request_45\">45 sekundi</string>\n    <string name=\"settings_su_request_60\">60 sekundi</string>\n    <string name=\"superuser_access\">Superuser pristup</string>\n    <string name=\"auto_response\">Automatski odgovor</string>\n    <string name=\"request_timeout\">Vremensko ograničenje zahtjeva</string>\n    <string name=\"superuser_notification\">Superuser obavijest</string>\n    <string name=\"settings_su_reauth_title\">Ponovno provjerite autentičnost nakon ažuriranja</string>\n    <string name=\"settings_su_reauth_summary\">Ponovno provjerite autentičnost Superuser-a dopuštenja nakon ažuriranja aplikacije</string>\n    <string name=\"settings_customization\">Prilagodba</string>\n    <string name=\"setting_add_shortcut_summary\">Dodajte lijepi prečac na početni zaslon u slučaju da je naziv i ikonu teško prepoznati nakon skrivanja aplikacije</string>\n    <string name=\"settings_doh_title\">DNS preko HTTPS-a</string>\n    <string name=\"settings_doh_description\">Zaobilazno trovanje DNS-om u nekim zemljama</string>\n\n    <string name=\"multiuser_mode\">Višekorisnički način rada</string>\n    <string name=\"settings_owner_only\">Samo vlasnik uređaja</string>\n    <string name=\"settings_owner_manage\">Upravljano od strane vlasnika uređaja</string>\n    <string name=\"settings_user_independent\">Neovisno o korisniku</string>\n    <string name=\"owner_only_summary\">Samo vlasnik ima root pristup</string>\n    <string name=\"owner_manage_summary\">Samo vlasnik može upravljati root pristupom i primati zahtjeve za root pristup</string>\n    <string name=\"user_independent_summary\">Svaki korisnik ima vlastita root pravila</string>\n\n    <string name=\"mount_namespace_mode\">Postavljanje imenskog prostora</string>\n    <string name=\"settings_ns_global\">Globalni imenski prostor</string>\n    <string name=\"settings_ns_requester\">Naslijediti imenski prostor</string>\n    <string name=\"settings_ns_isolate\">Izolirani imenski prostor</string>\n    <string name=\"global_summary\">Sve root sesije koriste globalni imenski prostor</string>\n    <string name=\"requester_summary\">Root sesije će naslijediti imenski prostor tražitelja</string>\n    <string name=\"isolate_summary\">Svaka root sesija ima svoj vlastiti imenski prostor</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk Ažuriranja</string>\n    <string name=\"progress_channel\">Obavijesti o napretku</string>\n    <string name=\"download_complete\">Preuzimanje završeno</string>\n    <string name=\"download_file_error\">Pogreška pri preuzimanju datoteke</string>\n    <string name=\"magisk_update_title\">Dostupno je Magisk ažuriranje!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Da</string>\n    <string name=\"no\">Ne</string>\n    <string name=\"repo_install_title\">Instaliraj %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Preuzmi</string>\n    <string name=\"reboot\">Ponovno pokreni</string>\n    <string name=\"release_notes\">Bilješke o izdavanju aplikacije</string>\n    <string name=\"flashing\">Apliciranje…</string>\n    <string name=\"done\">Dovršeno!</string>\n    <string name=\"failure\">Nije uspjelo</string>\n    <string name=\"open_link_failed_toast\">Nije pronađena aplikacija za otvaranje veze</string>\n    <string name=\"complete_uninstall\">Dovršite deinstalaciju</string>\n    <string name=\"restore_img\">Vrati Images</string>\n    <string name=\"restore_img_msg\">Vraćanje…</string>\n    <string name=\"restore_done\">Vraćanje je dovršeno!</string>\n    <string name=\"restore_fail\">Izvorna Sig. kopija ne postoji!</string>\n    <string name=\"setup_fail\">Postavljanje nije uspjelo</string>\n    <string name=\"env_fix_title\">Zahtijeva dodatno postavljanje</string>\n    <string name=\"setup_msg\">Postavljanje okruženja za pokretanje…</string>\n    <string name=\"unsupport_magisk_title\">Nepodržana Magisk verzija</string>\n    <string name=\"external_rw_permission_denied\">Odobrite dopuštenje za pohranu da biste omogućili ovu funkciju</string>\n    <string name=\"add_shortcut_title\">Dodaj prečac na početni zaslon</string>\n    <string name=\"app_not_found\">Nije pronađena nijedna aplikacijaza obradu ove radnju</string>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-hu/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modulok</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Naplók</string>\n    <string name=\"settings\">Beállítások</string>\n    <string name=\"install\">Telepítés</string>\n    <string name=\"section_home\">Kezdőlap</string>\n    <string name=\"section_theme\">Témák</string>\n    <string name=\"denylist\">Tiltólista</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nincs elérhető kapcsolat</string>\n    <string name=\"app_changelog\">Változási napló</string>\n    <string name=\"loading\">Betöltés…</string>\n    <string name=\"update\">Frissít</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Elrejt</string>\n    <string name=\"home_package\">Csomag</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">A Magisk-et CSAK a hivatalos GitHub oldalról töltsd le. Az ismeretlen forrásból származó fájlok rosszindulatúak lehetnek!</string>\n    <string name=\"home_support_title\">Támogass minket</string>\n    <string name=\"home_follow_title\">Kövess minket</string>\n    <string name=\"home_item_source\">Forrás</string>\n    <string name=\"home_support_content\">A Magisk ingyenes, nyílt forráskódú és mindig az is marad. Adományoddal azonban jelezheted, hogy törődsz vele.</string>\n    <string name=\"home_installed_version\">Telepítve</string>\n    <string name=\"home_latest_version\">Legújabb</string>\n    <string name=\"invalid_update_channel\">Érvénytelen frissítési csatorna</string>\n    <string name=\"uninstall_magisk_title\">A Magisk eltávolítása</string>\n    <string name=\"uninstall_magisk_msg\">Az összes modul le lesz tiltva/eltávolítva!\\nA root eltávolításra kerül!\\nA Magisk használatával titkosítatlan belső tárhely újra titkosításra kerül!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Az erőltetett titkosítás megőrzése</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity megőrzése</string>\n    <string name=\"recovery_mode\">Recovery mód</string>\n    <string name=\"install_options_title\">Lehetőségek</string>\n    <string name=\"install_method_title\">Módszer</string>\n    <string name=\"install_next\">Tovább</string>\n    <string name=\"install_start\">Hajrá</string>\n    <string name=\"manager_download_install\">Nyomd a letöltéshez és telepítéshez</string>\n    <string name=\"direct_install\">Közvetlen telepítés (ajánlott)</string>\n    <string name=\"install_inactive_slot\">Telepítés inaktív slot-ba (OTA után)</string>\n    <string name=\"install_inactive_slot_msg\">Eszközöd újraindítás után KÉNYSZERÍTVE az aktuális inaktív slot-ból indulásra!\\nEzt az opciót csak az OTA befejezése után használd.\\nFolytatod?</string>\n    <string name=\"setup_title\">További beállítások</string>\n    <string name=\"select_patch_file\">Fájl kiválasztás és módosítása</string>\n    <string name=\"patch_file_msg\">Válassz ki egy raw image-et (*.img) vagy egy ODIN tar fájlt (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Újraindítás 5 másodpercen belül…</string>\n    <string name=\"flash_screen_title\">Telepítés</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser kérés</string>\n    <string name=\"touch_filtered_warning\">Mivel egy alkalmazás eltakar egy Superuser kérést, a Magisk nem tudja ellenőrizni a válaszod</string>\n    <string name=\"deny\">Elutasít</string>\n    <string name=\"prompt\">Kérdés</string>\n    <string name=\"grant\">Megenged</string>\n    <string name=\"su_warning\">Teljes hozzáférést biztosít az eszközhöz.\\nTagadd meg, ha nem vagy biztos benne!</string>\n    <string name=\"forever\">Végleges</string>\n    <string name=\"once\">Egyszeri</string>\n    <string name=\"tenmin\">10 perc</string>\n    <string name=\"twentymin\">20 perc</string>\n    <string name=\"thirtymin\">30 perc</string>\n    <string name=\"sixtymin\">60 perc</string>\n    <string name=\"su_allow_toast\">%1$s - Superuser jogokat kapott</string>\n    <string name=\"su_deny_toast\">%1$s - Superuser jogok megtagadva</string>\n    <string name=\"su_snack_grant\">%1$s - Superuser jogok megadva</string>\n    <string name=\"su_snack_deny\">%1$s - Superuser jogok megtagadva</string>\n    <string name=\"su_snack_notif_on\">%1$s értesítései bekapcsolva</string>\n    <string name=\"su_snack_notif_off\">%1$s értesítései kikapcsolva</string>\n    <string name=\"su_snack_log_on\">%1$s naplózása bekapcsolva</string>\n    <string name=\"su_snack_log_off\">%1$s naplózása kikapcsolva</string>\n    <string name=\"su_revoke_title\">Visszavonod?</string>\n    <string name=\"su_revoke_msg\">Erősítsd meg %1$s Superuser jogainak visszavonását</string>\n    <string name=\"toast\">Felbukkanó</string>\n    <string name=\"none\">Egyik sem</string>\n\n    <string name=\"superuser_toggle_notification\">Értesítések</string>\n    <string name=\"superuser_toggle_revoke\">Visszavonás</string>\n    <string name=\"superuser_policy_none\">Még egyetlen alkalmazás sem kért Superuser engedélyt.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nincs naplózás, próbáld többet használni a root alkalmazásokat</string>\n    <string name=\"log_data_magisk_none\">A Magisk naplók üresek, ez furcsa</string>\n    <string name=\"menuSaveLog\">Napló mentés</string>\n    <string name=\"menuClearLog\">Napló törlése most</string>\n    <string name=\"logs_cleared\">A napló sikeresen törölve</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Cél UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Rendszerappok megjelenítése</string>\n    <string name=\"show_os_app\">OS-appok megjelenítése</string>\n    <string name=\"hide_filter_hint\">Szűrés névre</string>\n    <string name=\"hide_search\">Keresés</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nincs információ)</string>\n    <string name=\"reboot_userspace\">Lágy újraindítás</string>\n    <string name=\"reboot_recovery\">Újraindítás - Recovery</string>\n    <string name=\"reboot_bootloader\">Újraindítás - Bootloader</string>\n    <string name=\"reboot_download\">Újraindítás - Download</string>\n    <string name=\"reboot_edl\">Újraindítás - EDL</string>\n    <string name=\"module_version_author\">%1$s / %2$s</string>\n    <string name=\"module_state_remove\">Eltávolít</string>\n    <string name=\"module_state_restore\">Visszaállít</string>\n    <string name=\"module_action_install_external\">Telepítés tárhelyről</string>\n    <string name=\"update_available\">Frissítés érhető el</string>\n    <string name=\"suspend_text_riru\">A modul felfüggesztve, mert a %1$s engedélyezve van</string>\n    <string name=\"suspend_text_zygisk\">A modul felfüggesztve, mert a %1$s nincs engedélyezve</string>\n    <string name=\"zygisk_module_unloaded\">A Zygisk modul nem lett betöltve inkompatibilitás miatt</string>\n    <string name=\"module_empty\">Nincs telepített modul</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Téma mód</string>\n    <string name=\"settings_dark_mode_message\">Válaszd ki a stílusodhoz leginkább illő módot!</string>\n    <string name=\"settings_dark_mode_light\">Mindig világos</string>\n    <string name=\"settings_dark_mode_system\">Rendszer követése</string>\n    <string name=\"settings_dark_mode_dark\">Mindig sötét</string>\n    <string name=\"settings_download_path_title\">Letöltési útvonal</string>\n    <string name=\"settings_download_path_message\">A fájlok a következő helyre kerülnek: %1$s</string>\n    <string name=\"settings_hide_app_title\">A Magisk app elrejtése</string>\n    <string name=\"settings_hide_app_summary\">Telepíts egy proxyalkalmazást véletlenszerű csomagazonosítóval és egyéni alkalmazáscímkével</string>\n    <string name=\"settings_restore_app_title\">A Magisk app visszaállítása</string>\n    <string name=\"settings_restore_app_summary\">Oldja fel az app elrejtését, és állítsa vissza az eredeti APK-t</string>\n    <string name=\"language\">Nyelv</string>\n    <string name=\"system_default\">(Rendszer nyelve)</string>\n    <string name=\"settings_check_update_title\">Frissítések ellenőrzése</string>\n    <string name=\"settings_check_update_summary\">Rendszeresen ellenőrzi a frissítéseket a háttérben</string>\n    <string name=\"settings_update_channel_title\">Frissítési csatorna</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Egyéni</string>\n    <string name=\"settings_update_custom_msg\">Adj meg egy egyéni csatorna URL-t</string>\n    <string name=\"settings_zygisk_summary\">Futtassa a Magisk egyes részeit a zigóta démonban</string>\n    <string name=\"settings_denylist_title\">Tiltólista kényszerítés</string>\n    <string name=\"settings_denylist_summary\">A tiltólistán lévő folyamatok minden Magisk módosítást visszaállítanak</string>\n    <string name=\"settings_denylist_config_title\">Tiltólista konfigurálás</string>\n    <string name=\"settings_denylist_config_summary\">Válaszd ki a tiltólistára felvenni kívánt folyamatokat</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Systemless hosts támogatás a hirdetésblokkoló alkalmazásokhoz</string>\n    <string name=\"settings_hosts_toast\">Systemless hosts modul hozzáadva</string>\n    <string name=\"settings_app_name_hint\">Új név</string>\n    <string name=\"settings_app_name_helper\">Az alkalmazás ezzel a névvel lesz újracsomagolva</string>\n    <string name=\"settings_app_name_error\">Érvénytelen formátum</string>\n    <string name=\"settings_su_app_adb\">Appok és ADB</string>\n    <string name=\"settings_su_app\">Csak Appok</string>\n    <string name=\"settings_su_adb\">Csak ADB</string>\n    <string name=\"settings_su_disable\">Kikapcsolva</string>\n    <string name=\"settings_su_request_10\">10 mp</string>\n    <string name=\"settings_su_request_15\">15 mp</string>\n    <string name=\"settings_su_request_20\">20 mp</string>\n    <string name=\"settings_su_request_30\">30 mp</string>\n    <string name=\"settings_su_request_45\">45 mp</string>\n    <string name=\"settings_su_request_60\">60 mp</string>\n    <string name=\"superuser_access\">Superuser hozzáférés</string>\n    <string name=\"auto_response\">Automatikus válasz</string>\n    <string name=\"request_timeout\">Kérés időtúllépése</string>\n    <string name=\"superuser_notification\">Superuser értesítés</string>\n    <string name=\"settings_su_reauth_title\">Frissítés után hitelesítse újra</string>\n    <string name=\"settings_su_reauth_summary\">Az appok frissítés után kérjenek ismét Superuser engedélyeket</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking védelem</string>\n    <string name=\"settings_su_tapjack_summary\">A Superuser kérés párbeszédpanel nem válaszol a bevitelre, ha más ablak vagy átfedés eltakarja</string>\n    <string name=\"settings_customization\">Testreszabás</string>\n    <string name=\"setting_add_shortcut_summary\">Adjon hozzá egy szép parancsikont a kezdőképernyőhöz arra az esetre, ha a nevet és az ikont nehéz felismerni az alkalmazás elrejtése után</string>\n    <string name=\"settings_doh_title\">DNS HTTPS-en keresztül</string>\n    <string name=\"settings_doh_description\">Megoldás DNS-mérgezés esetén egyes országokban</string>\n\n    <string name=\"multiuser_mode\">Többfelhasználós mód</string>\n    <string name=\"settings_owner_only\">Csak az eszköz tulajdonosa</string>\n    <string name=\"settings_owner_manage\">Az eszköz tulajdonosa kezeli</string>\n    <string name=\"settings_user_independent\">Felhasználó-független</string>\n    <string name=\"owner_only_summary\">Csak a tulajdonos rendelkezik root hozzáféréssel</string>\n    <string name=\"owner_manage_summary\">Csak a tulajdonos kezelheti a root hozzáférést és fogadhatja a kéréseket</string>\n    <string name=\"user_independent_summary\">Minden felhasználónak megvannak a saját, külön root szabályai</string>\n\n    <string name=\"mount_namespace_mode\">Névtér felcsatolás módja</string>\n    <string name=\"settings_ns_global\">Globális névtér</string>\n    <string name=\"settings_ns_requester\">Örökölt névtér</string>\n    <string name=\"settings_ns_isolate\">Elszigetelt névtér</string>\n    <string name=\"global_summary\">Minden root szekció a globális csatolási névteret használja</string>\n    <string name=\"requester_summary\">A root munkamenetek öröklik a kérelmező névterét</string>\n    <string name=\"isolate_summary\">Minden root szekciónak saját, elszigetelt névtere lesz</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk frissítések</string>\n    <string name=\"progress_channel\">Folyamat értesítések</string>\n    <string name=\"updated_channel\">A frissítés kész</string>\n    <string name=\"download_complete\">A letöltés kész</string>\n    <string name=\"download_file_error\">Hiba a fájl letöltése közben</string>\n    <string name=\"magisk_update_title\">Magisk frissítés érhető el!</string>\n    <string name=\"updated_title\">A Magisk frissült</string>\n    <string name=\"updated_text\">Érintsd az app megnyitásához</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Igen</string>\n    <string name=\"no\">Nem</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) telepítése</string>\n    <string name=\"download\">Letöltés</string>\n    <string name=\"reboot\">Újraindítás</string>\n    <string name=\"release_notes\">Kiadási jegyzetek</string>\n    <string name=\"flashing\">Flashing…</string>\n    <string name=\"done\">Kész!</string>\n    <string name=\"failure\">Nem sikerült!</string>\n    <string name=\"hide_app_title\">A Magisk app elrejtése…</string>\n    <string name=\"open_link_failed_toast\">Nem található alkalmazás a link megnyitásához</string>\n    <string name=\"complete_uninstall\">Teljes eltávolítás</string>\n    <string name=\"restore_img\">Image-ek visszaállítása</string>\n    <string name=\"restore_img_msg\">Visszaállítás…</string>\n    <string name=\"restore_done\">A helyreállítás megtörtént!</string>\n    <string name=\"restore_fail\">Stock mentés nem létezik!</string>\n    <string name=\"setup_fail\">A beállítás nem sikerült</string>\n    <string name=\"env_fix_title\">További beállítást igényel</string>\n    <string name=\"env_fix_msg\">A Magisk megfelelő működéséhez az eszközön további beállításokra van szükség. Folytatod és újraindítod?</string>\n    <string name=\"setup_msg\">Futási környezet beállítása…</string>\n    <string name=\"unsupport_magisk_title\">Nem támogatott Magisk verzió</string>\n    <string name=\"unsupport_magisk_msg\">Az alkalmazás ezen verziója nem támogatja a %1$s-nál régebbi Magisk-verziókat.\\n\\nAz alkalmazás úgy fog viselkedni, mintha nem lenne telepítve a Magisk. A lehető leghamarabb frissítsd a Magisk-et..</string>\n    <string name=\"unsupport_general_title\">Rendellenes állapot</string>\n    <string name=\"unsupport_system_app_msg\">Az alkalmazás rendszeralkalmazásként való futtatása nem támogatott. Állítsd vissza az alkalmazást felhasználói alkalmazássá.</string>\n    <string name=\"unsupport_other_su_msg\">Nem a Magisktől származó \\\"su\\\" binárist észleltünk. Távolíts el minden konkurens gyökérmegoldást, és/vagy telepítsd újra a Magisk-et.</string>\n    <string name=\"unsupport_external_storage_msg\">A Magisk külső tárhelyre van telepítve. Helyezd át az alkalmazást a belső tárhelyre.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">A rejtett Magisk alkalmazás nem tud tovább működni, mert a root elveszett. Kérjük állítsd vissza az eredeti APK-t.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Adj tárhely engedélyt a funkció használatához</string>\n    <string name=\"install_unknown_denied\">Engedélyezd az \"Ismeretlen alkalmazások telepítése\" lehetőséget a funkció használatához</string>\n    <string name=\"add_shortcut_title\">Parancsikon hozzáadása a kezdőképernyőhöz</string>\n    <string name=\"add_shortcut_msg\">Az alkalmazás elrejtése után nehéz lehet felismerni a nevét és ikonját. Szeretnél egy szép parancsikont hozzáadni a kezdőképernyőhöz?</string>\n    <string name=\"app_not_found\">Nem található alkalmazás ennek a műveletnek a kezelésére</string>\n    <string name=\"reboot_apply_change\">A változtatások alkalmazásához indítsd újra</string>\n    <string name=\"restore_app_confirmation\">Ezzel visszaállítod a rejtett alkalmazást az eredeti alkalmazásra. Tényleg ezt akarod csinálni?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-in/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modul</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Log</string>\n    <string name=\"settings\">Setelan</string>\n    <string name=\"install\">Pasang</string>\n    <string name=\"section_home\">Beranda</string>\n    <string name=\"section_theme\">Tema</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Koneksi tidak tersedia</string>\n    <string name=\"app_changelog\">Catatan perubahan</string>\n    <string name=\"loading\">Memuat…</string>\n    <string name=\"update\">Perbarui</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Tutup</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">Aplikasi</string>\n    <string name=\"home_notice_content\">Unduh Magisk HANYA dari halaman GitHub resmi. Berkas dari sumber yang tidak dikenal dapat berbahaya!</string>\n    <string name=\"home_support_title\">Dukung Kami</string>\n    <string name=\"home_follow_title\">Ikuti Kami</string>\n    <string name=\"home_item_source\">Sumber</string>\n    <string name=\"home_support_content\">Magisk akan selalu menjadi aplikasi gratis dan serta bersumber terbuka. Bagaimanapun juga, Anda dapat menunjukan kepedulian Anda kepada kami dengan memberikan sedikit donasi.</string>\n    <string name=\"home_installed_version\">Terpasang</string>\n    <string name=\"home_latest_version\">Terbaru</string>\n    <string name=\"invalid_update_channel\">Saluran pembaruan tidak valid</string>\n    <string name=\"uninstall_magisk_title\">Hapus Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Semua modul akan dinonaktifkan/dihapus!\\nRoot akan dihapus!\\nSemua data internal yang tidak dienkripsi melalui penggunaan Magisk akan dienkripsi ulang!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Pertahankan enkripsi paksa</string>\n    <string name=\"keep_dm_verity\">Pertahankan AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mode Recovery</string>\n    <string name=\"install_options_title\">Opsi</string>\n    <string name=\"install_method_title\">Metode</string>\n    <string name=\"install_next\">Berikutnya</string>\n    <string name=\"install_start\">Mulai</string>\n    <string name=\"manager_download_install\">Sentuh untuk mengunduh dan memasang</string>\n    <string name=\"direct_install\">Langsung pasang (Disarankan)</string>\n    <string name=\"install_inactive_slot\">Pasang ke slot nonaktif (Setelah OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Perangkat Anda akan DIPAKSA untuk boot ke slot nonaktif saat ini setelah dimulai ulang!\\nGunakan opsi ini hanya setelah OTA selesai.\\nLanjutkan?</string>\n    <string name=\"setup_title\">Penyiapan tambahan</string>\n    <string name=\"select_patch_file\">Pilih dan tambal berkas</string>\n    <string name=\"patch_file_msg\">Pilih mentahan image (*.img) atau berkas tar ODIN (*.tar) atau payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Memulai ulang dalam 5 detik…</string>\n    <string name=\"flash_screen_title\">Pemasangan</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Permintaan Superuser</string>\n    <string name=\"touch_filtered_warning\">Magisk tidak dapat memverifikasi respons Anda, karena ada aplikasi yang menutupi permintaan superuser.</string>\n    <string name=\"deny\">Tolak</string>\n    <string name=\"prompt\">Ajukan</string>\n    <string name=\"restrict\">Batasi</string>\n    <string name=\"grant\">Izinkan</string>\n    <string name=\"su_warning\">Berikan akses penuh ke perangkat Anda.\\nTolak jika Anda tidak yakin!</string>\n    <string name=\"forever\">Selamanya</string>\n    <string name=\"once\">Sekali</string>\n    <string name=\"tenmin\">10 menit</string>\n    <string name=\"twentymin\">20 menit</string>\n    <string name=\"thirtymin\">30 menit</string>\n    <string name=\"sixtymin\">60 menit</string>\n    <string name=\"su_allow_toast\">%1$s diberikan izin superuser</string>\n    <string name=\"su_deny_toast\">%1$s ditolak izin superuser</string>\n    <string name=\"su_snack_grant\">Izin superuser %1$s diberikan</string>\n    <string name=\"su_snack_deny\">Izin superuser %1$s ditolak</string>\n    <string name=\"su_snack_notif_on\">Notifikasi %1$s diaktifkan</string>\n    <string name=\"su_snack_notif_off\">Notifikasi %1$s dinonaktifkan</string>\n    <string name=\"su_snack_log_on\">Pencatatan log %1$s diaktifkan</string>\n    <string name=\"su_snack_log_off\">Pencatatan log %1$s dinonaktifkan</string>\n    <string name=\"su_revoke_title\">Cabut?</string>\n    <string name=\"su_revoke_msg\">Konfirmasi untuk mencabut izin %1$s</string>\n    <string name=\"toast\">Pesan singkat</string>\n    <string name=\"none\">Tidak ada</string>\n    <string name=\"superuser_toggle_notification\">Notifikasi</string>\n    <string name=\"superuser_toggle_revoke\">Cabut</string>\n    <string name=\"superuser_policy_none\">Belum ada aplikasi yang meminta izin superuser.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Belum ada log. Coba gunakan aplikasi root Anda lebih sering.</string>\n    <string name=\"log_data_magisk_none\">Log Magisk kosong, ini aneh.</string>\n    <string name=\"menuSaveLog\">Simpan log</string>\n    <string name=\"menuClearLog\">Bersihkan log</string>\n    <string name=\"logs_cleared\">Log berhasil dibersihkan</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID Target: %1$d</string>\n    <string name=\"target_pid\">PID Target: %s</string>\n    <string name=\"selinux_context\">Konteks SELinux: %s</string>\n    <string name=\"supp_group\">Grup tambahan: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Tampilkan aplikasi sistem</string>\n    <string name=\"show_os_app\">Tampilkan aplikasi OS</string>\n    <string name=\"hide_filter_hint\">Saring berdasarkan nama</string>\n    <string name=\"hide_search\">Telusuri</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Info tidak tersedia)</string>\n    <string name=\"reboot_userspace\">Mulai ulang secara halus</string>\n    <string name=\"reboot_recovery\">Mulai ulang ke Recovery</string>\n    <string name=\"reboot_bootloader\">Mulai ulang ke Bootloader</string>\n    <string name=\"reboot_download\">Mulai ulang ke Download</string>\n    <string name=\"reboot_edl\">Mulai ulang ke EDL</string>\n    <string name=\"reboot_safe_mode\">Mode aman</string>\n    <string name=\"module_version_author\">%1$s oleh %2$s</string>\n    <string name=\"module_state_remove\">Hapus</string>\n    <string name=\"module_action\">Aksi</string>\n    <string name=\"module_state_restore\">Pulihkan</string>\n    <string name=\"module_action_install_external\">Pasang dari penyimpanan</string>\n    <string name=\"update_available\">Pembaruan tersedia</string>\n    <string name=\"suspend_text_riru\">Modul ditangguhkan karena %1$s diaktifkan</string>\n    <string name=\"suspend_text_zygisk\">Modul ditangguhkan karena %1$s tidak diaktifkan</string>\n    <string name=\"zygisk_module_unloaded\">Modul Zygisk tidak dimuat karena tidak kompatibel</string>\n    <string name=\"module_empty\">Tidak ada modul terpasang</string>\n    <string name=\"confirm_install\">Pasang modul %1$s?</string>\n    <string name=\"confirm_install_title\">Konfirmasi pemasangan</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mode tema</string>\n    <string name=\"settings_dark_mode_message\">Pilih mode yang paling sesuai dengan gaya Anda!</string>\n    <string name=\"settings_dark_mode_light\">Selalu terang</string>\n    <string name=\"settings_dark_mode_system\">Ikuti sistem</string>\n    <string name=\"settings_dark_mode_dark\">Selalu gelap</string>\n    <string name=\"settings_download_path_title\">Lokasi unduhan</string>\n    <string name=\"settings_download_path_message\">Berkas akan disimpan ke %1$s</string>\n    <string name=\"settings_hide_app_title\">Sembunyikan aplikasi Magisk</string>\n    <string name=\"settings_hide_app_summary\">Pasang aplikasi proxy dengan ID paket acak dan label aplikasi khusus</string>\n    <string name=\"settings_restore_app_title\">Pulihkan aplikasi Magisk</string>\n    <string name=\"settings_restore_app_summary\">Tampilkan kembali aplikasi yang disembunyikan dan pulihkan APK aslinya</string>\n    <string name=\"language\">Bahasa</string>\n    <string name=\"system_default\">(Bawaan sistem)</string>\n    <string name=\"settings_check_update_title\">Periksa pembaruan</string>\n    <string name=\"settings_check_update_summary\">Memeriksa pembaruan secara berkala di latar belakang</string>\n    <string name=\"settings_update_channel_title\">Saluran pembaruan</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Khusus</string>\n    <string name=\"settings_update_custom_msg\">Masukkan URL saluran khusus</string>\n    <string name=\"settings_zygisk_summary\">Menjalankan sebagian komponen Magisk di daemon Zygote</string>\n    <string name=\"settings_denylist_title\">Paksa DenyList</string>\n    <string name=\"settings_denylist_summary\">Proses pada denylist akan mengembalikan semua modifikasi Magisk</string>\n    <string name=\"settings_denylist_config_title\">Konfigurasi DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Pilih proses yang akan disertakan ke dalam denylist</string>\n    <string name=\"settings_hosts_title\">Hosts systemless</string>\n    <string name=\"settings_hosts_summary\">Dukungan hosts systemless untuk aplikasi pemblokir iklan</string>\n    <string name=\"settings_hosts_toast\">Menambahkan modul hosts systemless</string>\n    <string name=\"settings_app_name_hint\">Nama baru</string>\n    <string name=\"settings_app_name_helper\">Aplikasi akan dikemas ulang dengan nama tersebut</string>\n    <string name=\"settings_app_name_error\">Format tidak valid</string>\n    <string name=\"settings_su_app_adb\">Aplikasi dan ADB</string>\n    <string name=\"settings_su_app\">Hanya Aplikasi</string>\n    <string name=\"settings_su_adb\">Hanya ADB</string>\n    <string name=\"settings_su_disable\">Dinonaktifkan</string>\n    <string name=\"settings_su_request_10\">10 detik</string>\n    <string name=\"settings_su_request_15\">15 detik</string>\n    <string name=\"settings_su_request_20\">20 detik</string>\n    <string name=\"settings_su_request_30\">30 detik</string>\n    <string name=\"settings_su_request_45\">45 detik</string>\n    <string name=\"settings_su_request_60\">60 detik</string>\n    <string name=\"superuser_access\">Akses superuser</string>\n    <string name=\"auto_response\">Respons otomatis</string>\n    <string name=\"request_timeout\">Batas waktu permintaan</string>\n    <string name=\"superuser_notification\">Notifikasi superuser</string>\n    <string name=\"settings_su_reauth_title\">Autentikasikan ulang setelah pembaruan</string>\n    <string name=\"settings_su_reauth_summary\">Minta izin akses superuser lagi setelah memperbarui aplikasi</string>\n    <string name=\"settings_su_tapjack_title\">Perlindungan tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">Dialog permintaan superuser tidak akan merespons input saat tertutup oleh jendela atau overlay lain</string>\n    <string name=\"settings_su_auth_title\">Autentikasi pengguna</string>\n    <string name=\"settings_su_auth_summary\">Minta autentikasi pengguna selama permintaan superuser</string>\n    <string name=\"settings_su_auth_insecure\">Tidak ada metode autentikasi yang dikonfigurasi pada perangkat</string>\n    <string name=\"settings_su_restrict_title\">Batasi kemampuan root</string>\n    <string name=\"settings_su_restrict_summary\">Secara bawaan akan membatasi aplikasi superuser baru. Peringatan: dapat menyebabkan sebagian besar aplikasi tidak berfungsi. Jangan aktifkan kecuali Anda memahami risikonya.</string>\n    <string name=\"settings_customization\">Personalisasi</string>\n    <string name=\"setting_add_shortcut_summary\">Tambahkan pintasan yang menarik ke layar utama jika nama dan ikon sulit dikenali setelah menyembunyikan aplikasi</string>\n    <string name=\"settings_doh_title\">DNS melalui HTTPS</string>\n    <string name=\"settings_doh_description\">Solusi mengatasi masalah DNS di beberapa negara</string>\n    <string name=\"settings_random_name_title\">Acak nama output</string>\n    <string name=\"settings_random_name_description\">Acak nama berkas output dari image yang dipatch dan berkas tar untuk mencegah deteksi</string>\n    <string name=\"multiuser_mode\">Mode multi pengguna</string>\n    <string name=\"settings_owner_only\">Hanya pemilik perangkat</string>\n    <string name=\"settings_owner_manage\">Dikelola pemilik perangkat</string>\n    <string name=\"settings_user_independent\">Independen per pengguna</string>\n    <string name=\"owner_only_summary\">Hanya pemilik yang memiliki akses root</string>\n    <string name=\"owner_manage_summary\">Hanya pemilik yang dapat mengelola akses root dan menerima permintaan</string>\n    <string name=\"user_independent_summary\">Setiap pengguna memiliki aturan root terpisah</string>\n    <string name=\"mount_namespace_mode\">Ruang nama mount</string>\n    <string name=\"settings_ns_global\">Global</string>\n    <string name=\"settings_ns_requester\">Diwariskan</string>\n    <string name=\"settings_ns_isolate\">Terpisah</string>\n    <string name=\"global_summary\">Semua sesi root menggunakan ruang nama mount global</string>\n    <string name=\"requester_summary\">Sesi root akan mewarisi ruang nama dari pemintanya</string>\n    <string name=\"isolate_summary\">Setiap sesi root akan memiliki ruang nama tersendiri</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Pembaruan Magisk</string>\n    <string name=\"progress_channel\">Notifikasi kemajuan</string>\n    <string name=\"updated_channel\">Pembaruan selesai</string>\n    <string name=\"download_complete\">Unduhan selesai</string>\n    <string name=\"download_file_error\">Kesalahan saat mengunduh berkas</string>\n    <string name=\"magisk_update_title\">Pembaruan Magisk tersedia!</string>\n    <string name=\"updated_title\">Magisk diperbarui</string>\n    <string name=\"updated_text\">Ketuk untuk buka aplikasi</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ya</string>\n    <string name=\"no\">Tidak</string>\n    <string name=\"repo_install_title\">Pasang %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Unduh</string>\n    <string name=\"reboot\">Mulai ulang</string>\n    <string name=\"close\">Tutup</string>\n    <string name=\"release_notes\">Catatan rilis</string>\n    <string name=\"flashing\">Memasang…</string>\n    <string name=\"running\">Memproses…</string>\n    <string name=\"done\">Selesai!</string>\n    <string name=\"done_action\">Aksi %1$s selesai</string>\n    <string name=\"failure\">Gagal!</string>\n    <string name=\"hide_app_title\">Menyembunyikan aplikasi Magisk…</string>\n    <string name=\"open_link_failed_toast\">Tidak ditemukan aplikasi untuk membuka tautan</string>\n    <string name=\"complete_uninstall\">Penghapusan penuh</string>\n    <string name=\"restore_img\">Pulihkan image</string>\n    <string name=\"restore_img_msg\">Memulihkan…</string>\n    <string name=\"restore_done\">Memulihkan selesai!</string>\n    <string name=\"restore_fail\">Cadangan asli tidak ada!</string>\n    <string name=\"setup_fail\">Penyiapan gagal</string>\n    <string name=\"env_fix_title\">Perlu penyiapan tambahan</string>\n    <string name=\"env_fix_msg\">Perangkat Anda memerlukan penyiapan tambahan agar Magisk dapat berfungsi dengan benar. Apakah Anda ingin melanjutkan dan memulai ulang?</string>\n    <string name=\"env_full_fix_msg\">Perangkat Anda perlu melakukan pemasangan ulang Magisk agar berfungsi dengan benar. Harap pasang ulang Magisk melalui aplikasi, mode recovery tidak dapat memperoleh informasi perangkat yang benar.</string>\n    <string name=\"setup_msg\">Menyiapkan lingkungan…</string>\n    <string name=\"unsupport_magisk_title\">Versi Magisk tidak didukung</string>\n    <string name=\"unsupport_magisk_msg\">Versi aplikasi ini tidak mendukung versi Magisk yang lebih rendah dari %1$s.\\n\\nAplikasi akan berperilaku seolah-olah tidak ada Magisk yang terpasang. Harap perbarui Magisk sesegera mungkin.</string>\n    <string name=\"unsupport_general_title\">Status abnormal</string>\n    <string name=\"unsupport_system_app_msg\">Menjalankan aplikasi ini sebagai aplikasi sistem tidak didukung. Harap kembalikan ke aplikasi pengguna.</string>\n    <string name=\"unsupport_other_su_msg\">Sebuah biner \\\"su\\\" bukan berasal dari Magisk telah terdeteksi. Harap hapus solusi root yang bersaing dan/atau pasang ulang Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk dipasang ke penyimpanan eksternal. Harap pindahkan aplikasi ke penyimpanan internal.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Aplikasi Magisk tersembunyi tidak dapat berfungsi lagi karena akses root hilang. Harap pulihkan APK asli.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Berikan izin penyimpanan untuk mengaktifkan fungsi ini</string>\n    <string name=\"post_notifications_denied\">Berikan izin notifikasi untuk mengaktifkan fungsi ini</string>\n    <string name=\"install_unknown_denied\">Izinkan \\\"Instal aplikasi tidak dikenal\\\" untuk mengaktifkan fungsi ini</string>\n    <string name=\"add_shortcut_title\">Tambahkan pintasan ke layar utama</string>\n    <string name=\"add_shortcut_msg\">Setelah menyembunyikan aplikasi ini, nama dan ikonnya mungkin sulit dikenali. Apakah Anda ingin menambahkan pintasan ke layar utama?</string>\n    <string name=\"app_not_found\">Tidak ditemukan aplikasi untuk menangani tindakan ini</string>\n    <string name=\"reboot_apply_change\">Mulai ulang untuk menerapkan perubahan</string>\n    <string name=\"restore_app_confirmation\">Ini akan mengembalikan aplikasi tersembunyi ke aslinya. Apakah Anda benar-benar ingin melakukan ini?</string>\n\n</resources>\n\n"
  },
  {
    "path": "app/core/src/main/res/values-it/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduli</string>\n    <string name=\"superuser\">Accesso root</string>\n    <string name=\"logs\">Registro eventi</string>\n    <string name=\"settings\">Impostazioni</string>\n    <string name=\"install\">Installa</string>\n    <string name=\"section_home\">Home</string>\n    <string name=\"section_theme\">Temi</string>\n    <string name=\"denylist\">Lista di blocco</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Connessione non disponibile</string>\n    <string name=\"app_changelog\">Changelog</string>\n    <string name=\"loading\">Caricamento…</string>\n    <string name=\"update\">Aggiorna</string>\n    <string name=\"not_available\">N/D</string>\n    <string name=\"hide\">Nascondi</string>\n    <string name=\"home_package\">Pacchetto</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">Scarica Magisk SOLTANTO dalla pagina GitHub ufficiale. I file provenienti da fonti sconosciute possono essere dannosi!</string>\n    <string name=\"home_support_title\">Supportaci</string>\n    <string name=\"home_follow_title\">Seguici</string>\t\t\t\t\t\t\t\t\t\t\t\t   \n    <string name=\"home_item_source\">Codice sorgente</string>\n    <string name=\"home_support_content\">Magisk è, e sempre sarà, gratuito ed open source. Puoi comunque mostrarci il tuo apprezzamento facendo una donazione.</string>\n    <string name=\"home_installed_version\">Installata</string>\n    <string name=\"home_latest_version\">Ultima</string>\n    <string name=\"invalid_update_channel\">Canale di aggiornamento non valido</string>\n    <string name=\"uninstall_magisk_title\">Disinstalla Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Tutti i moduli verranno disabilitati/rimossi!\\nIl root verrà rimosso!\\nGli spazi di archiviazione interni decriptati tramite l\\'uso di Magisk verranno nuovamente criptati!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Mantieni crittografia forzata</string>\n    <string name=\"keep_dm_verity\">Mantieni AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Modalità recovery</string>\n    <string name=\"install_options_title\">Opzioni</string>\n    <string name=\"install_method_title\">Metodo</string>\n    <string name=\"install_next\">Avanti</string>\n    <string name=\"install_start\">Andiamo!</string>\n    <string name=\"manager_download_install\">Apri questa notifica per scaricare e installare</string>\n    <string name=\"direct_install\">Installazione diretta (raccomandata)</string>\n    <string name=\"install_inactive_slot\">Installa nello slot inattivo (dopo un aggiornamento OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Questo dispositivo verrà FORZATO ad avviarsi usando lo slot inattivo!\\nUsa questo metodo solo dopo che un aggiornamento OTA è stato installato.\\nVuoi continuare?</string>\n    <string name=\"setup_title\">Configurazione aggiuntiva</string>\n    <string name=\"select_patch_file\">Seleziona e aggiorna un file</string>\n    <string name=\"patch_file_msg\">Seleziona un\\'immagine in formato .img, un file .tar di ODIN o un file payload.bin</string>\n    <string name=\"reboot_delay_toast\">Riavvio fra 5 secondi…</string>\n    <string name=\"flash_screen_title\">Installazione</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Richiesta di accesso root</string>\n    <string name=\"touch_filtered_warning\">Magisk non può verificare la tua risposta perché un\\'app sta oscurando una richiesta di accesso root</string>\n    <string name=\"deny\">Nega</string>\n    <string name=\"prompt\">Chiedi</string>\n    <string name=\"grant\">Consenti</string>\n    <string name=\"su_warning\">Concede il pieno accesso al dispositivo.\\nNega se non sei sicuro!</string>\n    <string name=\"forever\">Sempre</string>\n    <string name=\"once\">Una volta</string>\n    <string name=\"tenmin\">10 minuti</string>\n    <string name=\"twentymin\">20 minuti</string>\n    <string name=\"thirtymin\">30 minuti</string>\n    <string name=\"sixtymin\">60 minuti</string>\n    <string name=\"su_allow_toast\">%1$s ha ottenuto i permessi di root</string>\n    <string name=\"su_deny_toast\">%1$s non ha ottenuto i permessi di root</string>\n    <string name=\"su_snack_grant\">%1$s potrà ottenere i permessi di root</string>\n    <string name=\"su_snack_deny\">%1$s non potrà più ottenere i permessi di root</string>\n    <string name=\"su_snack_notif_on\">Notifiche per %1$s abilitate</string>\n    <string name=\"su_snack_notif_off\">Notifiche per %1$s disabilitate</string>\n    <string name=\"su_snack_log_on\">Registrazione eventi abilitata per %1$s</string>\n    <string name=\"su_snack_log_off\">Registrazione eventi disabilitata per %1$s</string>\n    <string name=\"su_revoke_title\">Conferma revoca</string>\n    <string name=\"su_revoke_msg\">Confermi la revoca dei permessi a %1$s?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Nessuno</string>\n\n    <string name=\"superuser_toggle_notification\">Notifiche</string>\n    <string name=\"superuser_toggle_revoke\">Revoca</string>\n    <string name=\"superuser_policy_none\">Nessuna app ha ancora chiesto i permessi di root.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Non ci sono eventi, prova ad utilizzare un\\'app che richiede i permessi di root.</string>\n    <string name=\"log_data_magisk_none\">Il registro eventi di Magisk è vuoto, questo è davvero strano.</string>\n    <string name=\"menuSaveLog\">Salva registro eventi</string>\n    <string name=\"menuClearLog\">Svuota il registro eventi</string>\n    <string name=\"logs_cleared\">Registro eventi svuotato correttamente.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Target UID: %1$d</string>\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Mostra app di sistema</string>\n    <string name=\"show_os_app\">Mostra app del sistema operativo</string>\n    <string name=\"hide_filter_hint\">Filtra per nome</string>\n    <string name=\"hide_search\">Cerca</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nessuna informazione)</string>\n    <string name=\"reboot_userspace\">Riavvio veloce</string>\n    <string name=\"reboot_recovery\">Riavvia in recovery</string>\n    <string name=\"reboot_bootloader\">Riavvia in bootloader</string>\n    <string name=\"reboot_download\">Riavvia in modalità download</string>\n    <string name=\"reboot_edl\">Riavvia in modalità EDL</string>\n    <string name=\"module_version_author\">%1$s di %2$s</string>\n    <string name=\"module_state_remove\">Rimuovi</string>\n    <string name=\"module_state_restore\">Ripristina</string>\n    <string name=\"module_action_install_external\">Installa dal dispositivo</string>\n    <string name=\"update_available\">Aggiornamento disponibile</string>\n    <string name=\"suspend_text_riru\">Modulo sospeso perché %1$s è abilitato</string>\n    <string name=\"suspend_text_zygisk\">Modulo sospeso perché %1$s non è abilitato</string>\n    <string name=\"zygisk_module_unloaded\">Modulo Zygisk non caricato a causa di incompatibilità</string>\n    <string name=\"module_empty\">Nessun modulo installato</string>\n\n    <!--Settings -->\n    <string name=\"confirm_install\">Vuoi installare il modulo %1$s?</string>\n    <string name=\"confirm_install_title\">Conferma installazione</string>\n    <string name=\"settings_dark_mode_title\">Impostazioni tema</string>\n    <string name=\"settings_dark_mode_message\">Seleziona il tema che si adatta meglio al tuo stile!</string>\n    <string name=\"settings_dark_mode_light\">Chiaro</string>\n    <string name=\"settings_dark_mode_system\">Adatta al sistema</string>\n    <string name=\"settings_dark_mode_dark\">Scuro</string>\n    <string name=\"settings_download_path_title\">Percorso di download</string>\n    <string name=\"settings_download_path_message\">I file verranno salvati in %1$s</string>\n    <string name=\"settings_hide_app_title\">Nascondi l\\'app di Magisk</string>\n    <string name=\"settings_hide_app_summary\">Installa un\\'app \\\"proxy\\\" con un ID pacchetto casuale e un nome personalizzato</string>\n    <string name=\"settings_restore_app_title\">Ripristina l\\'app di Magisk</string>\n    <string name=\"settings_restore_app_summary\">Rendi l\\'app nuovamente rilevabile e ripristina l\\'APK originale</string>\n    <string name=\"language\">Lingua</string>\n    <string name=\"system_default\">(Sistema)</string>\n    <string name=\"settings_check_update_title\">Controlla aggiornamenti</string>\n    <string name=\"settings_check_update_summary\">Controlla automaticamente gli aggiornamenti in background</string>\n    <string name=\"settings_update_channel_title\">Canale di aggiornamento</string>\n    <string name=\"settings_update_stable\">Stabile</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Personalizzato</string>\n    <string name=\"settings_update_custom_msg\">Inserisci l\\'URL del canale personalizzato</string>\n    <string name=\"settings_zygisk_summary\">Esegui parti di Magisk nel processo zygote</string>\n    <string name=\"settings_denylist_title\">Applica lista di blocco</string>\n    <string name=\"settings_denylist_summary\">Tutte le modifiche apportate da Magisk verranno annullate per i processi in lista di blocco</string>\n    <string name=\"settings_denylist_config_title\">Configura lista di blocco</string>\n    <string name=\"settings_denylist_config_summary\">Seleziona i processi da includere nella lista di blocco</string>\n    <string name=\"settings_hosts_title\">File hosts systemless</string>\n    <string name=\"settings_hosts_summary\">Supporto al file hosts systemless per le app che bloccano le pubblicità</string>\n    <string name=\"settings_hosts_toast\">Aggiunto modulo per il supporto al file hosts systemless</string>\n    <string name=\"settings_app_name_hint\">Nuovo nome</string>\n    <string name=\"settings_app_name_helper\">L\\'app sarà ricreata con questo nome</string>\n    <string name=\"settings_app_name_error\">Formato non valido</string>\n    <string name=\"settings_su_app_adb\">App e ADB</string>\n    <string name=\"settings_su_app\">Solo app</string>\n    <string name=\"settings_su_adb\">Solo ADB</string>\n    <string name=\"settings_su_disable\">Disabilitato</string>\n    <string name=\"settings_su_request_10\">10 secondi</string>\n    <string name=\"settings_su_request_15\">15 secondi</string>\n    <string name=\"settings_su_request_20\">20 secondi</string>\n    <string name=\"settings_su_request_30\">30 secondi</string>\n    <string name=\"settings_su_request_45\">45 secondi</string>\n    <string name=\"settings_su_request_60\">60 secondi</string>\n    <string name=\"superuser_access\">Accesso root</string>\n    <string name=\"auto_response\">Azione predefinita</string>\n    <string name=\"request_timeout\">Timeout richiesta</string>\n    <string name=\"superuser_notification\">Notifica di accesso root</string>\n    <string name=\"settings_su_reauth_title\">Riautentica dopo aggiornamento</string>\n    <string name=\"settings_su_reauth_summary\">Richiedi nuovamente i permessi di root dopo l\\'aggiornamento di un\\'app</string>\n    <string name=\"settings_su_tapjack_title\">Protezione anti-tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">La schermata di richiesta dei permessi di root non risponderà al tocco mentre è oscurata da altre finestre o elementi in sovraimpressione</string>\n    <string name=\"settings_su_auth_title\">Autenticazione utente</string>\n    <string name=\"settings_su_auth_summary\">Richiedi l\\'autenticazione dell\\'utente per le richieste di accesso root</string>\n    <string name=\"settings_su_auth_insecure\">Sul dispositivo non è configurato alcun metodo di autenticazione</string>\n    <string name=\"settings_customization\">Personalizzazione</string>\n    <string name=\"setting_add_shortcut_summary\">Aggiungi un collegamento alla schermata iniziale se il nome e l\\'icona sono difficili da riconoscere dopo aver nascosto l\\'app</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Aggira il DNS poisoning in alcune nazioni</string>\n\n    <string name=\"multiuser_mode\">Modalità multiutente</string>\n    <string name=\"settings_owner_only\">Solo per il proprietario del dispositivo</string>\n    <string name=\"settings_owner_manage\">Gestito dal proprietario del dispositivo</string>\n    <string name=\"settings_user_independent\">Indipendente dall\\'utente</string>\n    <string name=\"owner_only_summary\">Solo il proprietario possiede i permessi di root</string>\n    <string name=\"owner_manage_summary\">Solo il proprietario può gestire l\\'accesso root e ricevere le richieste dalle app</string>\n    <string name=\"user_independent_summary\">Ogni utente ha le proprie regole di root indipendenti</string>\n\n    <string name=\"mount_namespace_mode\">Modalità mount namespace</string>\n    <string name=\"settings_ns_global\">Namespace globale</string>\n    <string name=\"settings_ns_requester\">Namespace ereditato</string>\n    <string name=\"settings_ns_isolate\">Namespace isolato</string>\n    <string name=\"global_summary\">Tutte le sessioni di root erediteranno il namespace globale</string>\n    <string name=\"requester_summary\">Le sessioni di root erediteranno il namespace del loro richiedente</string>\n    <string name=\"isolate_summary\">Ogni sessione di root avrà il suo namespace isolato</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Aggiornamenti di Magisk</string>\n    <string name=\"progress_channel\">Notifiche di avanzamento</string>\n    <string name=\"updated_channel\">Aggiornamento completato</string>\n    <string name=\"download_complete\">Download completato</string>\n    <string name=\"download_file_error\">Errore durante il download del file</string>\n    <string name=\"magisk_update_title\">È disponibile un aggiornamento di Magisk!</string>\n    <string name=\"updated_title\">Magisk è stato aggiornato</string>\n    <string name=\"updated_text\">Tocca per aprire l\\'app</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Sì</string>\n    <string name=\"no\">No</string>\n    <string name=\"repo_install_title\">Installazione di %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Download</string>\n    <string name=\"reboot\">Riavvia</string>\n    <string name=\"release_notes\">Note di rilascio</string>\n    <string name=\"flashing\">Flash in corso…</string>\n    <string name=\"done\">Completato!</string>\n    <string name=\"failure\">Fallito</string>\n    <string name=\"hide_app_title\">Sto nascondendo l\\'app di Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nessuna app disponibile per aprire il link</string>\n    <string name=\"complete_uninstall\">Disinstalla completamente</string>\n    <string name=\"restore_img\">Ripristina immagini</string>\n    <string name=\"restore_img_msg\">Ripristino…</string>\n    <string name=\"restore_done\">Ripristino completato!</string>\n    <string name=\"restore_fail\">Non esiste un backup dell\\'immagine di boot originale!</string>\n    <string name=\"setup_fail\">Configurazione fallita</string>\n    <string name=\"env_fix_title\">Configurazione aggiuntiva richiesta</string>\n    <string name=\"env_fix_msg\">Il tuo dispositivo necessita di una configurazione aggiuntiva per far funzionare Magisk correttamente. Vuoi procedere e riavviare il dispositivo?</string>\n    <string name=\"env_full_fix_msg\">È necessario eseguire di nuovo il flash di Magisk per farlo funzionare correttamente. Reinstalla Magisk utilizzando l\\'app, in modalità recovery non è possibile ottenere informazioni corrette sul dispositivo.</string>\n    <string name=\"setup_msg\">Configurazione dell\\'ambiente in corso…</string>\n    <string name=\"unsupport_magisk_title\">Versione di Magisk non supportata</string>\n    <string name=\"unsupport_magisk_msg\">Questa versione dell\\'app non supporta le versioni di Magisk inferiori a %1$s.\\n\\nL\\'app si comporterà come se Magisk non fosse installato: aggiornalo il prima possibile.</string>\n    <string name=\"unsupport_general_title\">Stato anomalo rilevato</string>\n    <string name=\"unsupport_system_app_msg\">L\\'esecuzione di quest\\'app come app di sistema non è supportata. Reinstallala come applicazione utente.</string>\n    <string name=\"unsupport_other_su_msg\">È stato rilevato un binario \\\"su\\\" che non appartiene a Magisk. Rimuovi qualsiasi altro sistema di root e/o reinstalla Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk è installato nella memoria esterna. Sposta l\\'app nella memoria interna.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Dal momento che i permessi di root sono stati persi, l\\'app di Magisk nascosta non può più funzionare. Ripristina l\\'APK originale.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Consenti l\\'accesso alla memoria del dispositivo per abilitare questa opzione</string>\n    <string name=\"post_notifications_denied\">Consenti l\\'invio di notifiche per abilitare questa opzione</string>\n    <string name=\"install_unknown_denied\">Consenti l\\'installazione di app sconosciute per abilitare questa funzionalità</string>\n    <string name=\"add_shortcut_title\">Aggiungi collegamento alla schermata iniziale</string>\n    <string name=\"add_shortcut_msg\">Dopo aver nascosto quest\\'app, il suo nome e la sua icona potrebbero diventare difficili da riconoscere. Vuoi aggiungere una scorciatoia alla schermata principale?</string>\n    <string name=\"app_not_found\">Non è stata trovata un\\'app per gestire questa azione</string>\n    <string name=\"reboot_apply_change\">Riavvia per applicare le modifiche</string>\n    <string name=\"restore_app_confirmation\">L\\'app nascosta verrà sostituita con la versione originale. Sei sicuro di voler continuare?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-iw/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">מודולים</string>\n    <string name=\"superuser\">משתמש על</string>\n    <string name=\"logs\">Log</string>\n    <string name=\"settings\">הגדרות</string>\n    <string name=\"install\">התקנה</string>\n    <string name=\"section_home\">בית</string>\n    <string name=\"section_theme\">עיצוב</string>\n    <string name=\"denylist\">רשימת דחייה</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">אין חיבור זמין</string>\n    <string name=\"app_changelog\">רשימת שינויים</string>\n    <string name=\"loading\">טוען…</string>\n    <string name=\"update\">עדכון</string>\n    <string name=\"not_available\">לא זמין</string>\n    <string name=\"hide\">הסתרה</string>\n    <string name=\"home_package\">חבילה</string>\n    <string name=\"home_app_title\">יישום</string>\n    <string name=\"home_notice_content\">יש להוריד את Magisk רק מהדף הרשמי של GitHub. קבצים ממקורות לא ידועים יכולים להיות זדוניים!</string>\n    <string name=\"home_support_title\">תמיכה בנו</string>\n    <string name=\"home_follow_title\">עקבו אחרינו</string>\n    <string name=\"home_item_source\">מקור</string>\n    <string name=\"home_support_content\">Magisk היה ותמיד יהיה בקוד פתוח. עם זאת באפשרותך להראות לנו שאכפת לך על ידי שליחת תרומה קטנה.</string>\n    <string name=\"home_installed_version\">מותקנת</string>\n    <string name=\"home_latest_version\">אחרונה</string>\n    <string name=\"invalid_update_channel\">ערוץ עדכון לא חוקי</string>\n    <string name=\"uninstall_magisk_title\">הסרת Magisk</string>\n    <string name=\"uninstall_magisk_msg\">כל המודולים יושבתו/יוסרו!\\nגישת Root תושבת!\\nהנתונים שלך עשויים להיות מוצפנים אם לא הוצפנו כבר!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">שמירה על הצפנה בכח</string>\n    <string name=\"keep_dm_verity\">שמירה על AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">מצב Recovery</string>\n    <string name=\"install_options_title\">אפשרויות</string>\n    <string name=\"install_method_title\">שיטה</string>\n    <string name=\"install_next\">הבא</string>\n    <string name=\"install_start\">צא לדרך</string>\n    <string name=\"manager_download_install\">לחיצה להורדה והתקנה</string>\n    <string name=\"direct_install\">התקנה ישירה (מומלץ)</string>\n    <string name=\"install_inactive_slot\">התקנה לסלוט לא פעיל (לאחר OTA)</string>\n    <string name=\"install_inactive_slot_msg\">ההתקן שלך ייאלץ אתחול לסלוט הלא פעיל הנוכחי שלך לאחר הפעלה מחדש!\\nיש להשתמש באפשרות זו רק לאחר ביצוע OTA בלבד.\\nלהמשיך?</string>\n    <string name=\"setup_title\">התקנה נוספת</string>\n    <string name=\"select_patch_file\">בחירה והתקנת קובץ</string>\n    <string name=\"patch_file_msg\">בחירת קובץ גולמי (*.img) או ODIN tarfile (*.tar) או payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">מאתחל בעוד 5 שניות…</string>\n    <string name=\"flash_screen_title\">התקנה</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">בקשות משתמש על</string>\n    <string name=\"touch_filtered_warning\">מכיוון שיישום מסתיר בקשה של משתמש על, Magisk לא יכול לאמת את תגובתך</string>\n    <string name=\"deny\">דחייה</string>\n    <string name=\"prompt\">מיידי</string>\n    <string name=\"grant\">הענקה</string>\n    <string name=\"su_warning\">מעניק גישה מלאה להתקן שלך.\\nיש לדחות באי וודאות!</string>\n    <string name=\"forever\">לצמיתות</string>\n    <string name=\"once\">פעם אחת</string>\n    <string name=\"tenmin\">10 דקות</string>\n    <string name=\"twentymin\">20 דקות</string>\n    <string name=\"thirtymin\">חצי שעה</string>\n    <string name=\"sixtymin\">שעה</string>\n    <string name=\"su_allow_toast\">%1$s קיבל הרשאות משתמש על</string>\n    <string name=\"su_deny_toast\">%1$s נשללו הרשאות משתמש על</string>\n    <string name=\"su_snack_grant\">הרשאות משתמש על עבור %1$s הוענקו</string>\n    <string name=\"su_snack_deny\">הרשאות משתמש על עבור %1$s נשללו</string>\n    <string name=\"su_snack_notif_on\">התראות של %1$s מופעלות</string>\n    <string name=\"su_snack_notif_off\">התראות של %1$s מושבתות</string>\n    <string name=\"su_snack_log_on\">Log עבור %1$s מופעל</string>\n    <string name=\"su_snack_log_off\">Log עבור %1$s מושבת</string>\n    <string name=\"su_revoke_title\">להסיר?</string>\n    <string name=\"su_revoke_msg\">נא לאשר שלילת הרשאות עבור %1$s?</string>\n    <string name=\"toast\">התראה</string>\n    <string name=\"none\">ללא</string>\n    <string name=\"superuser_toggle_notification\">התראות</string>\n    <string name=\"superuser_toggle_revoke\">הסרה</string>\n    <string name=\"superuser_policy_none\">טרם נתבקשו הרשאות משתמש על על ידי יישומים</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">אין Log, יש לנסות להשתמש יותר ביישומי הRoot שלך</string>\n    <string name=\"log_data_magisk_none\">Log Magisk ריק, זה מוזר</string>\n    <string name=\"menuSaveLog\">שמירת Log</string>\n    <string name=\"menuClearLog\">ניקוי Log כעת</string>\n    <string name=\"logs_cleared\">Log נוקה בהצלחה</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">יעד UID: %1$d</string>\n    <string name=\"target_pid\">מציב ns יעד PID: %s</string>\n    <string name=\"selinux_context\">הקשר SELinux: %s</string>\n    <string name=\"supp_group\">קבוצה משלימה: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">הצגת יישומי מערכת</string>\n    <string name=\"show_os_app\">הצגת יישומי מערכת הפעלה</string>\n    <string name=\"hide_filter_hint\">סינון לפי שם</string>\n    <string name=\"hide_search\">חיפוש</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(לא סופק מידע)</string>\n    <string name=\"reboot_userspace\">אתחול רך</string>\n    <string name=\"reboot_recovery\">אתחול למצב Recovery</string>\n    <string name=\"reboot_bootloader\">אתחול לBootloader</string>\n    <string name=\"reboot_download\">אתחול למצב הורדה</string>\n    <string name=\"reboot_edl\">אתחול למצב EDL</string>\n    <string name=\"reboot_safe_mode\">מצב בטוח</string>\n    <string name=\"module_version_author\">%1$s מאת %2$s</string>\n    <string name=\"module_state_remove\">הסרה</string>\n    <string name=\"module_action\">פעולה</string>\n    <string name=\"module_state_restore\">שיחזור</string>\n    <string name=\"module_action_install_external\">התקנה מהאחסון</string>\n    <string name=\"update_available\">עדכונים זמינים</string>\n    <string name=\"suspend_text_riru\">מודול מושעה כי %1$s מופעל</string>\n    <string name=\"suspend_text_zygisk\">המודול הושעה כי %1$s אינו מופעל</string>\n    <string name=\"zygisk_module_unloaded\">מודול Zygisk לא נטען עקב חוסר תאימות</string>\n    <string name=\"module_empty\">לא מותקן מודול</string>\n    <string name=\"confirm_install\">להתקין מודול %1$s?</string>\n    <string name=\"confirm_install_title\">אישור התקנה</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">מצב עיצוב</string>\n    <string name=\"settings_dark_mode_message\">נא לבחור מצב המתאים ביותר לסגנון שלך!</string>\n    <string name=\"settings_dark_mode_light\">תמיד בהיר</string>\n    <string name=\"settings_dark_mode_system\">בהתאם למערכת</string>\n    <string name=\"settings_dark_mode_dark\">תמיד כהה</string>\n    <string name=\"settings_download_path_title\">נתיב הורדה</string>\n    <string name=\"settings_download_path_message\">הקבצים ישמרו אל %1$s</string>\n    <string name=\"settings_hide_app_title\">הסתרת היישום Magisk</string>\n    <string name=\"settings_hide_app_summary\">התקנת יישום מתווך עם מזהה חבילה אקראי ותווית שם מותאמת אישית</string>\n    <string name=\"settings_restore_app_title\">שיחזור היישום Magisk</string>\n    <string name=\"settings_restore_app_summary\">ביטול הסתרת היישום ושיחזור אל ה-APK המקורי</string>\n    <string name=\"language\">שפה</string>\n    <string name=\"system_default\">(ברירת מחדל מערכת)</string>\n    <string name=\"settings_check_update_title\">בדיקת עדכונים</string>\n    <string name=\"settings_check_update_summary\">בדיקה מעת לעת ברקע אם יש עדכונים</string>\n    <string name=\"settings_update_channel_title\">ערוץ עדכון</string>\n    <string name=\"settings_update_stable\">יציב</string>\n    <string name=\"settings_update_beta\">בטא</string>\n    <string name=\"settings_update_custom\">מותאם אישית</string>\n    <string name=\"settings_update_custom_msg\">הזנת כתובת מותאמת אישית</string>\n    <string name=\"settings_zygisk_summary\">הפעל חלקים של Magisk בדמון zygote</string>\n    <string name=\"settings_denylist_title\">אכיפת רשימת דחייה</string>\n    <string name=\"settings_denylist_summary\">כל השינויים של Magisk יוחזרו לתהליכים ברשימת הדחייה</string>\n    <string name=\"settings_denylist_config_title\">הגדרת רשימת הדחייה</string>\n    <string name=\"settings_denylist_config_summary\">בחירת התהליכים שייכללו ברשימת הדחייה</string>\n    <string name=\"settings_hosts_title\">hosts חסרי מערכת</string>\n    <string name=\"settings_hosts_summary\">hosts חסרי מערכת תומכים ביישומים חוסמי פרסומות</string>\n    <string name=\"settings_hosts_toast\">הוספת מודול hosts חסרי מערכת</string>\n    <string name=\"settings_app_name_hint\">שם חדש</string>\n    <string name=\"settings_app_name_helper\">היישום יארז מחדש בשם זה</string>\n    <string name=\"settings_app_name_error\">פורמט לא חוקי</string>\n    <string name=\"settings_su_app_adb\">יישומים ו-ADB</string>\n    <string name=\"settings_su_app\">יישומים בלבד</string>\n    <string name=\"settings_su_adb\">ADB בלבד</string>\n    <string name=\"settings_su_disable\">מושבת</string>\n    <string name=\"settings_su_request_10\">10 שניות</string>\n    <string name=\"settings_su_request_15\">15 שניות</string>\n    <string name=\"settings_su_request_20\">20 שניות</string>\n    <string name=\"settings_su_request_30\">30 שניות</string>\n    <string name=\"settings_su_request_45\">45 שניות</string>\n    <string name=\"settings_su_request_60\">60 שניות</string>\n    <string name=\"superuser_access\">גישת משתמש על</string>\n    <string name=\"auto_response\">תגובה אוטומטית</string>\n    <string name=\"request_timeout\">בקשת פסק זמן</string>\n    <string name=\"superuser_notification\">התראות משתמש על</string>\n    <string name=\"settings_su_reauth_title\">אימות מחדש לאחר שדרוג</string>\n    <string name=\"settings_su_reauth_summary\">אימות מחדש הרשאות של משתמש על לאחר שדרוג יישום</string>\n    <string name=\"settings_su_tapjack_title\">הגנת Tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">תיבת הדו שיח של משתמש העל לא תגיב לקלט כשהיא מוסתרת על ידי חלון או שכבת על אחרת</string>\n    <string name=\"settings_su_auth_title\">אימות משתמש</string>\n    <string name=\"settings_su_auth_summary\">בקשת אימות משתמש במהלך בקשות משתמש על</string>\n    <string name=\"settings_su_auth_insecure\">לא מוגדרת שיטת אימות בהתקן</string>\n    <string name=\"settings_customization\">התאמה אישית</string>\n    <string name=\"setting_add_shortcut_summary\">הוספת קיצור דרך יפה במסך הבית למקרה שקשה לזהות את השם ואת הסמל לאחר הסתרת היישום</string>\n    <string name=\"settings_doh_title\">DNS על HTTPS</string>\n    <string name=\"settings_doh_description\">עקיפת DNS מורעל במדינות מסוימות</string>\n    <string name=\"settings_random_name_title\">שם פלט אקראי</string>\n    <string name=\"settings_random_name_description\">שם אקראי לקובץ הפלט של תמונות מתוקנות וקבצי tar כדי למנוע זיהוי</string>\n    <string name=\"multiuser_mode\">מצב מרובה משתמשים</string>\n    <string name=\"settings_owner_only\">בעל ההתקן בלבד</string>\n    <string name=\"settings_owner_manage\">אחראי ניהול ההתקן</string>\n    <string name=\"settings_user_independent\">משתמש עצמאי</string>\n    <string name=\"owner_only_summary\">לבעלים בלבד ישנה גישת Root</string>\n    <string name=\"owner_manage_summary\">הבעלים בלבד יכול לנהל גישת Root ולקבל הנחיות לבקשה</string>\n    <string name=\"user_independent_summary\">לכל משתמש יש כללי Root נפרדים משלו</string>\n    <string name=\"mount_namespace_mode\">מצב הצבת מרחב שם</string>\n    <string name=\"settings_ns_global\">מרחב שם גלובלי</string>\n    <string name=\"settings_ns_requester\">מרחב שם מורש</string>\n    <string name=\"settings_ns_isolate\">מרחב שם מבודד</string>\n    <string name=\"global_summary\">כלל חיבורי הRoot משתמשים במרחב שם הגלובלי</string>\n    <string name=\"requester_summary\">חיבורי הRoot יירשו את מרחב השם של המבקש</string>\n    <string name=\"isolate_summary\">לכל חיבור Root יהיה מרחב שם מבודד</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">עדכוני Magisk</string>\n    <string name=\"progress_channel\">התראות התקדמות</string>\n    <string name=\"updated_channel\">העדכון הושלם</string>\n    <string name=\"download_complete\">ההורדה הושלמה</string>\n    <string name=\"download_file_error\">שגיאה בהורדת קובץ</string>\n    <string name=\"magisk_update_title\">עדכון Magisk זמין!</string>\n    <string name=\"updated_title\">עדכון Magisk</string>\n    <string name=\"updated_text\">הקשה כדי לפתוח יישום</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">כן</string>\n    <string name=\"no\">לא</string>\n    <string name=\"repo_install_title\">מתקין %1$s %2$s(%3$d)</string>\n    <string name=\"download\">הורדה</string>\n    <string name=\"reboot\">הפעלה מחדש</string>\n    <string name=\"close\">סגירה</string>\n    <string name=\"release_notes\">הערות שחרור</string>\n    <string name=\"flashing\">צורב…</string>\n    <string name=\"running\">רץ…</string>\n    <string name=\"done\">בוצע!</string>\n    <string name=\"done_action\">בוצעה ריצת פעולה של %1$s</string>\n    <string name=\"failure\">נכשל!</string>\n    <string name=\"hide_app_title\">מסתיר את יישום Magisk…</string>\n    <string name=\"open_link_failed_toast\">לא נמצאו יישומים לפתיחת קישור זה</string>\n    <string name=\"complete_uninstall\">הסרה מלאה</string>\n    <string name=\"restore_img\">שיחזור תמונות</string>\n    <string name=\"restore_img_msg\">משחזר…</string>\n    <string name=\"restore_done\">השיחזור בוצע!</string>\n    <string name=\"restore_fail\">גיבוי מקורי אינו קיים!</string>\n    <string name=\"setup_fail\">ההתקנה כשלה</string>\n    <string name=\"env_fix_title\">דורש התקנה נוספת</string>\n    <string name=\"env_fix_msg\">ההתקן שלך זקוק להתקנה נוספת כדי ש-Magisk יפעל כראוי. האם ברצונך להמשיך ולהפעיל מחדש?</string>\n    <string name=\"env_full_fix_msg\">ההתקן שלך זקוק לצריבה מחדש של Magisk כדי לעבוד כראוי. נא להתקין מחדש את Magisk בתוך היישום, מצב הRecovery אינו יכול לקבל מידע נכון על ההתקן.</string>\n    <string name=\"setup_msg\">הגדרת סביבת ריצה…</string>\n    <string name=\"unsupport_magisk_title\">גרסת Magisk אינה נתמכת</string>\n    <string name=\"unsupport_magisk_msg\">גרסה זו של היישום אינה תומכת ביישום  Magisk הנמוך מ- %1$s.\\n\\nהיישום יתנהג כאילו Magisk אינו מותקן,יש לשדרג את Magisk במהירות האפשריות.</string>\n    <string name=\"unsupport_general_title\">מצב לא תקין</string>\n    <string name=\"unsupport_system_app_msg\">הפעלת יישום זה כיישום מערכת אינה נתמך. נא להשיב את היישום כיישום משתמש.</string>\n    <string name=\"unsupport_other_su_msg\">התגלתה פקודת \\\"su\\\" שאינה שייכת ליישום Magisk נא להסיר את הפקודה שאינה נתמכת.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk מותקן באחסון החיצוני. נא להעביר את היישום לאחסון הפנימי.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">היישום אינו יכול להמשיך לעבוד במצב הנסתר מכיוון שגישת הRoot אבדה. נא לשחזר אותו חזרה ל-APK המקורי.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">הענת הרשאת אחסון להפעלת פונקציה זו</string>\n    <string name=\"post_notifications_denied\">הענקת הרשאה להתראות כדי להפעיל פונקציה זו</string>\n    <string name=\"install_unknown_denied\">יש לאפשר \"התקנה ממקורות לא ידועים\" בכדי להפעיל פונקציה זו</string>\n    <string name=\"add_shortcut_title\">הוספת קיצור דרך למסך הבית</string>\n    <string name=\"add_shortcut_msg\">לאחר הסתרת היישום הזה, השם והסמליל שלה עשויים להיות קשים לזיהוי. האם ברצונך להוסיף קיצור דרך יפה למסך הבית?</string>\n    <string name=\"app_not_found\">לא נמצא יישום לטיפול בפעולה זו</string>\n    <string name=\"reboot_apply_change\">ייש להפעיל מחדש כדי להחיל שינויים</string>\n    <string name=\"restore_app_confirmation\">פעולה זו תשחזר את היישום המוסתר חזרה ליישום המקורי. האם בוודאות ברצונך לעשות את זה?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ja/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">モジュール</string>\n    <string name=\"superuser\">スーパーユーザー</string>\n    <string name=\"logs\">ログ</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"install\">インストール</string>\n    <string name=\"section_home\">ホーム</string>\n    <string name=\"section_theme\">テーマ</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">インターネット接続なし</string>\n    <string name=\"app_changelog\">アプリの更新履歴</string>\n    <string name=\"loading\">読み込み中…</string>\n    <string name=\"update\">更新</string>\n    <string name=\"not_available\">なし</string>\n    <string name=\"hide\">隠す</string>\n    <string name=\"home_package\">パッケージ ID </string>\n    <string name=\"home_app_title\">アプリ</string>\n\n    <string name=\"home_notice_content\">Magisk は必ず公式の GitHub ページからダウンロードしてください。その他の提供元では悪意あるコードを含んでいる可能性があります!</string>\n    <string name=\"home_support_title\">サポートする</string>\n    <string name=\"home_follow_title\">フォローする</string>\n    <string name=\"home_item_source\">ソース</string>\n    <string name=\"home_support_content\">Magisk はフリーでオープンソースです。寄付を送ると､ Magisk の開発を支援することができます。</string>\n    <string name=\"home_installed_version\">インストール済</string>\n    <string name=\"home_latest_version\">最新版</string>\n    <string name=\"invalid_update_channel\">不正な更新チャンネルです</string>\n    <string name=\"uninstall_magisk_title\">Magisk のアンインストール</string>\n    <string name=\"uninstall_magisk_msg\">すべてのモジュールが無効化/削除されます。\\nアンインストール後は root が削除され､ストレージが暗号化されていない場合は暗号化される場合があります。</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">暗号化を維持する</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity を維持する</string>\n    <string name=\"recovery_mode\">リカバリーモード</string>\n    <string name=\"install_options_title\">オプション</string>\n    <string name=\"install_method_title\">方法</string>\n    <string name=\"install_next\">次へ</string>\n    <string name=\"install_start\">はじめる</string>\n    <string name=\"manager_download_install\">タップしてダウンロード＆インストールします</string>\n    <string name=\"direct_install\">直接インストール (推奨)</string>\n    <string name=\"install_inactive_slot\">別のスロットにインストール (OTA 後)</string>\n    <string name=\"install_inactive_slot_msg\">お使いのデバイスは再起動後に現在とは別のスロットで強制的に起動されます！\\nこのオプションは OTA の完了後にのみ使用してください。\\n続行しますか？</string>\n    <string name=\"setup_title\">追加セットアップ</string>\n    <string name=\"select_patch_file\">パッチするファイルの選択</string>\n    <string name=\"patch_file_msg\">イメージファイル (*.img) または ODIN tar ファイル (*.tar) を選択してください</string>\n    <string name=\"reboot_delay_toast\">5秒後に再起動します…</string>\n    <string name=\"flash_screen_title\">インストール</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">スーパーユーザー権限の要求</string>\n    <string name=\"touch_filtered_warning\">他のアプリがスーパーユーザー権限の要求ダイアログを隠しているため、Magisk はあなたの応答を確認できません。</string>\n    <string name=\"deny\">拒否</string>\n    <string name=\"prompt\">尋ねる</string>\n    <string name=\"grant\">許可</string>\n    <string name=\"su_warning\">端末への完全なアクセスを許可します。\\nもし確信が持てなければ拒否してください！</string>\n    <string name=\"forever\">今後も</string>\n    <string name=\"once\">今回のみ</string>\n    <string name=\"tenmin\">10 分</string>\n    <string name=\"twentymin\">20 分</string>\n    <string name=\"thirtymin\">30 分</string>\n    <string name=\"sixtymin\">60 分</string>\n    <string name=\"su_allow_toast\">%1$s のスーパーユーザー権限を許可しました</string>\n    <string name=\"su_deny_toast\">%1$s のスーパーユーザー権限を拒否しました</string>\n    <string name=\"su_snack_grant\">%1$s のスーパーユーザー権限を許可しました</string>\n    <string name=\"su_snack_deny\">%1$s のスーパーユーザー権限を拒否しました</string>\n    <string name=\"su_snack_notif_on\">%1$s の通知は有効です</string>\n    <string name=\"su_snack_notif_off\">%1$s の通知は無効です</string>\n    <string name=\"su_snack_log_on\">%1$s のログは有効です</string>\n    <string name=\"su_snack_log_off\">%1$s のログは無効です</string>\n    <string name=\"su_revoke_title\">確認</string>\n    <string name=\"su_revoke_msg\">%1$s の権限を取り消しますか？</string>\n    <string name=\"toast\">トースト通知</string>\n    <string name=\"none\">なし</string>\n\n    <string name=\"superuser_toggle_notification\">通知</string>\n    <string name=\"superuser_toggle_revoke\">取り消し</string>\n    <string name=\"superuser_policy_none\">スーパーユーザー権限の許可を求めたアプリはまだありません</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">ログはありません。スーパーユーザー権限を使用するアプリをもっと使ってみてください</string>\n    <string name=\"log_data_magisk_none\">おかしなことに Magisk のログは空です</string>\n    <string name=\"menuSaveLog\">ログを保存</string>\n    <string name=\"menuClearLog\">ログを消去</string>\n    <string name=\"logs_cleared\">ログを消去しました</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">ターゲット UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">システムアプリを表示</string>\n    <string name=\"show_os_app\">OS アプリを表示</string>\n    <string name=\"hide_filter_hint\">名前で絞り込み</string>\n    <string name=\"hide_search\">検索</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(情報なし)</string>\n    <string name=\"reboot_userspace\">ソフトリブート</string>\n    <string name=\"reboot_recovery\">Recovery へ再起動</string>\n    <string name=\"reboot_bootloader\">Bootloader へ再起動</string>\n    <string name=\"reboot_download\">Download へ再起動</string>\n    <string name=\"reboot_edl\">EDL へ再起動</string>\n    <string name=\"module_version_author\">%1$s 開発者：%2$s</string>\n    <string name=\"module_state_remove\">削除</string>\n    <string name=\"module_state_restore\">復元</string>\n    <string name=\"module_action_install_external\">ストレージからインストール</string>\n    <string name=\"update_available\">更新あり</string>\n    <string name=\"suspend_text_riru\">モジュールは %1$s が有効化されているため休止しました</string>\n    <string name=\"suspend_text_zygisk\">モジュールは %1$s が有効化されていないため休止しました</string>\n    <string name=\"zygisk_module_unloaded\">互換性がないため Zygisk モジュールは読み込まれませんでした</string>\n    <string name=\"module_empty\">モジュールをインストールしていません</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">テーマ</string>\n    <string name=\"settings_dark_mode_message\">あなたの好きなスタイルを選びましょう！</string>\n    <string name=\"settings_dark_mode_light\">ライトモード</string>\n    <string name=\"settings_dark_mode_system\">システムに合わせる</string>\n    <string name=\"settings_dark_mode_dark\">ダークモード</string>\n    <string name=\"settings_download_path_title\">ファイルの保存場所</string>\n    <string name=\"settings_download_path_message\">ファイルは %1$s に保存されます</string>\n    <string name=\"settings_hide_app_title\">Magisk アプリを隠す</string>\n    <string name=\"settings_hide_app_summary\">ランダムなパッケージ ID と任意のアプリ名のプロキシアプリをインストールします</string>\n    <string name=\"settings_restore_app_title\">Magisk アプリを復元する</string>\n    <string name=\"settings_restore_app_summary\">隠すのを止めて、元の APK をインストールします</string>\n    <string name=\"language\">言語 (Language)</string>\n    <string name=\"system_default\">(システム標準)</string>\n    <string name=\"settings_check_update_title\">更新をチェック</string>\n    <string name=\"settings_check_update_summary\">バックグラウンドで定期的に更新をチェックします</string>\n    <string name=\"settings_update_channel_title\">更新チャンネル</string>\n    <string name=\"settings_update_stable\">安定版</string>\n    <string name=\"settings_update_beta\">ベータ版</string>\n    <string name=\"settings_update_custom\">カスタム</string>\n    <string name=\"settings_update_custom_msg\">カスタム URL を入力</string>\n    <string name=\"settings_zygisk_summary\">Magisk の一部を Zygote デーモン上で実行します</string>\n    <string name=\"settings_denylist_title\">DenyList を適用</string>\n    <string name=\"settings_denylist_summary\">DenyList 内のプロセスは Magisk の変更がすべて戻されます</string>\n    <string name=\"settings_denylist_config_title\">DenyList を構成</string>\n    <string name=\"settings_denylist_config_summary\">DenyList に含めるプロセスを選択します</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">広告ブロックアプリのための Systemless hosts サポートを有効化します</string>\n    <string name=\"settings_hosts_toast\">Systemless hosts モジュールを追加しました</string>\n    <string name=\"settings_app_name_hint\">アプリ名</string>\n    <string name=\"settings_app_name_helper\">プロキシアプリはこのアプリ名でインストールされます</string>\n    <string name=\"settings_app_name_error\">不正な形式</string>\n    <string name=\"settings_su_app_adb\">アプリと ADB</string>\n    <string name=\"settings_su_app\">アプリのみ</string>\n    <string name=\"settings_su_adb\">ADB のみ</string>\n    <string name=\"settings_su_disable\">無効</string>\n    <string name=\"settings_su_request_10\">10 秒</string>\n    <string name=\"settings_su_request_15\">15 秒</string>\n    <string name=\"settings_su_request_20\">20 秒</string>\n    <string name=\"settings_su_request_30\">30 秒</string>\n    <string name=\"settings_su_request_45\">45 秒</string>\n    <string name=\"settings_su_request_60\">60 秒</string>\n    <string name=\"superuser_access\">スーパーユーザー権限を使用できるプログラム</string>\n    <string name=\"auto_response\">自動応答</string>\n    <string name=\"request_timeout\">要求のタイムアウト</string>\n    <string name=\"superuser_notification\">スーパーユーザー通知</string>\n    <string name=\"settings_su_reauth_title\">アップグレード後の再認証</string>\n    <string name=\"settings_su_reauth_summary\">アプリのアップグレード後にスーパーユーザー権限を再認証します</string>\n    <string name=\"settings_su_tapjack_title\">タップジャッキング保護を有効にする</string>\n    <string name=\"settings_su_tapjack_summary\">他のウィンドウやオーバーレイで表示されている間は、スーパーユーザー権限の要求ダイアログが入力に応答しないようにします</string>\n    <string name=\"settings_su_auth_title\">ユーザー認証</string>\n    <string name=\"settings_su_auth_summary\">スーパーユーザー権限の要求でユーザー認証を行ないます</string>\n    <string name=\"settings_su_auth_insecure\">端末の認証方法が設定されていません</string>\n    <string name=\"settings_customization\">カスタマイズ</string>\n    <string name=\"setting_add_shortcut_summary\">アプリを隠した後に見つけられなくなったときは、ここでホーム画面にショートカットを追加できます</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">一部の国で DNS キャッシュポイズニングから守ります</string>\n\n    <string name=\"multiuser_mode\">複数ユーザーモード</string>\n    <string name=\"settings_owner_only\">端末の管理者のみ</string>\n    <string name=\"settings_owner_manage\">端末の管理者が管理</string>\n    <string name=\"settings_user_independent\">ユーザー毎</string>\n    <string name=\"owner_only_summary\">端末の管理者のみスーパーユーザー権限を利用できます</string>\n    <string name=\"owner_manage_summary\">端末の管理者のみがスーパーユーザー権限を管理し、要求を受け付けられます</string>\n    <string name=\"user_independent_summary\">ユーザー毎にそれぞれスーパーユーザー権限を設定できます</string>\n\n    <string name=\"mount_namespace_mode\">名前空間のマウントモード</string>\n    <string name=\"settings_ns_global\">グローバル名前空間</string>\n    <string name=\"settings_ns_requester\">継承された名前空間</string>\n    <string name=\"settings_ns_isolate\">分離された名前空間</string>\n    <string name=\"global_summary\">すべての root セッションがグローバル名前空間を使用します</string>\n    <string name=\"requester_summary\">root セッションはリクエスト者の名前空間を継承します</string>\n    <string name=\"isolate_summary\">root セッション毎に分離された名前空間を使用します</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk の更新</string>\n    <string name=\"progress_channel\">進捗通知</string>\n    <string name=\"updated_channel\">更新完了</string>\n    <string name=\"download_complete\">ダウンロード完了</string>\n    <string name=\"download_file_error\">ダウンロード中にエラーが発生しました</string>\n    <string name=\"magisk_update_title\">Magisk の更新があります！</string>\n    <string name=\"updated_title\">Magisk を更新しました</string>\n    <string name=\"updated_text\">タップでアプリを開きます</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">対応</string>\n    <string name=\"no\">非対応</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) をインストール</string>\n    <string name=\"download\">ダウンロード</string>\n    <string name=\"reboot\">再起動</string>\n    <string name=\"release_notes\">更新履歴</string>\n    <string name=\"flashing\">書込中…</string>\n    <string name=\"done\">完了！</string>\n    <string name=\"failure\">失敗！</string>\n    <string name=\"hide_app_title\">Magisk アプリを隠しています…</string>\n    <string name=\"open_link_failed_toast\">このリンクを開けるアプリがありません</string>\n    <string name=\"complete_uninstall\">完全にアンインストール</string>\n    <string name=\"restore_img\">イメージのリストア</string>\n    <string name=\"restore_img_msg\">リストア中…</string>\n    <string name=\"restore_done\">リストア完了！</string>\n    <string name=\"restore_fail\">Stock のバックアップがありません！</string>\n    <string name=\"setup_fail\">セットアップに失敗しました</string>\n    <string name=\"env_fix_title\">追加のセットアップが必要です</string>\n    <string name=\"env_fix_msg\">Magisk を正常に動作させるためには追加のセットアップが必要です。今すぐ再起動しますか？</string>\n    <string name=\"setup_msg\">追加セットアップを実行中…</string>\n    <string name=\"unsupport_magisk_title\">対応していない Magisk バージョンです</string>\n    <string name=\"unsupport_magisk_msg\">このバージョンのアプリではバージョン %1$s 以下の Magisk に対応していません。\\n\\nアプリで Magisk を使えないため、すぐに Magisk を更新してください</string>\n    <string name=\"unsupport_general_title\">異常な状態です</string>\n    <string name=\"unsupport_system_app_msg\">このアプリをシステムアプリとして起動することはサポートされていません。ユーザーアプリに戻してください。</string>\n    <string name=\"unsupport_other_su_msg\">Magisk 以外の \\\"su\\\" バイナリが検出されました。競合する root 権限取得ソフトを削除し、Magisk をインストールし直してください。</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk が外部ストレージにインストールされています。内部ストレージに移動してください。</string>\n    <string name=\"unsupport_nonroot_stub_msg\">root 権限が失われたため､隠し Magisk アプリが動作できなくなりました。元の APK を復元してください。</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">この機能を有効にするにはストレージ権限を許可してください</string>\n    <string name=\"post_notifications_denied\">この機能を有効化するには権限を許可してください</string>\n    <string name=\"install_unknown_denied\">この機能を有効化するには、不明なソースからのインストールを許可してください</string>\n    <string name=\"add_shortcut_title\">ホーム画面にショートカットを追加</string>\n    <string name=\"add_shortcut_msg\">アプリを隠すと、アプリ名やアイコンが判別しにくいことがあります。分かりやすいようホーム画面にショートカットを追加しますか？</string>\n    <string name=\"app_not_found\">このアクションを扱えるアプリがありません</string>\n    <string name=\"reboot_apply_change\">再起動して変更を適用</string>\n    <string name=\"restore_app_confirmation\">隠しアプリを元のアプリへと戻します。続行しますか?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ka/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">მოდულები</string>\n    <string name=\"superuser\">სუპერმომხმარებელი</string>\n    <string name=\"logs\">ლოგები</string>\n    <string name=\"settings\">პარამეტრები</string>\n    <string name=\"install\">ინსტალაცია</string>\n    <string name=\"section_home\">საწყისი</string>\n    <string name=\"section_theme\">თემები</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">კავშირი არ არის</string>\n    <string name=\"app_changelog\">ცვლილებები</string>\n    <string name=\"loading\">იტვირთება…</string>\n    <string name=\"update\">განახლება</string>\n    <string name=\"not_available\">ხელმიუწვდომელია</string>\n    <string name=\"hide\">დამალვა</string>\n    <string name=\"home_package\">პაკეტი</string>\n    <string name=\"home_app_title\">აპი</string>\n\n    <string name=\"home_notice_content\">გადმოიწერეთ Magisk მხოლოდ ოფიციალური GitHub-ის გვერდიდან. სხვა წყაროებიდან გადმოწერილი ფაილები შესაძლოა სახიფათო იყოს!</string>\n    <string name=\"home_support_title\">მხარდაჭერა</string>\n    <string name=\"home_item_source\">წყარო</string>\n    <string name=\"home_support_content\">Magisk-ი არის და ყოველთვის იქნება უფასო და წყარო-გახსნილი. მაგრამ თქვენ შეგიძლიათ დახმარება გაგვიწიოთ პატარა დონაციით.</string>\n    <string name=\"home_installed_version\">დაინსტალირებული</string>\n    <string name=\"home_latest_version\">უახლესი</string>\n    <string name=\"invalid_update_channel\">არასწორი განახლების არხი</string>\n    <string name=\"uninstall_magisk_title\">Magisk-ის დეინსტალაცია</string>\n    <string name=\"uninstall_magisk_msg\">ყველა მოდული იქნება წაშლილი/გამორთული!\\nRoot-ი წაიშლება!\\nთქვენი მონაცემები დაიშიფრება!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">შიფრირება ყოველთვის იყოს ჩართული</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity იყოს ჩართული</string>\n    <string name=\"recovery_mode\">Recovery რეჟიმი</string>\n    <string name=\"install_options_title\">ოფციები</string>\n    <string name=\"install_method_title\">მეთოდი</string>\n    <string name=\"install_next\">შემდეგი</string>\n    <string name=\"install_start\">დაწყება</string>\n    <string name=\"manager_download_install\">დააჭირეთ გადმოსაწერად და შემდგომ დასაყენებლად</string>\n    <string name=\"direct_install\">აქვე დაინსტალირება (რეკომენდირებულია)</string>\n    <string name=\"install_inactive_slot\">არააქტიურ სლოტში დაყენება (სისტემის განახლების შემდეგ)</string>\n    <string name=\"install_inactive_slot_msg\">თქვენი მოწყობილობა გადაიტვირთება არააქატიურ სლოტში!\\nგამოიყენეთ ეს ოფცია მხოლოდ სისტემის განახლების დროს.\\nგავაგრძელოთ?</string>\n    <string name=\"setup_title\">დამატებითი ინსტალაცია</string>\n    <string name=\"select_patch_file\">ფაილის დაპატჩვა</string>\n    <string name=\"patch_file_msg\">აირჩიეთ (*.img) ფაილი ან ODIN tarfile-ი (*.tar)</string>\n    <string name=\"reboot_delay_toast\">გადატვირთვა 5 წამში…</string>\n    <string name=\"flash_screen_title\">ინსტალაცია</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">სუპერმომხმარებლის ნებართვის მოთხოვნა</string>\n    <string name=\"touch_filtered_warning\">სხვა აპი ხელს უშლის Magisk-ს თქვენი პასუხის დადასტურებაში</string>\n    <string name=\"deny\">აკრძალვა</string>\n    <string name=\"prompt\">კითხვა</string>\n    <string name=\"grant\">დართვა</string>\n    <string name=\"su_warning\">ანიჭებს სრულ წვდომას მოწყობილობასთან.\\nაკრძალეთ თუ დარწმუნებული არ ხართ!</string>\n    <string name=\"forever\">სამუდამოდ</string>\n    <string name=\"once\">ერთჯერადად</string>\n    <string name=\"tenmin\">10 წუთში</string>\n    <string name=\"twentymin\">20 წუთში</string>\n    <string name=\"thirtymin\">30 წუთში</string>\n    <string name=\"sixtymin\">60 წუთში</string>\n    <string name=\"su_allow_toast\">%1$s-ს მიენიჭა სუპერმომხმარებლის ნებართვები</string>\n    <string name=\"su_deny_toast\">%1$s-ს აეკრძალა სუპერმომხმარებლის ნებართვები</string>\n    <string name=\"su_snack_grant\">%1$s-ის ნებართვები იქნა მინიჭებული</string>\n    <string name=\"su_snack_deny\">%1$s-ის ნებართვები იქნა აკრძალული</string>\n    <string name=\"su_snack_notif_on\">%1$s-ის შეტყობინებები ჩართულია</string>\n    <string name=\"su_snack_notif_off\">%1$s-ის შეტყობინებები გამორთულია</string>\n    <string name=\"su_snack_log_on\">%1$s-ის ლოგინგი ჩართულია</string>\n    <string name=\"su_snack_log_off\">%1$s-ის ლოგინგი გამორთულია</string>\n    <string name=\"su_revoke_title\">გავაუქმოთ?</string>\n    <string name=\"su_revoke_msg\">ნამდვილად გსურთ %1$s-ისთვის ნებართვების გაუქმება?</string>\n    <string name=\"toast\">ჩვეულებრივი</string>\n    <string name=\"none\">არანაირი</string>\n\n    <string name=\"superuser_toggle_notification\">შეტყობინებები</string>\n    <string name=\"superuser_toggle_revoke\">გაუქმება</string>\n    <string name=\"superuser_policy_none\">ჯერ არცერთ აპს არ მოუთხოვია სუპერმომხმარებლის ნებართვები.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">თქვენი ლოგი ცარიელია, სცადეთ Root-ზე დაფუძნებული პროგრამების გამოყენება.</string>\n    <string name=\"log_data_magisk_none\">Magisk-ის ლოგები ცარიელია, უცნაურია.</string>\n    <string name=\"menuSaveLog\">ლოგის შენხავა</string>\n    <string name=\"menuClearLog\">ლოგის გასუფთავება</string>\n    <string name=\"logs_cleared\">ლოგი წარმატებით გასუფთავდა.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">სამიზნე UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">სისტემის პროგრამების ჩვენება</string>\n    <string name=\"show_os_app\">ოპერაციული სისტემის აპების ჩვენება</string>\n    <string name=\"hide_filter_hint\">ფილტრაცია სახელის მიხედვით</string>\n    <string name=\"hide_search\">ძებნა</string>\n\n    <!--Module Fragment-->\n    <string name=\"no_info_provided\">(ინფორმაცია არ არის მითითებული)</string>\n    <string name=\"reboot_userspace\">სისტემის გადატვირთვა</string>\n    <string name=\"reboot_recovery\">Recovery-ში გადატვირთვა</string>\n    <string name=\"reboot_bootloader\">Bootloader-ში გადატვირთვა</string>\n    <string name=\"reboot_download\">Download-ში გადატვირთვა</string>\n    <string name=\"reboot_edl\">EDL-ში გადატვირთვა</string>\n    <string name=\"module_version_author\">%1$s %2$s-სგან</string>\n    <string name=\"module_state_remove\">წაშლა</string>\n    <string name=\"module_state_restore\">აღდგენა</string>\n    <string name=\"module_action_install_external\">მახსოვრობიდან დაინსტალირება</string>\n    <string name=\"update_available\">ხელმისაწვდომია განახლება</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">თემების ჩამონათვალი</string>\n    <string name=\"settings_dark_mode_message\">აირჩიეთ ის რაც ყველაზე ძალიან მოგწონთ!</string>\n    <string name=\"settings_dark_mode_light\">ღია თემა</string>\n    <string name=\"settings_dark_mode_system\">სისტემაზე დაფუძნებული</string>\n    <string name=\"settings_dark_mode_dark\">მუქი თემა</string>\n    <string name=\"settings_download_path_title\">გადმოწერის ლოკაცია</string>\n    <string name=\"settings_download_path_message\">ფაილები შეინახება %1$s-ში</string>\n    <string name=\"settings_hide_app_title\">Magisk-ის აპის დამალვა</string>\n    <string name=\"settings_hide_app_summary\">დაყენდეს ამოუცნობელი აპი შემთხვევითი პაკეტის ID-ით და განსხვავებული სახელით</string>\n    <string name=\"settings_restore_app_title\">Magisk-ის აპის დაბრუნება</string>\n    <string name=\"settings_restore_app_summary\">აპის თავდაპირველ მდგომარეობაზე დაბრუნება</string>\n    <string name=\"language\">ენა</string>\n    <string name=\"system_default\">(სისტემის რჩეული)</string>\n    <string name=\"settings_check_update_title\">განახლებების შემოწმება</string>\n    <string name=\"settings_check_update_summary\">განახლებების პერიოდულად შემოწმება ფონურ რეჟიმში</string>\n    <string name=\"settings_update_channel_title\">განახლებების არხი</string>\n    <string name=\"settings_update_stable\">სტაბილური</string>\n    <string name=\"settings_update_beta\">ბეტა</string>\n    <string name=\"settings_update_custom\">პერსონალიზირებული</string>\n    <string name=\"settings_update_custom_msg\">სხვა URL-ის ჩასმა</string>\n    <string name=\"settings_hosts_title\">გარესისტემური ჰოსტები</string>\n    <string name=\"settings_hosts_summary\">გარესისტემური ჰოსტები Adblock-ებისთვის</string>\n    <string name=\"settings_hosts_toast\">გარესისტემური ჰოსტების მოდული დამატებულია</string>\n    <string name=\"settings_app_name_hint\">ახალი სახელი</string>\n    <string name=\"settings_app_name_helper\">პროგრამას ეს პაკეტის სახელი ექნება</string>\n    <string name=\"settings_app_name_error\">არასწორი ფორმატი</string>\n    <string name=\"settings_su_app_adb\">აპები და ADB</string>\n    <string name=\"settings_su_app\">მხოლოდ აპები</string>\n    <string name=\"settings_su_adb\">მხოლოდ ADB</string>\n    <string name=\"settings_su_disable\">გამორთულია</string>\n    <string name=\"settings_su_request_10\">10 წამი</string>\n    <string name=\"settings_su_request_15\">15 წამი</string>\n    <string name=\"settings_su_request_20\">20 წამი</string>\n    <string name=\"settings_su_request_30\">30 წამი</string>\n    <string name=\"settings_su_request_45\">45 წამი</string>\n    <string name=\"settings_su_request_60\">60 წამი</string>\n    <string name=\"superuser_access\">სუპერმომხმარებელთან წვდომა</string>\n    <string name=\"auto_response\">ავტომატური პასუხი</string>\n    <string name=\"request_timeout\">პასუხის ტაიმაუტი</string>\n    <string name=\"superuser_notification\">სუპერმომხმარებლის შეტყობინება</string>\n    <string name=\"settings_su_reauth_title\">რეაუტენთიფიკაცია განახლების შემდეგ</string>\n    <string name=\"settings_su_reauth_summary\">სუპერმომხმარებლის ნებართვის რეაუტენთიფიკაცია აპის განახლების შემდეგ</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking-სგან თავის დაცვა</string> <!-- TODO: tapjacking has no direct translation; must make something up later -->\n    <string name=\"settings_su_tapjack_summary\">superuser-ის დიალოგის ღილაკებზე დაჭერა არ იქნება შესაძლებელი თუ სხვა აპი არის მასზე გადახურული</string>\n    <string name=\"settings_customization\">პერსონალიზაცია</string>\n    <string name=\"setting_add_shortcut_summary\">ლამაზი ხატულის დამატება საწყისს ეკრანზე, იმ შემთხვევაში თუ აპის ამოცნობა არის რთული დამალვის შემდეგ</string>\n    <string name=\"settings_doh_title\">DNS HTTPS-ზე</string>\n    <string name=\"settings_doh_description\">DNS-ის ლიმიტების მოხსნა კონკრეტულ სახელმწიფოებში</string>\n\n    <string name=\"multiuser_mode\">მრავალმომხმარებლიანი რეჟიმი</string>\n    <string name=\"settings_owner_only\">მხოლოდ მოწყობილობის მფლობელი</string>\n    <string name=\"settings_owner_manage\">მოწყობილობის მფლობელის მიერ მართვა</string>\n    <string name=\"settings_user_independent\">მომხმარებლისგან დამოუკიდებელი</string>\n    <string name=\"owner_only_summary\">მხოლოდ მფლობელს ჰქონდეს სუპერმომხმარებელთან წვდომა</string>\n    <string name=\"owner_manage_summary\">მხოლოდ მფლობელს ჰქონდეს ნებართვების მართვა</string>\n    <string name=\"user_independent_summary\">ყოველ მომხმარებელს თავიანთი ნებართვები ჰქონდეთ</string>\n\n    <string name=\"mount_namespace_mode\">Namespace რეჟიმის დამონტაჟება</string>\n    <string name=\"settings_ns_global\">გლობალური Namespace-ი</string>\n    <string name=\"settings_ns_requester\">მიღებითი Namespace-ი</string>\n    <string name=\"settings_ns_isolate\">იზოლირებული Namespace-ი</string>\n    <string name=\"global_summary\">ყველა root სესია გამოიყენებს გლობალურ სამონტაჟო namespace-ს</string>\n    <string name=\"requester_summary\">Root სესიები მიიღებენ მომწოდებლის სამონტაჟო namespace-ს</string>\n    <string name=\"isolate_summary\">ყოველ root სესიას ექნებათ თავიანთი იზოლირებული namespace-ი</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk-ის განახლებები</string>\n    <string name=\"progress_channel\">პროგრესის შეტყობინებები</string>\n    <string name=\"download_complete\">გადმოწერა დასრულებულია</string>\n    <string name=\"download_file_error\">შეცდომა გადმოწერის დროს</string>\n    <string name=\"magisk_update_title\">ხელმისაწვდომია Magisk-ის განახლება!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">დიახ</string>\n    <string name=\"no\">არა</string>\n    <string name=\"download\">გადმოწერა</string>\n    <string name=\"reboot\">გადატვირთვა</string>\n    <string name=\"release_notes\">რელიზის შენიშვნები</string>\n    <string name=\"flashing\">მიმდინარეობს ჩაშენება…</string>\n    <string name=\"done\">დასრულდა!</string>\n    <string name=\"failure\">ჩაიშალა</string>\n    <string name=\"hide_app_title\">ვმალავთ Magisk-ის აპს…</string>\n    <string name=\"open_link_failed_toast\">ლინკის გამხსნელი აპლიკაცია ვერ მოიძებნა</string>\n    <string name=\"complete_uninstall\">დეინსტალაციის დასრულება</string>\n    <string name=\"restore_img\">სურათის აღდგენა</string>\n    <string name=\"restore_img_msg\">მიმდინარეობს აღდგენა…</string>\n    <string name=\"restore_done\">აღდგენა დასრულდა!</string>\n    <string name=\"restore_fail\">საწყისი რეზერვი არ არსებობს!</string>\n    <string name=\"setup_fail\">ინსტალაცია ჩაიშალა</string>\n    <string name=\"env_fix_title\">სჭირდება დამატებითი გამართვა</string>\n    <string name=\"env_fix_msg\">თქვენს მოწყობილობას სჭრიდება დამატებითი გამართვა იმისთვის რომ Magisk-მა იმუშავოს. გადმოიწერება \"Magisk setup.zip\" ფაილი, გსურთ გაგრძელება?</string>\n    <string name=\"setup_msg\">მიმდინარეობს ინტერფეისის ინსტალაცია…</string>\n    <string name=\"unsupport_magisk_title\">შეუთავსებელი Magisk-ის ვერსია</string>\n    <string name=\"unsupport_magisk_msg\">ეს აპის ვერსია შეუთავსებსლია Magisk-ის %1$s-ზე ნაკლებ ვერსიებთან.\\n\\nაპს ეგონება, რომ Magisk-ი არაა დაყენებული, გთხოვთ გაანხალოთ Magisk-ი დროულად.</string>\n    <string name=\"external_rw_permission_denied\">დართეთ მეხსიერებასთან წვდომის ნებართვა ამ ფუნქციის გამოსაყნებლად</string>\n    <string name=\"add_shortcut_title\">ხატულის დამატება საწყისს ეკრანზე</string>\n    <string name=\"add_shortcut_msg\">აპის დამალვის შემდეგ, მისი ნახატი და სახელი შეიძლება ამოუცნობი გახდეს. გნებავთ ხატულის დამატება ასწყისს ეკრანზე?</string>\n    <string name=\"app_not_found\">ამ ფუნქციის შესასრულებლად აპი ვერ მოიძებნა</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-kk/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Модульдер</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Логтар</string>\n    <string name=\"settings\">Баптау</string>\n    <string name=\"install\">Орнату</string>\n    <string name=\"section_home\">Басты бет</string>\n    <string name=\"section_theme\">Кейіп</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Байланыс жоқ</string>\n    <string name=\"app_changelog\">Өзгеріс тізімі</string>\n    <string name=\"loading\">Жүктелуде…</string>\n    <string name=\"update\">Жаңарту</string>\n    <string name=\"not_available\">Белгісіз</string>\n    <string name=\"hide\">Жасыру</string>\n    <string name=\"home_package\">Десте атауы</string>\n    <string name=\"home_app_title\">Қолданба</string>\n\n    <string name=\"home_notice_content\">Magisk-ті ТЕК ресми GitHub репозиториінен жүктеп алыңыз. Басқа көзден алынған файлдар зиянды болуы мүмкін!</string>\n    <string name=\"home_support_title\">Бізді қолдау</string>\n    <string name=\"home_follow_title\">Бізге жазылу</string>\n    <string name=\"home_item_source\">Бастапқы коды</string>\n    <string name=\"home_support_content\">Magisk жобасы тегін әрі еркін болып, болашақта да солай болып қала бермек. Қиын болмаса, бізді қаржылай қолдасаңыз болады.</string>\n    <string name=\"home_installed_version\">Орнатылған</string>\n    <string name=\"home_latest_version\">Соңғы</string>\n    <string name=\"invalid_update_channel\">Жарамсыз жаңарту көзі</string>\n    <string name=\"uninstall_magisk_title\">Magisk-ті жою</string>\n    <string name=\"uninstall_magisk_msg\">Барлық модуль сөндіріледі/жойылады!\\nRoot құқығынан айырыласыз!\\nMagisk көмегімен шифрлауды сөндірген болсаңыз, құрылғының жады қайта шифрланады!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Шифрлауды сөндірмей қалдыру</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity қалдыру</string>\n    <string name=\"recovery_mode\">Recovery режімі</string>\n    <string name=\"install_options_title\">Баптау</string>\n    <string name=\"install_method_title\">Тәсіл</string>\n    <string name=\"install_next\">Келесі</string>\n    <string name=\"install_start\">Бастау</string>\n    <string name=\"manager_download_install\">Жүктеп алып, орнату үшін басыңыз</string>\n    <string name=\"direct_install\">Тікелей орнату (ұсынылады)</string>\n    <string name=\"install_inactive_slot\">Белсенді емес слотқа орнату (OTA-дан кейін)</string>\n    <string name=\"install_inactive_slot_msg\">Құрылғыңыз белсенді емес слотқа МӘЖБҮРЛІ ТҮРДЕ қайта жүктеледі!\\nБұл тәсілді тек OTA жаңартуынан кейін қолданыңыз.\\nЖалғастырасыз ба?</string>\n    <string name=\"setup_title\">Қосымша баптау</string>\n    <string name=\"select_patch_file\">Файл таңдап, патчтау</string>\n    <string name=\"patch_file_msg\">Жад бөлімінің бейнесін (*.img), ODIN мұрағатын (*.tar) немесе payload.bin (*.bin) файлын таңдаңыз</string>\n    <string name=\"reboot_delay_toast\">5 секундтан кейін қайта жүктеледі…</string>\n    <string name=\"flash_screen_title\">Орнату</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser құқығына сұраным</string>\n    <string name=\"touch_filtered_warning\">Өзге қолданба Magisk терезесін бүркегендіктен, Superuser құқығына сұраным қабылданбады</string>\n    <string name=\"deny\">Бас тарту</string>\n    <string name=\"prompt\">Сұрау</string>\n    <string name=\"grant\">Беру</string>\n    <string name=\"su_warning\">Құрылғыңызға шексіз рұқсат бергелі жатырсыз.\\nСенімді болмасаңыз, бас тартыңыз!</string>\n    <string name=\"forever\">Әрдайым</string>\n    <string name=\"once\">Бір рет</string>\n    <string name=\"tenmin\">10 минут</string>\n    <string name=\"twentymin\">20 минут</string>\n    <string name=\"thirtymin\">30 минут</string>\n    <string name=\"sixtymin\">60 минут</string>\n    <string name=\"su_allow_toast\">%1$s Superuser құқығын алды</string>\n    <string name=\"su_deny_toast\">%1$s Superuser құқығын алмады</string>\n    <string name=\"su_snack_grant\">%1$s қолданбасына Superuser құқығы берілді</string>\n    <string name=\"su_snack_deny\">%1$s қолданбасы Superuser құқығынан айырылды</string>\n    <string name=\"su_snack_notif_on\">%1$s жайлы мәлімдемелер қосулы</string>\n    <string name=\"su_snack_notif_off\">%1$s жайлы мәлімдемелер сөндірулі</string>\n    <string name=\"su_snack_log_on\">%1$s әрекеттері логта сақталады</string>\n    <string name=\"su_snack_log_off\">%1$s әрекеттері логта сақталмайды</string>\n    <string name=\"su_revoke_title\">Superuser баптауын арылту</string>\n    <string name=\"su_revoke_msg\">%1$s қолданбасының Superuser баптауын арылтасыз ба?</string>\n    <string name=\"toast\">Қалқыма мәлімдеме</string>\n    <string name=\"none\">Сөндірулі</string>\n\n    <string name=\"superuser_toggle_notification\">Мәлімдеме</string>\n    <string name=\"superuser_toggle_revoke\">Арылту</string>\n    <string name=\"superuser_policy_none\">Superuser құқығын әлі еш қолданба сұраған жоқ.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Әрекет логы бос, root құқығын қажет ететін қолданбаларды көбірек қолданып көріңіз.</string>\n    <string name=\"log_data_magisk_none\">Magisk логы бос, біртүрлі екен</string>\n    <string name=\"menuSaveLog\">Логты сақтау</string>\n    <string name=\"menuClearLog\">Логты тазалау</string>\n    <string name=\"logs_cleared\">Лог сәтті тазаланды</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Мақсатты UID: %1$d</string>\n    <string name=\"target_pid\">Mount ns-тегі мақсатты PID: %s</string>\n    <string name=\"selinux_context\">SELinux контексті: %s</string>\n    <string name=\"supp_group\">Қосымша топ: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Жүйе қолданбаларын көрсету</string>\n    <string name=\"show_os_app\">ОЖ қолданбаларын көрсету</string>\n    <string name=\"hide_filter_hint\">Атауы бойынша іздеу</string>\n    <string name=\"hide_search\">Іздеу</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Ақпары жоқ)</string>\n    <string name=\"reboot_userspace\">Жүйеге қайта жүктеу</string>\n    <string name=\"reboot_recovery\">Recovery режіміне қайта жүктеу</string>\n    <string name=\"reboot_bootloader\">Bootloader-ге қайта жүктеу</string>\n    <string name=\"reboot_download\">Download режіміне қайта жүктеу</string>\n    <string name=\"reboot_edl\">EDL режіміне қайта жүктеу</string>\n    <string name=\"reboot_safe_mode\">Қауіпсіз режімге қайта жүктеу</string>\n    <string name=\"module_version_author\">%1$s, %2$s жасаған</string>\n    <string name=\"module_state_remove\">Жою</string>\n    <string name=\"module_state_restore\">Қалпына келтіру</string>\n    <string name=\"module_action_install_external\">Құрылғы жадынан орнату</string>\n    <string name=\"update_available\">Жаңарту қолжетімді</string>\n    <string name=\"suspend_text_riru\">Модуль сөндірілді, себебі %1$s қосылған</string>\n    <string name=\"suspend_text_zygisk\">Модуль сөндірілді, себебі %1$s қосылмаған</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk модулі үйлеспегендіктен жүктелмеді</string>\n    <string name=\"module_empty\">Еш модуль орнатылған жоқ</string>\n    <string name=\"confirm_install\">%1$s модулін орнатасыз ба?</string>\n    <string name=\"confirm_install_title\">Орнатуды растау</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Кейіп режімі</string>\n    <string name=\"settings_dark_mode_message\">Өзіңізге ұнаған режімді таңдаңыз!</string>\n    <string name=\"settings_dark_mode_light\">Әрдайым ақшыл</string>\n    <string name=\"settings_dark_mode_system\">Жүйедегідей</string>\n    <string name=\"settings_dark_mode_dark\">Әрдайым қараңғы</string>\n    <string name=\"settings_download_path_title\">Жүктеу бумасы</string>\n    <string name=\"settings_download_path_message\">Файлдар %1$s бумасына сақталады</string>\n    <string name=\"settings_hide_app_title\">Magisk қолданбасын жасыру</string>\n    <string name=\"settings_hide_app_summary\">Қолданба атауын өзгертіп, кездейсоқ десте атауы бар APK файлын құрастыру</string>\n    <string name=\"settings_restore_app_title\">Magisk қолданбасын қалпына келтіру</string>\n    <string name=\"settings_restore_app_summary\">Қолданбаны жасырын күйден шығарып, түпнұсқа APK орнату</string>\n    <string name=\"language\">Тіл</string>\n    <string name=\"system_default\">(Жүйедегідей)</string>\n    <string name=\"settings_check_update_title\">Жаңартуды тексеру</string>\n    <string name=\"settings_check_update_summary\">Жаңартудың бар-жоғын мезгіл-мезгіл тексеру</string>\n    <string name=\"settings_update_channel_title\">Жаңарту көзі</string>\n    <string name=\"settings_update_stable\">Тұрақты</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Басқа</string>\n    <string name=\"settings_update_custom_msg\">Басқа арнаның URL мекенжайын енгізіңіз</string>\n    <string name=\"settings_zygisk_summary\">Magisk кодын zygote процесіне енгізу</string>\n    <string name=\"settings_denylist_title\">DenyList-тi қолдану</string>\n    <string name=\"settings_denylist_summary\">Magisk жасаған барлық өзгеріс DenyList-тегі процестерден жасырылады.</string>\n    <string name=\"settings_denylist_config_title\">DenyList-тi баптау</string>\n    <string name=\"settings_denylist_config_summary\">DenyList-ке қосқыңыз келетін процестерді таңдаңыз</string>\n    <string name=\"settings_hosts_title\">Жүйеден тыс hosts</string>\n    <string name=\"settings_hosts_summary\">Жарнама өшіретін қолданбалар үшін /system бөлімінен тыс hosts файлын қолдану</string>\n    <string name=\"settings_hosts_toast\">Жүйеден тыс hosts модулі қосылды</string>\n    <string name=\"settings_app_name_hint\">Жаңа атау</string>\n    <string name=\"settings_app_name_helper\">Қолданбаның қайта құрылған нұсқасы осы атауға ие болады</string>\n    <string name=\"settings_app_name_error\">Жарамсыз пішім</string>\n    <string name=\"settings_su_app_adb\">Қолданбалар мен ADB</string>\n    <string name=\"settings_su_app\">Қолданбалар ғана</string>\n    <string name=\"settings_su_adb\">ADB ғана</string>\n    <string name=\"settings_su_disable\">Сөндірулі</string>\n    <string name=\"settings_su_request_10\">10 секунд</string>\n    <string name=\"settings_su_request_15\">15 секунд</string>\n    <string name=\"settings_su_request_20\">20 секунд</string>\n    <string name=\"settings_su_request_30\">30 секунд</string>\n    <string name=\"settings_su_request_45\">45 секунд</string>\n    <string name=\"settings_su_request_60\">60 секунд</string>\n    <string name=\"superuser_access\">Superuser құқығына рұқсат</string>\n    <string name=\"auto_response\">Сұранымға әдепкі жауап</string>\n    <string name=\"request_timeout\">Сұранымның күту уақыты</string>\n    <string name=\"superuser_notification\">Superuser мәлімдемесі</string>\n    <string name=\"settings_su_reauth_title\">Жаңартудан кейін қайта растау</string>\n    <string name=\"settings_su_reauth_summary\">Әлдебір қолданба жаңартылғаннан кейін оның Superuser құқығын қайта растау</string>\n    <string name=\"settings_su_tapjack_title\">Қабаттасудан қорғау</string>\n    <string name=\"settings_su_tapjack_summary\">Өзге қолданба Magisk терезесін бүркесе, Superuser құқығын беру батырмасы сөндіріледі (tapjacking шабуылына жол бермеу үшін)</string>\n    <string name=\"settings_su_auth_title\">Қолданушыны растау</string>\n    <string name=\"settings_su_auth_summary\">Superuser құқығына сұраным келгенде қолданушының құпиясөзін не биометриялық дерегін сұрау</string>\n    <string name=\"settings_su_auth_insecure\">Бұл құрылғыда аутентификация әдісі орнатылмаған</string>\n    <string name=\"settings_customization\">Сыртқы көрініс</string>\n    <string name=\"setting_add_shortcut_summary\">Жасырылған қолданбаны табу қиын болса, бастапқы экранға ыңғайлы таңбаша қосуға болады</string>\n    <string name=\"settings_doh_title\">HTTPS арқылы DNS</string>\n    <string name=\"settings_doh_description\">Кейбір елдерде қолданылатын жалған DNS-тен қорғау</string>\n\n    <string name=\"multiuser_mode\">Көп қолданушы режімі</string>\n    <string name=\"settings_owner_only\">Құрылғы иесі ғана</string>\n    <string name=\"settings_owner_manage\">Құрылғы иесінің бақылауында</string>\n    <string name=\"settings_user_independent\">Тәуелсіз қолданушылар</string>\n    <string name=\"owner_only_summary\">Құрылғы иесі ғана root құқығын қолдана алады</string>\n    <string name=\"owner_manage_summary\">Құрылғы иесі ғана сұранымдарды қабылдап, басқаларға root құқығын бере алады</string>\n    <string name=\"user_independent_summary\">Әр қолданушы өз root ережелерін қолдана алады</string>\n\n    <string name=\"mount_namespace_mode\">Mount атау кеңістігін баптау</string>\n    <string name=\"settings_ns_global\">Ғаламдық атау кеңістігі</string>\n    <string name=\"settings_ns_requester\">Мұраланған атау кеңістігі</string>\n    <string name=\"settings_ns_isolate\">Оқшауланған атау кеңістігі</string>\n    <string name=\"global_summary\">Root сеанстары ғаламдық атау кеңістігін қолданады</string>\n    <string name=\"requester_summary\">Root сеанстары қолданушының атау кеңістігін мұралайды</string>\n    <string name=\"isolate_summary\">Әр root сеансы өз атау кеңістігін қолданады</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk жаңартулары</string>\n    <string name=\"progress_channel\">Прогресс мәлімдемесі</string>\n    <string name=\"updated_channel\">Жаңарту аяқталды</string>\n    <string name=\"download_complete\">Жүктеу аяқталды</string>\n    <string name=\"download_file_error\">Файлды жүктеп алу қатесі</string>\n    <string name=\"magisk_update_title\">Magisk жаңартуы қолжетімді!</string>\n    <string name=\"updated_title\">Magisk жаңартылды</string>\n    <string name=\"updated_text\">Қолданбасын ашу үшін басыңыз</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Иә</string>\n    <string name=\"no\">Жоқ</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) орнату</string>\n    <string name=\"download\">Жүктеп алу</string>\n    <string name=\"reboot\">Қайта жүктеу</string>\n    <string name=\"release_notes\">Не жаңалық</string>\n    <string name=\"flashing\">Орнатуда…</string>\n    <string name=\"done\">Дайын!</string>\n    <string name=\"failure\">Қате!</string>\n    <string name=\"hide_app_title\">Magisk қолданбасын жасыруда…</string>\n    <string name=\"open_link_failed_toast\">Сілтемені аша алатын қолданба табылмады</string>\n    <string name=\"complete_uninstall\">Толығымен жою</string>\n    <string name=\"restore_img\">Жад бөлімдерін қалпына келтіру</string>\n    <string name=\"restore_img_msg\">Қалпына келтіруде…</string>\n    <string name=\"restore_done\">Жад қалпына келтірілді!</string>\n    <string name=\"restore_fail\">Өзгертілген бөлімдердің сақтық көшірмесі жоқ!</string>\n    <string name=\"setup_fail\">Орнату сәтсіз аяқталды</string>\n    <string name=\"env_fix_title\">Қосымша баптау керек</string>\n    <string name=\"env_fix_msg\">Magisk дұрыс жұмыс істеуі үшін қосымша әрекет қажет. Орнатуды жалғастырып, құрылғыны қайта қосасыз ба?</string>\n    <string name=\"env_full_fix_msg\">Magisk дұрыс жұмыс істеуі үшін оны қайта орнату керек. Magisk-ті қолданба арқылы қайта орнатыңыз; Recovery режімі құрылғыңыз туралы дұрыс ақпаратты таба алмайды.</string>\n    <string name=\"setup_msg\">Жұмыс ортасын баптауда…</string>\n    <string name=\"unsupport_magisk_title\">Үйлеспейтін Magisk нұсқасы</string>\n    <string name=\"unsupport_magisk_msg\">Қолданбаның осы нұсқасы Magisk-тің %1$s дейінгі нұсқаларына үйлеспейді.\\n\\nҚолданба Magisk орнатылмағандай жұмыс істейді; Magisk-ті жақын арада жаңартыңыз.</string>\n    <string name=\"unsupport_general_title\">Қалыпты емес жағдай</string>\n    <string name=\"unsupport_system_app_msg\">Бұл қолданба жүйе қолданбасы ретінде жұмыс істей алмайды. Оны қалыпты қолданба ретінде қайта орнатыңыз.</string>\n    <string name=\"unsupport_other_su_msg\">Magisk өзге бағдарламаға тиесілі \\\"su\\\" файлын анықтады. Қайшы келетін root қолданбасын жойып, Magisk-ті қайта орнатыңыз.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk қолданбасы сыртқы жадқа орнатылды. Қолданбаны ішкі жадқа жылжытыңыз.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Root құқығы жоғалғандықтан, жасырылған Magisk қолданбасы жұмыс істей алмайды. Түпнұсқа APK файлын орнатыңыз.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Мүмкіндікті қолдану үшін құрылғы жадына рұқсат беріңіз</string>\n    <string name=\"post_notifications_denied\">Мүмкіндікті қолдану үшін мәлімдеме жіберуге рұқсат беріңіз</string>\n    <string name=\"install_unknown_denied\">Мүмкіндікті қолдану үшін \"белгісіз қолданбаларды орнатуға\" рұқсат беріңіз</string>\n    <string name=\"add_shortcut_title\">Бастапқы экранға таңбаша қосу</string>\n    <string name=\"add_shortcut_msg\">Қолданба жасырылғаннан кейін, оның атауы мен таңбашасын тану қиын болуы мүмкін. Бастапқы экранға ыңғайлы таңбаша қосу керек пе?</string>\n    <string name=\"app_not_found\">Бұл әрекетті орындай алатын қолданба табылмады</string>\n    <string name=\"reboot_apply_change\">Өзгерістерді қолдану үшін құрылғыны қайта қосыңыз</string>\n    <string name=\"restore_app_confirmation\">Сөйтсеңіз, жасырын қолданба түпнұсқа қолданбаға айналады. Мұны істеу керек екеніне сенімдісіз бе?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ko/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">모듈</string>\n    <string name=\"superuser\">슈퍼유저</string>\n    <string name=\"logs\">로그</string>\n    <string name=\"settings\">설정</string>\n    <string name=\"install\">설치</string>\n    <string name=\"section_home\">홈</string>\n    <string name=\"section_theme\">테마</string>\n    <string name=\"denylist\">거부목록</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">인터넷 연결 없음</string>\n    <string name=\"app_changelog\">앱 변경 사항</string>\n    <string name=\"loading\">로딩중…</string>\n    <string name=\"update\">업데이트</string>\n    <string name=\"not_available\">알 수 없음</string>\n    <string name=\"hide\">숨기기</string>\n    <string name=\"home_package\">패키지</string>\n    <string name=\"home_app_title\">앱</string>\n\n    <string name=\"home_notice_content\">공식 Github 페이지에서 Magisk를 다운로드하십시오. 알 수 없는 출처에서 받은 파일이 위험할 수 있습니다!</string>\n    <string name=\"home_support_title\">후원하기</string>\n    <string name=\"home_item_source\">소스</string>\n    <string name=\"home_support_content\">Magisk는 항상 무료일 것이며, 오픈소스일 것입니다. 그러나 소액의 후원을 통해 관심을 표할 수 있습니다.</string>\n    <string name=\"home_installed_version\">설치됨</string>\n    <string name=\"home_latest_version\">최신</string>\n    <string name=\"invalid_update_channel\">잘못된 업데이트 채널</string>\n    <string name=\"uninstall_magisk_title\">Magisk 제거</string>\n    <string name=\"uninstall_magisk_msg\">모든 모듈이 비활성화/제거됩니다. 루트도 제거될 것이며, 데이터도 암호화 되어있지 않으면 암호화될 수도 있습니다.</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">강제 암호화 유지</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity 유지</string>\n    <string name=\"recovery_mode\">리커버리 모드</string>\n    <string name=\"install_options_title\">옵션</string>\n    <string name=\"install_method_title\">설치 방법</string>\n    <string name=\"install_next\">다음</string>\n    <string name=\"install_start\">설치</string>\n    <string name=\"manager_download_install\">눌러서 다운로드하고 설치</string>\n    <string name=\"direct_install\">바로 설치 (권장됨)</string>\n    <string name=\"install_inactive_slot\">비활성 슬롯에 설치 (OTA 이후)</string>\n    <string name=\"install_inactive_slot_msg\">기기를 다시 시작한 후에는 현재 비활성 상태인 슬롯으로 강제로 부팅됩니다!\\nOTA 업데이트 후에만 이 옵션을 사용하십시오.\\n계속하시겠습니까?</string>\n    <string name=\"setup_title\">추가 설정</string>\n    <string name=\"select_patch_file\">파일 선택 및 패치</string>\n    <string name=\"patch_file_msg\">raw 이미지 (*.img) 또는 ODIN tar 파일 (*.tar) 선택</string>\n    <string name=\"reboot_delay_toast\">5초 후 다시 시작합니다…</string>\n    <string name=\"flash_screen_title\">설치과정</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">슈퍼유저 요청</string>\n    <string name=\"touch_filtered_warning\">앱이 슈퍼유저 요청을 가려, Magisk에서 응답을 확인할 수 없습니다.</string>\n    <string name=\"deny\">모두 거부</string>\n    <string name=\"prompt\">물어보기</string>\n    <string name=\"grant\">모두 허용</string>\n    <string name=\"su_warning\">기기에 대한 슈퍼유저 권한을 부여합니다.\\n확실하지 않은 경우 거부하세요!</string>\n    <string name=\"forever\">영구</string>\n    <string name=\"once\">한 번만</string>\n    <string name=\"tenmin\">10분</string>\n    <string name=\"twentymin\">20분</string>\n    <string name=\"thirtymin\">30분</string>\n    <string name=\"sixtymin\">60분</string>\n    <string name=\"su_allow_toast\">%1$s에 슈퍼유저 권한이 허용됨</string>\n    <string name=\"su_deny_toast\">%1$s에 슈퍼유저 권한이 거부됨</string>\n    <string name=\"su_snack_grant\">%1$s의 슈퍼유저 권한이 허용됨</string>\n    <string name=\"su_snack_deny\">%1$s의 슈퍼유저 권한이 거부됨</string>\n    <string name=\"su_snack_notif_on\">%1$s의 알림이 활성화됨</string>\n    <string name=\"su_snack_notif_off\">%1$s의 알림이 비활성화됨</string>\n    <string name=\"su_snack_log_on\">%1$s의 로깅이 활성화됨</string>\n    <string name=\"su_snack_log_off\">%1$s의 로깅이 비활성화됨</string>\n    <string name=\"su_revoke_title\">제거하시겠습니까?</string>\n    <string name=\"su_revoke_msg\">정말 %1$s의 권한을 제거하시겠습니까?</string>\n    <string name=\"toast\">토스트</string>\n    <string name=\"none\">없음</string>\n\n    <string name=\"superuser_toggle_notification\">알림</string>\n    <string name=\"superuser_toggle_revoke\">권한 제거</string>\n    <string name=\"superuser_policy_none\">슈퍼유저 권한을 요청한 앱이 없습니다.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">로그가 없습니다. 슈퍼유저 권한을 필요로 하는 앱을 사용하십시오.</string>\n    <string name=\"log_data_magisk_none\">Magisk 로그가 없습니다.</string>\n    <string name=\"menuSaveLog\">로그 저장</string>\n    <string name=\"menuClearLog\">로그 삭제</string>\n    <string name=\"logs_cleared\">로그 삭제 완료.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Target UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">시스템 앱 표시</string>\n    <string name=\"show_os_app\">OS 앱 표시</string>\n    <string name=\"hide_filter_hint\">이름 검색</string>\n    <string name=\"hide_search\">검색</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(정보 없음)</string>\n    <string name=\"reboot_userspace\">조용히 다시 시작</string>\n    <string name=\"reboot_recovery\">복구 모드로 다시 시작</string>\n    <string name=\"reboot_bootloader\">부트로더로 다시 시작</string>\n    <string name=\"reboot_download\">다운로드 모드로 다시 시작</string>\n    <string name=\"reboot_edl\">EDL로 다시 시작</string>\n    <string name=\"module_version_author\">%1$s by %2$s</string>\n    <string name=\"module_state_remove\">제거</string>\n    <string name=\"module_state_restore\">복구</string>\n    <string name=\"module_action_install_external\">저장소에서 설치</string>\n    <string name=\"update_available\">업데이트 가능</string>\n    <string name=\"suspend_text_riru\">%1$s 가 활성화 되어있어 모듈이 로드되지 않았습니다.</string>\n    <string name=\"suspend_text_zygisk\">%1$s 가 활성화되어 있지 않아 모듈이 로드되지 않았습니다.</string>\n    <string name=\"zygisk_module_unloaded\">호환성 문제로 인해 Zygisk 모듈이 로드되지 않았습니다.</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">테마 선택</string>\n    <string name=\"settings_dark_mode_message\">원하는 테마 모드를 선택하세요!</string>\n    <string name=\"settings_dark_mode_light\">기본</string>\n    <string name=\"settings_dark_mode_system\">자동</string>\n    <string name=\"settings_dark_mode_dark\">다크 모드</string>\n    <string name=\"settings_download_path_title\">다운로드 위치</string>\n    <string name=\"settings_download_path_message\">파일이 %1$s에 저장됩니다</string>\n    <string name=\"settings_hide_app_title\">Magisk 앱 숨기기</string>\n    <string name=\"settings_hide_app_summary\">무작위 패키지명과 사용자 지정 앱 이름으로 Magisk 프록시 앱을 설치합니다.</string>\n    <string name=\"settings_restore_app_title\">Magisk 앱 복원</string>\n    <string name=\"settings_restore_app_summary\">앱 숨기기를 해제하고 원래 APK로 복원합니다.</string>\n    <string name=\"language\">언어</string>\n    <string name=\"system_default\">(시스템 기본값)</string>\n    <string name=\"settings_check_update_title\">업데이트 확인</string>\n    <string name=\"settings_check_update_summary\">백그라운드에서 주기적으로 업데이트를 확인합니다.</string>\n    <string name=\"settings_update_channel_title\">업데이트 채널</string>\n    <string name=\"settings_update_stable\">안정</string>\n    <string name=\"settings_update_beta\">베타</string>\n    <string name=\"settings_update_custom\">사용자 지정</string>\n    <string name=\"settings_update_custom_msg\">사용자 지정 URL 입력</string>\n    <string name=\"settings_zygisk_summary\">zygote 데몬에서 Magisk 부분 실행</string>\n    <string name=\"settings_denylist_title\">DenyList 적용</string>\n    <string name=\"settings_denylist_summary\">DenyList에 있는 프로세스를 실행할때 Magisk의 모든 수정사항을 되돌리고 실행합니다.</string>\n    <string name=\"settings_denylist_config_title\">DenyList 구성</string>\n    <string name=\"settings_denylist_config_summary\">Denylist에 포함할 프로세스를 선택합니다.</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">광고 차단 앱에서 사용하는 systemless hosts를 지원합니다.</string>\n    <string name=\"settings_hosts_toast\">Systemless hosts 모듈 추가됨</string>\n    <string name=\"settings_app_name_hint\">새 이름</string>\n    <string name=\"settings_app_name_helper\">이 이름으로 앱을 다시 패키징합니다.</string>\n    <string name=\"settings_app_name_error\">올바르지 않은 형식</string>\n    <string name=\"settings_su_app_adb\">앱 및 ADB</string>\n    <string name=\"settings_su_app\">앱만</string>\n    <string name=\"settings_su_adb\">ADB만</string>\n    <string name=\"settings_su_disable\">사용 안 함</string>\n    <string name=\"settings_su_request_10\">10초</string>\n    <string name=\"settings_su_request_15\">15초</string>\n    <string name=\"settings_su_request_20\">20초</string>\n    <string name=\"settings_su_request_30\">30초</string>\n    <string name=\"settings_su_request_45\">45초</string>\n    <string name=\"settings_su_request_60\">60초</string>\n    <string name=\"superuser_access\">슈퍼유저 액세스</string>\n    <string name=\"auto_response\">슈퍼유저 요청</string>\n    <string name=\"request_timeout\">요청 시간 제한</string>\n    <string name=\"superuser_notification\">슈퍼유저 알림</string>\n    <string name=\"settings_su_reauth_title\">업데이트 후 다시 승인</string>\n    <string name=\"settings_su_reauth_summary\">앱이 업데이트되면 권한 승인 요청을 다시 합니다.</string>\n    <string name=\"settings_su_tapjack_title\">탭재킹 보호 활성화</string>\n    <string name=\"settings_su_tapjack_summary\">슈퍼유저 프롬프트가 다른 창이나 오버레이로 가려지는 동안의 입력을 무시합니다.</string>\n    <string name=\"settings_customization\">커스터마이즈</string>\n    <string name=\"setting_add_shortcut_summary\">앱을 숨긴 후 아이콘과 이름을 알아보기 힘들 경우를 위해 알아보기 쉬운 바로가기를 홈 화면에 추가합니다.</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">일부 국가에 존재하는 DNS 포이즈닝을 해결합니다.</string>\n\n    <string name=\"multiuser_mode\">다중 사용자 모드</string>\n    <string name=\"settings_owner_only\">주인 사용자만</string>\n    <string name=\"settings_owner_manage\">주인 사용자에 의해 관리됨</string>\n    <string name=\"settings_user_independent\">사용자별 분리</string>\n    <string name=\"owner_only_summary\">주인 사용자만 루트 액세스를 갖습니다.</string>\n    <string name=\"owner_manage_summary\">주인 사용자가 다른 사용자들의 루트 액세스를 관리하고 요청을 받을 수 있습니다.</string>\n    <string name=\"user_independent_summary\">각각의 사용자가 권한을 관리합니다.</string>\n\n    <string name=\"mount_namespace_mode\">네임스페이스 마운트 모드</string>\n    <string name=\"settings_ns_global\">전역 네임스페이스</string>\n    <string name=\"settings_ns_requester\">네임스페이스 상속</string>\n    <string name=\"settings_ns_isolate\">격리된 네임스페이스</string>\n    <string name=\"global_summary\">모든 루트 세션이 전역 마운트 네임스페이스를 사용합니다.</string>\n    <string name=\"requester_summary\">루트 세션은 요청자의 네임스페이스를 상속합니다.</string>\n    <string name=\"isolate_summary\">각각의 루트 세션은 자신만의 독립된 네임스페이스를 사용합니다.</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk 업데이트</string>\n    <string name=\"progress_channel\">진행 상황</string>\n    <string name=\"updated_channel\">업데이트 완료</string>\n    <string name=\"download_complete\">다운로드 완료</string>\n    <string name=\"download_file_error\">파일 다운로드 실패</string>\n    <string name=\"magisk_update_title\">새 버전의 Magisk를 사용할 수 있습니다!</string>\n    <string name=\"updated_title\">Magisk가 업데이트 되었습니다!</string>\n    <string name=\"updated_text\">터치하여 앱 열기</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">예</string>\n    <string name=\"no\">아니오</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) 설치</string>\n    <string name=\"download\">다운로드</string>\n    <string name=\"reboot\">다시 시작</string>\n    <string name=\"release_notes\">수정사항</string>\n    <string name=\"flashing\">설치 중…</string>\n    <string name=\"done\">완료!</string>\n    <string name=\"failure\">실패!</string>\n    <string name=\"hide_app_title\">Magisk 앱 숨기는 중…</string>\n    <string name=\"open_link_failed_toast\">링크를 열 수 있는 앱이 없습니다.</string>\n    <string name=\"complete_uninstall\">완전히 제거</string>\n    <string name=\"restore_img\">이미지 복구</string>\n    <string name=\"restore_img_msg\">복구하는 중…</string>\n    <string name=\"restore_done\">복구 완료!</string>\n    <string name=\"restore_fail\">백업이 존재하지 않습니다!</string>\n    <string name=\"setup_fail\">설치 실패</string>\n    <string name=\"env_fix_title\">추가 설정 필요</string>\n    <string name=\"env_fix_msg\">Magisk가 제대로 작동하려면 추가 설정이 필요합니다. 다시 시작 하시겠습니까?</string>\n    <string name=\"setup_msg\">환경 설정 진행 중…</string>\n    <string name=\"unsupport_magisk_title\">지원되지 않는 Magisk 버전</string>\n    <string name=\"unsupport_magisk_msg\">이 버전의 앱은 %1$s 미만의 Magisk 버전을 지원하지 않습니다.\\n\\n해당앱은 Magisk가 설치되지 않은것처럼 동작할것입니다. 가능한 빨리 Magisk 를 업데이트 하세요.</string>\n    <string name=\"unsupport_general_title\">비정상적인 상태</string>\n    <string name=\"unsupport_system_app_msg\">해당 앱을 시스템 앱으로 실행하는 것은 지원되지 않습니다. 앱을 일반 사용자 앱으로 실행해 주세요.</string>\n    <string name=\"unsupport_other_su_msg\">Magisk으로 부터 설치되지 않은 \\\"su\\\" 바이너리가 감지되었습니다. 다른 루팅 방법을 제거하거나, Magisk 를 다시 설치해주세요.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk 가 외부 저장소에 설치되어 있습니다. Magisk 를 내부 저장소에 설치 해주세요.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">이 숨겨진 Magisk 앱은 루트 권한이 손실되어 사용할 수 없습니다. 원래 APK 를 복원하거나 재설치 해주세요.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">해당 기능을 사용하려면 저장소 권한을 허용해 주십시오.</string>\n    <string name=\"install_unknown_denied\">이 기능을 활성화 하려면 \"출처를 알 수 없는 앱 설치\"를 허용해주세요.</string>\n    <string name=\"add_shortcut_title\">홈 화면에 바로가기 추가</string>\n    <string name=\"add_shortcut_msg\">앱을 숨긴 후 아이콘과 이름을 알아보기 힘들 경우를 위해 알아보기 쉬운 바로가기를 홈 화면에 추가합니다.</string>\n    <string name=\"app_not_found\">해당 작업을 처리할 어플리케이션이 없습니다.</string>\n    <string name=\"reboot_apply_change\">변경 사항을 적용하려면 재부팅하세요.</string>\n    <string name=\"restore_app_confirmation\">정말로 실행하시겠습니까? 숨겨진 앱이 원본 앱으로 복원됩니다.</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ku/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">زیادکراوەکان</string>\n    <string name=\"superuser\">سوپەر یوسەر</string>\n    <string name=\"logs\">تۆمارەکان</string>\n    <string name=\"settings\">ڕێکخستنەکان</string>\n    <string name=\"install\">دامەزراندن</string>\n    <string name=\"section_home\">ماڵەوە</string>\n    <string name=\"section_theme\">ڕووکارەکان</string>\n    <string name=\"denylist\">پێڕستی ڕێگەپێنەدراوەکان</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">هێڵ بەردەست نییە</string>\n    <string name=\"app_changelog\">گۆڕانکارییەکان</string>\n    <string name=\"loading\">کردنەوە…</string>\n    <string name=\"update\">بەرزکردنەوە</string>\n    <string name=\"not_available\">نییە</string>\n    <string name=\"hide\">شاردنەوە</string>\n    <string name=\"home_package\">پاکێج</string>\n    <string name=\"home_app_title\">ئەپ</string>\n\n    <string name=\"home_notice_content\">تەنها لە گیتهەبی فەرمی ماجیسک دابگرە، لە شوێنی تر لەوانەیە زیانبەخش بێت</string>\n    <string name=\"home_support_title\">پشتگیریمان بکە</string>\n    <string name=\"home_follow_title\">شوێنمان بکەوە</string>\n    <string name=\"home_item_source\">سەرچاوە</string>\n    <string name=\"home_support_content\">ماجیسک بە خۆڕاییە و هەر واش ئەمێنێتەوە، بەهەرحاڵ ئەتوانیت پشتگیرییەکمان بکەی بۆ گرنگی پێدان</string>\n    <string name=\"home_installed_version\">داگیراوە</string>\n    <string name=\"home_latest_version\">دوایین وەشان</string>\n    <string name=\"invalid_update_channel\">کەناڵێکی نوێکردنەوەی هەڵە</string>\n    <string name=\"uninstall_magisk_title\">سڕینەوەی ماجیسک</string>\n    <string name=\"uninstall_magisk_msg\">هەموو زیادکراوەکان دەسڕێنەوە، ڕۆت دەسڕێتەوە، و هەر ڕەمزێنراوێک بەهۆی ماجیسک کرابێ لادەچێت!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">ڕەمزاندنی بەزۆر بهێڵەوە</string>\n    <string name=\"keep_dm_verity\">بهێڵەوە AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">دۆخی ڕیکەڤەڕی</string>\n    <string name=\"install_options_title\">هەڵبژاردنەکان</string>\n    <string name=\"install_method_title\">ڕێگای</string>\n    <string name=\"install_next\">دواتر</string>\n    <string name=\"install_start\">با بیکەین</string>\n    <string name=\"manager_download_install\">بۆ داگرتن و ڕێکخستن کرتە بکە</string>\n    <string name=\"direct_install\">داگرتنی ڕاستەوخۆ(پێشنیارکراوە)</string>\n    <string name=\"install_inactive_slot\">دایبگرە بۆ خانەی ناچالاک(پاش OTA)</string>\n    <string name=\"install_inactive_slot_msg\">ئێستا ئامێرەکەت دەچێتە خانە ناچالاکەکە، ئەمە بەکاربهێنە تەنها دوای ئەپدەیت کردن لە ڕێگەی OTA، بەردەوام دەبیت؟</string>\n    <string name=\"setup_title\">ڕێکخستنی زیاتر</string>\n    <string name=\"select_patch_file\">فایلێک هەڵبژێرە و پینەی بکە</string>\n    <string name=\"patch_file_msg\">تکایە فایلێکی Tar یان img یان payload.bin هەڵبژێرە</string>\n    <string name=\"reboot_delay_toast\">ڕێستارت کردنەوە لە ماوەی ٥ چرکە…</string>\n    <string name=\"flash_screen_title\">ڕێکخستن</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">داواکاری سوپەریوسەر</string>\n    <string name=\"touch_filtered_warning\">ئەپێک لە سەر شاشەکەیە، ناتوانین دڵنیا بینەوە</string>\n    <string name=\"deny\">ڕەتکردنەوە</string>\n    <string name=\"prompt\">داواکاری</string>\n    <string name=\"grant\">ڕێگەپێدان</string>\n    <string name=\"su_warning\">ڕێگەپێدان بۆ تەواوی ئامێرەکەت، گەر دڵنیا نیت ڕەتی بکەوە</string>\n    <string name=\"forever\">بۆ هەمیشە</string>\n    <string name=\"once\">بۆ یەکجار</string>\n    <string name=\"tenmin\">بۆ ١٠ خولەک</string>\n    <string name=\"twentymin\">بۆ ٢٠ خولەک</string>\n    <string name=\"thirtymin\">بۆ ٣٠ خولەک</string>\n    <string name=\"sixtymin\">بۆ ٦٠ خولەک</string>\n    <string name=\"su_allow_toast\">%1$s ڕێگەپێدانی سوپەریوسەری بۆ زیادکرا</string>\n    <string name=\"su_deny_toast\">%1$s ڕێگەپێدانی سوپەریوسەر ڕەتکرایەوە</string>\n    <string name=\"su_snack_grant\">ڕێگەپێدانی سوپەریوسەری %1$s بۆ درا</string>\n    <string name=\"su_snack_deny\">ڕێگەپێدانی سوپەریوسەر %1$s ڕەتکرایەوە</string>\n    <string name=\"su_snack_notif_on\">ئاگەدارکردنەوەکانی %1$s کارا کراوە</string>\n    <string name=\"su_snack_notif_off\">ئاگەدارکردنەوەکانی %1$s کوژاوەتەوە</string>\n    <string name=\"su_snack_log_on\">تۆمارەکانی %1$s کراوەتەوە</string>\n    <string name=\"su_snack_log_off\">تۆمارەکانی %1$s کوژاوەتەوە</string>\n    <string name=\"su_revoke_title\">لابردن؟</string>\n    <string name=\"su_revoke_msg\">دڵنیابەوە بۆ لابردنی سوپەریوسەر بۆ %1$s </string>\n    <string name=\"toast\">هێنانەسەر</string>\n    <string name=\"none\">هیچ</string>\n\n    <string name=\"superuser_toggle_notification\">ئاگادارییەکان</string>\n    <string name=\"superuser_toggle_revoke\">لابردن</string>\n    <string name=\"superuser_policy_none\">هیچ ئەپێک تا ئێستا داوای سوپەریوسەری نەکردووە</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">هیچ تۆمارێک نییە، ئەو ئەپانەی ڕۆتیان پێویستە زوزو بەکاریبێنە</string>\n    <string name=\"log_data_magisk_none\">تۆمارەکانی ماجیسک بەتاڵن، باشە بۆ؟</string>\n    <string name=\"menuSaveLog\">تۆمارەکان هەڵبگرە</string>\n    <string name=\"menuClearLog\">تۆمارەکان بسڕەوە</string>\n    <string name=\"logs_cleared\">بەسەرکەوتویی تۆمارەکان سڕانەوە</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">ئامانج UID: %1$d</string>\n    <string name=\"target_pid\">Mount ns target PID: %s</string>\n    <string name=\"selinux_context\">SELinux context: %s</string>\n    <string name=\"supp_group\">Supplementary group: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">پیشاندانی ئەپەکانی سیستەم</string>\n    <string name=\"show_os_app\">پیشاندانی ئەپەکان</string>\n    <string name=\"hide_filter_hint\">پاڵاوتنی بەپێی ناو</string>\n    <string name=\"hide_search\">گەڕان</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(هیچ زانیارییەک نییە)</string>\n    <string name=\"reboot_userspace\">ڕێستارت کردنەوە</string>\n    <string name=\"reboot_recovery\">چوونە ناو ڕیکەڤەری</string>\n    <string name=\"reboot_bootloader\">چوونە ناو بووتلۆدەر</string>\n    <string name=\"reboot_download\">چوونە ناو داونلۆد</string>\n    <string name=\"reboot_edl\">چوونە ناو EDL</string>\n    <string name=\"reboot_safe_mode\">دۆخی پارێزراو</string>\n    <string name=\"module_version_author\">%1$s by %2$s</string>\n    <string name=\"module_state_remove\">سڕینەوە</string>\n    <string name=\"module_action\">کارا</string>\n    <string name=\"module_state_restore\">گەڕاندنەوە</string>\n    <string name=\"module_action_install_external\">لە بیرگەکەتەوە ڕێکی بخە</string>\n    <string name=\"update_available\">وەشانی نوێ بەردەستە</string>\n    <string name=\"suspend_text_riru\">زیادکراوەکە کار ناکات چونکە %1$s کراوەتەوە</string>\n    <string name=\"suspend_text_zygisk\">زیادکراوەکە کارناکات چونکە %1$s نەکراوەتەوە</string>\n    <string name=\"zygisk_module_unloaded\">زیادکراوی Zygisk بەهۆی نەگونجان کارناکات</string>\n    <string name=\"module_empty\">هیچ زیادکراوێک دانەبەزیوە</string>\n    <string name=\"confirm_install\">دابەزاندنی زیادکراو %1$s?</string>\n    <string name=\"confirm_install_title\">دڵنیابوونەوە لە دابەزاندن</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">جۆری ڕووکار</string>\n    <string name=\"settings_dark_mode_message\">حەزت لە کامەی بوو ئەوە هەڵبژێرە</string>\n    <string name=\"settings_dark_mode_light\">هەمیشە دۆخی ڕوناک</string>\n    <string name=\"settings_dark_mode_system\">با بەگوێرەی سیستەمەکە بێت!</string>\n    <string name=\"settings_dark_mode_dark\">هەمیشە دۆخی تاریک</string>\n    <string name=\"settings_download_path_title\">شوێنی داگرتنەکە</string>\n    <string name=\"settings_download_path_message\">فایلەکان هەڵدەگیرێن لە %1$s</string>\n    <string name=\"settings_hide_app_title\">شاردنەوەی ئەپی ماجیسک</string>\n    <string name=\"settings_hide_app_summary\">داگرتنی ماجیسک بە ناوی جیاوە</string>\n    <string name=\"settings_restore_app_title\">ئەپە ڕەسەنەکە بهێنەوە</string>\n    <string name=\"settings_restore_app_summary\">ئەپەکە دەربخەوە و ڕەسەنڵ</string>\n    <string name=\"language\">زمان</string>\n    <string name=\"system_default\">(وەک هی ئامێرەکە)</string>\n    <string name=\"settings_check_update_title\">گەڕان بەدوای نوێکاری</string>\n    <string name=\"settings_check_update_summary\">گەڕان بەدوای نوێکاری خۆکارانە</string>\n    <string name=\"settings_update_channel_title\">کەناڵی نوێکاری</string>\n    <string name=\"settings_update_stable\">جێگیر</string>\n    <string name=\"settings_update_beta\">پێشوەختە(بێتا)</string>\n    <string name=\"settings_update_custom\">تایبەت</string>\n    <string name=\"settings_update_custom_msg\">بەستەرێکی تایبەت دابنێ</string>\n    <string name=\"settings_zygisk_summary\">کارپێکردنی بەشێکی ماجیسک لە zygote daemon</string>\n    <string name=\"settings_denylist_title\">پێڕستی نەرێنی کراوەکان</string>\n    <string name=\"settings_denylist_summary\">هەر ئەپێک لە پێڕستی نەرێنییەکان کاریگەریەکانی ماجیسکی لەسەر نییە</string>\n    <string name=\"settings_denylist_config_title\">دەستکاریکردنی پێڕستی نەرێنیکراوەکان</string>\n    <string name=\"settings_denylist_config_summary\">ئەو ئەپە هەڵبژێرە کە دەتەوێت نەرێنیی بکەیت</string>\n    <string name=\"settings_hosts_title\">هۆستی ناسیستەمی</string>\n    <string name=\"settings_hosts_summary\"> هۆستی ناسیستەمی بۆ لابردنی ڕیکلامەکان</string>\n    <string name=\"settings_hosts_toast\"> هۆستی ناسیستەمی زیادکرا</string>\n    <string name=\"settings_app_name_hint\">ناوی نوێ</string>\n    <string name=\"settings_app_name_helper\">ئەپەکە بەم ناوەوە دروست دەکرێتەوە</string>\n    <string name=\"settings_app_name_error\">هەڵەیە</string>\n    <string name=\"settings_su_app_adb\">ئەپەکان و ADB</string>\n    <string name=\"settings_su_app\">تەنها ئەپەکان</string>\n    <string name=\"settings_su_adb\">ADB تەنها</string>\n    <string name=\"settings_su_disable\">ناچالاک کراوە</string>\n    <string name=\"settings_su_request_10\">10 چرکە</string>\n    <string name=\"settings_su_request_15\">15 چرکە</string>\n    <string name=\"settings_su_request_20\">20 چرکە</string>\n    <string name=\"settings_su_request_30\">30 چرکە</string>\n    <string name=\"settings_su_request_45\">45 چرکە</string>\n    <string name=\"settings_su_request_60\">60 چرکە</string>\n    <string name=\"superuser_access\">دەسەڵاتی سوپەریوسەر</string>\n    <string name=\"auto_response\">وەڵامدانەوەی خۆکارانە</string>\n    <string name=\"request_timeout\">ماوەی وەڵامدانەوە</string>\n    <string name=\"superuser_notification\">ئاگەدارییەکانی سوپەریوسەر</string>\n    <string name=\"settings_su_reauth_title\">پرسیاربکەوە دوای هەر نوێکردنەوەیەک</string>\n    <string name=\"settings_su_reauth_summary\">دوای نوێکردنەوەی ئەپەکان دووبارە پرسیار بکەوە بۆ دەسەڵاتی سوپەریوسەر</string>\n    <string name=\"settings_su_tapjack_title\">پارێزگاری کردن لە ئەگەری دەستلێدانی تر</string>\n    <string name=\"settings_su_tapjack_summary\"> کاتێک ئەپێکی تر بەسەر شاشەکەوەیە، سوپەر یوسەر وەڵام ناداتەوە لە کردنی هەر بژاردەیەک لەبەر پارێزراوی</string>\n    <string name=\"settings_su_auth_title\">دڵنیاکردنەوەی کەسی</string>\n    <string name=\"settings_su_auth_summary\">داواکاری بکە بۆ دڵنیاکردنەوەی کەسی لەکاتی داواکاری سوپەریوسەر</string>\n    <string name=\"settings_su_auth_insecure\">هیچ دڵنیاکردنەوەیەک نییە</string>\n    <string name=\"settings_customization\">دەستکاریکردن</string>\n    <string name=\"setting_add_shortcut_summary\">یەک ئایکۆنی جوان زیادبکە بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە</string>\n    <string name=\"settings_doh_title\">DNS بەسەر HTTPS</string>\n    <string name=\"settings_doh_description\">Workaround DNS خراپە لە هەندێک شوێن</string>\n    <string name=\"settings_random_name_title\">ناوێک لەخۆیەوە</string>\n    <string name=\"settings_random_name_description\">دانانی ناوێک لەخۆوە تاوەکوو ئاشکرا نەبێت</string>\n\n    <string name=\"multiuser_mode\">دۆخی فرەبەکارهێنەر</string>\n    <string name=\"settings_owner_only\">تەنها خاوەنی ئامێر</string>\n    <string name=\"settings_owner_manage\">خاوەنی ئامێر</string>\n    <string name=\"settings_user_independent\">بەکارهێنەری سەربەخۆ</string>\n    <string name=\"owner_only_summary\">تەنها خاوەنەکە دۆخی ڕۆتی هەیە</string>\n    <string name=\"owner_manage_summary\">تەنها خاوەنەکە دەسەڵاتی بەکارهێنانی ڕۆتی هەیە</string>\n    <string name=\"user_independent_summary\">هەر بەکارهێنەرێک یاسای جیاوازی هەیە</string>\n\n    <string name=\"mount_namespace_mode\">چونە دۆخی بۆشایی ناو</string>\n    <string name=\"settings_ns_global\">بۆشاییناوی گشتی</string>\n    <string name=\"settings_ns_requester\">بۆشایی ناوی خۆیی</string>\n    <string name=\"settings_ns_isolate\">بۆشایی ناوی جیا</string>\n    <string name=\"global_summary\">هەمو ڕۆتەکان ناوی گشتی بەکار ئەهێنن</string>\n    <string name=\"requester_summary\">هەمو ڕۆتەکان ناوی خۆیی بەکار ئەهێنن</string>\n    <string name=\"isolate_summary\">هەمو ڕۆتەکان ناوی جیا بەکار ئەهێنن</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">نوێکردنەوەکانی ماجیسک</string>\n    <string name=\"progress_channel\">ئاگادارییە کاراکان</string>\n    <string name=\"updated_channel\">نوێکردنەوە سەرکەوتووبوو</string>\n    <string name=\"download_complete\">داگرتن سەرکەوتووبوو</string>\n    <string name=\"download_file_error\">هەڵەیەک رووی دا لەکاتی داگرتنی فایلەکە</string>\n    <string name=\"magisk_update_title\">وەشانی نوێی ماجیسک ئامادەیە!</string>\n    <string name=\"updated_title\">ماجیسک نوێکراوە!</string>\n    <string name=\"updated_text\">کرتە بکە بۆ کردنەوەی ئەپ</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">بەڵێ</string>\n    <string name=\"no\">نەخێر</string>\n    <string name=\"repo_install_title\">داگرتن %1$s %2$s(%3$d)</string>\n    <string name=\"download\">داگرتن</string>\n    <string name=\"reboot\">ڕێستارت</string>\n    <string name=\"close\">داخستن</string>\n    <string name=\"release_notes\">نێبینییەکان</string>\n    <string name=\"flashing\">فلاش کردن</string>\n    <string name=\"running\">کار کردن...</string>\n    <string name=\"done\">تەواو!</string>\n    <string name=\"done_action\">کارکردنی %1$s تەواو بوو</string>\n    <string name=\"failure\">Failed!</string>\n    <string name=\"hide_app_title\">شاردنەوەی ئەپی ماجیسک…</string>\n    <string name=\"open_link_failed_toast\">هیچ ئەپێک تییە تا لینکەکەی پێ بکرێتەوە</string>\n    <string name=\"complete_uninstall\">سڕینەوەی تەواوی</string>\n    <string name=\"restore_img\">هێنانەوەی img</string>\n    <string name=\"restore_img_msg\">هێنانەوە…</string>\n    <string name=\"restore_done\">هاتەوە!</string>\n    <string name=\"restore_fail\">هیچ فایلێکی هەڵگیراوت نیە!</string>\n    <string name=\"setup_fail\">ڕێکخستن شکستی هێنا</string>\n    <string name=\"env_fix_title\">پێویستی بە ڕێکخستنی زیاترە</string>\n    <string name=\"env_fix_msg\">مۆبایلەکەت پێویستی بە ڕێکخستنی زیاترە، ئایا ئەتەوێت بەردەوام بیت و ڕێستارتی بکەیتەوە؟</string>\n    <string name=\"env_full_fix_msg\"> پێویستە دوبارە ماجیسک دابگریتەوە بۆ ئەوەی بەباشی کاربکات تکایە ماجیسک دابگرەوە لەناو ئەپەکە خۆی چونکە لە ڕیکەڤەرییەوە ناتوانرێ زانیاری تەواو لەسەر ئامێرەکە دەستبخرێت </string>\n    <string name=\"setup_msg\">دەستپێکردن....</string>\n    <string name=\"unsupport_magisk_title\">وەشانی ماجیسک پاڵپشتینەکراوە</string>\n    <string name=\"unsupport_magisk_msg\">وەشانی ماجیسکەکەت زۆر کۆنە وەک ئەوە وایە هەر نەبێت، تکایە نوێی بکەوە بە زوترین کات</string>\n    <string name=\"unsupport_general_title\">باری نائاسایی</string>\n    <string name=\"unsupport_system_app_msg\">ئەم ئەپە وەکو ئەپی سیستەم کارناکات، تکایە بیگۆڕەوە بۆ ئەپی ئاسایی</string>\n    <string name=\"unsupport_other_su_msg\"> \\\"su\\\" binary یەکی بێگانە دۆزرایەوە، تکایە جگە لە ماجیسک ئەپی تر بەکارمەهێنە بۆ ڕۆت کردن  </string>\n    <string name=\"unsupport_external_storage_msg\">ماجیسک لە بیرگەی دەرەکی داگیراوە، تکایە بیبەوە بۆ ناوەکی</string>\n    <string name=\"unsupport_nonroot_stub_msg\">ئەپە شاراوەکە کار ناکات چونکە ڕۆتەکە نەماوە، تکایە ئەپە ڕەسەنەکە بگەڕێنەوە</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">ڕەزامەندی بیرگە بدە تاوەکوو ئەمە کار بکات</string>\n    <string name=\"post_notifications_denied\">ڕەزامەندی ئاگاداری بکە تاوەکوو ئەمە کار بکات</string>\n    <string name=\"install_unknown_denied\">ڕەزامەندی \"install unknown apps\" تاوەکوو کار بکات</string>\n    <string name=\"add_shortcut_title\">زیادی بکە بۆ سەر شاشە</string>\n    <string name=\"add_shortcut_msg\">دوای شاردنەوەی ئەم ئەپە، ئەتەوێت یەک ئایکۆنی جوان زیادبکەیت بۆ سەر شاشەکە ئەگەر قورس بوو ئەوەی خۆی بدۆزیتەوە؟</string>\n    <string name=\"app_not_found\">هیچ ئەپێک نەدۆزرایەوە تاوەکوو ئەم کارەی پێ بکرێت</string>\n    <string name=\"reboot_apply_change\">ڕێستارت بکە تاوەکوو کاریگەریەکان کار بکەن</string>\n    <string name=\"restore_app_confirmation\">ئەمە ئەپە ڕەسەنەکە ئەهێنێتەوە، دڵنیایت لە کردنی؟</string>\n\n</resources>\n      \n"
  },
  {
    "path": "app/core/src/main/res/values-lt/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduliai</string>\n    <string name=\"superuser\">Supernaudotojas</string>\n    <string name=\"logs\">Žurnalas</string>\n    <string name=\"settings\">Nustatymai</string>\n    <string name=\"install\">Įdiegti</string>\n    <string name=\"section_home\">Pagrindinis</string>\n    <string name=\"section_theme\">Temos</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nėra ryšio</string>\n    <string name=\"app_changelog\">Keitimų sąrašas</string>\n    <string name=\"loading\">Įkeliama…</string>\n    <string name=\"update\">Naujinti</string>\n    <string name=\"not_available\">Neįdiegta</string>\n    <string name=\"hide\">Slėpti</string>\n    <string name=\"home_package\">Paketas</string>\n    <string name=\"home_app_title\">Programėlė</string>\n\n    <string name=\"home_notice_content\">Magisk siųskites TIK iš oficialiojo GitHub puslapio. Failai iš nežinomų šaltinių gali būti kenkėjiški!</string>\n    <string name=\"home_support_title\">Palaikykite mus</string>\n    <string name=\"home_follow_title\">Sekite mus</string>\n    <string name=\"home_item_source\">Pirminis kodas</string>\n    <string name=\"home_support_content\">Magisk yra ir visada bus nemokamas, atvirojo kodo. Tačiau galite paaukoti pinigų, jei norite parodyti, kad jums rūpi.</string>\n    <string name=\"home_installed_version\">Įdiegta versija</string>\n    <string name=\"home_latest_version\">Naujausia versija</string>\n    <string name=\"invalid_update_channel\">Neteisingas naujinimų kanalas</string>\n    <string name=\"uninstall_magisk_title\">Pašalinti Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Visi moduliai bus išjungti/pašalinti!\\nRoot prieiga bus pašalinta!\\nVidinė atmintis, iššifruota su Magisk, bus iš naujo užšifruota!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Neišjungti priverstinio šifravimo</string>\n    <string name=\"keep_dm_verity\">Neišjungti AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Atkūrimo režimas</string>\n    <string name=\"install_options_title\">Parinktys</string>\n    <string name=\"install_method_title\">Būdas</string>\n    <string name=\"install_next\">Toliau</string>\n    <string name=\"install_start\">Įdiegti</string>\n    <string name=\"manager_download_install\">Spustelėkite, kad atsisiųstumėte ir įdiegtumėte</string>\n    <string name=\"direct_install\">Tiesioginis įdiegimas (rekomenduojama)</string>\n    <string name=\"install_inactive_slot\">Įdiegti į antrąją vietą (po OTA naujinimo)</string>\n    <string name=\"install_inactive_slot_msg\">Jūsų įremginys bus PRIVERSTAS pasileisti į dabartinę neaktyvią vietą po paleidimo iš naujo!\\nNaudokite šią parinktį, kai OTA naujinimas yra baigtas.\\nTęsti?</string>\n    <string name=\"setup_title\">Išplėstinė sąranka</string>\n    <string name=\"select_patch_file\">Pasirinkite ir taisykite failą</string>\n    <string name=\"patch_file_msg\">Pasirinkite vaizdo failą (*.img) arba ODIN archyvą (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Paleidžiama iš naujo po 5 sekundžių…</string>\n    <string name=\"flash_screen_title\">Įdiegimas</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Supernaudotojo užklausa</string>\n    <string name=\"touch_filtered_warning\">Programėlė užstoja supernaudotojo užklausą, todėl Magisk negali patvirtinti jūsų atsakymo</string>\n    <string name=\"deny\">Atmesti</string>\n    <string name=\"prompt\">Klausti</string>\n    <string name=\"grant\">Leisti</string>\n    <string name=\"su_warning\">Suteikia prieigą prie jūsų įrenginio.\\nAtmeskite, jei nesate tikri!</string>\n    <string name=\"forever\">Amžinai</string>\n    <string name=\"once\">Kartą</string>\n    <string name=\"tenmin\">10 minučių</string>\n    <string name=\"twentymin\">20 minučių</string>\n    <string name=\"thirtymin\">30 minučių</string>\n    <string name=\"sixtymin\">60 minučių</string>\n    <string name=\"su_allow_toast\">%1$s suteiktos supernaudotojo teisės</string>\n    <string name=\"su_deny_toast\">%1$s atmestos supernaudotojo teisės</string>\n    <string name=\"su_snack_grant\">%1$s supernaudotojo teisės yra suteiktos</string>\n    <string name=\"su_snack_deny\">%1$s supernaudotojo teisės yra atmestos</string>\n    <string name=\"su_snack_notif_on\">%1$s pranešimai yra įjungti</string>\n    <string name=\"su_snack_notif_off\">%1$s pranešimai yra išjungti</string>\n    <string name=\"su_snack_log_on\">%1$s registravimas yra įjungtas</string>\n    <string name=\"su_snack_log_off\">%1$s registravimas yra išjungtas</string>\n    <string name=\"su_revoke_title\">Atšaukti?</string>\n    <string name=\"su_revoke_msg\">Patvirtinkite %1$s supernaudotojo teisių atšaukimą</string>\n    <string name=\"toast\">Iššokantys pranešimai</string>\n    <string name=\"none\">Nėra</string>\n\n    <string name=\"superuser_toggle_notification\">Pranešimai</string>\n    <string name=\"superuser_toggle_revoke\">Atšaukti</string>\n    <string name=\"superuser_policy_none\">Kol kas jokia programėlė neprašė supernaudotojo teisių.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Žurnalas tuščias</string>\n    <string name=\"log_data_magisk_none\">Magisk žurnalas yra tuščias, keistoka</string>\n    <string name=\"menuSaveLog\">Išsaugoti žurnalą</string>\n    <string name=\"menuClearLog\">Valyti žurnalą</string>\n    <string name=\"logs_cleared\">Žurnalas išvalytas</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Tikslinis UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Rodyti sistemos programėles</string>\n    <string name=\"show_os_app\">Rodyti OS programėles</string>\n    <string name=\"hide_filter_hint\">Filtruoti pagal pavadinimą</string>\n    <string name=\"hide_search\">Ieškoti</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nėra informacijos)</string>\n    <string name=\"reboot_userspace\">Paleidimas neišjungus</string>\n    <string name=\"reboot_recovery\">Paleidimas į Recovery</string>\n    <string name=\"reboot_bootloader\">Paleidimas į Bootloader</string>\n    <string name=\"reboot_download\">Paleidimas į Download</string>\n    <string name=\"reboot_edl\">Paleidimas į EDL</string>\n    <string name=\"module_version_author\">%1$s nuo %2$s</string>\n    <string name=\"module_state_remove\">Šalinti</string>\n    <string name=\"module_state_restore\">Atkurti</string>\n    <string name=\"module_action_install_external\">Įdiegti iš saugyklos</string>\n    <string name=\"update_available\">Prieinamas naujinimas</string>\n    <string name=\"suspend_text_riru\">Modulis išjungtas, nes %1$s yra įjungtas</string>\n    <string name=\"suspend_text_zygisk\">Modulis išjungtas, nes %1$s nėra įjungtas</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk moduis neįkeltas dėl nesuderinamumo</string>\n    <string name=\"module_empty\">Modulių nėra</string>\n    <string name=\"confirm_install\">Įdiegti modulį %1$s?</string>\n    <string name=\"confirm_install_title\">Įdiegimo patvirtinimas</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Temos režimas</string>\n    <string name=\"settings_dark_mode_message\">Pasirinkite režimą, kuris geriausiai atitinka jūsų stilių!</string>\n    <string name=\"settings_dark_mode_light\">Visada šviesi</string>\n    <string name=\"settings_dark_mode_system\">Sistemos</string>\n    <string name=\"settings_dark_mode_dark\">Visada tamsi</string>\n    <string name=\"settings_download_path_title\">Atsisiuntimo kelias</string>\n    <string name=\"settings_download_path_message\">Failai bus išsaugoti %1$s</string>\n    <string name=\"settings_hide_app_title\">Slėpti Magisk programėlę</string>\n    <string name=\"settings_hide_app_summary\">Įdiegti įgaliotąją programėlę su atsitiktiniu paketo ID ir kitu pavadinimu</string>\n    <string name=\"settings_restore_app_title\">Atkurti Magisk programėlę</string>\n    <string name=\"settings_restore_app_summary\">Atkurti programėlę ir originalų APK</string>\n    <string name=\"language\">Kalba</string>\n    <string name=\"system_default\">(Sistemos numatytoji)</string>\n    <string name=\"settings_check_update_title\">Naujinimų tikrinimas</string>\n    <string name=\"settings_check_update_summary\">Periodiškai fone tikrinti, ar nėra naujinimų</string>\n    <string name=\"settings_update_channel_title\">Naujinimų kanalas</string>\n    <string name=\"settings_update_stable\">Stabilus</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Tinkintas</string>\n    <string name=\"settings_update_custom_msg\">Įdėkite tinkinto kanalo URL</string>\n    <string name=\"settings_zygisk_summary\">Vykdyti Magisk dalis zygote tarnyboje</string>\n    <string name=\"settings_denylist_title\">Aktyvinti DenyList</string>\n    <string name=\"settings_denylist_summary\">Visos procesų, esančių denylist, Magisk modifikacijos bus atkurtos</string>\n    <string name=\"settings_denylist_config_title\">Konfigūruoti DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Pasirinkite procesus, kuriuos norite įtraukti į denylist</string>\n    <string name=\"settings_hosts_title\">Basisteminis hosts</string>\n    <string name=\"settings_hosts_summary\">Basisteminis hosts palaiko reklamų blokatorių programėlėms</string>\n    <string name=\"settings_hosts_toast\">Pridėtas besisteminio hosts modulis</string>\n    <string name=\"settings_app_name_hint\">Naujas pavadinimas</string>\n    <string name=\"settings_app_name_helper\">Programėlė bus perpakuota su šiuo pavadinimu</string>\n    <string name=\"settings_app_name_error\">Netinkamas formatas</string>\n    <string name=\"settings_su_app_adb\">Programėlės ir ADB</string>\n    <string name=\"settings_su_app\">Tik programėlės</string>\n    <string name=\"settings_su_adb\">Tik ADB</string>\n    <string name=\"settings_su_disable\">Išjungta</string>\n    <string name=\"settings_su_request_10\">10 sekundžių</string>\n    <string name=\"settings_su_request_15\">15 sekundžių</string>\n    <string name=\"settings_su_request_20\">20 sekundžių</string>\n    <string name=\"settings_su_request_30\">30 sekundžių</string>\n    <string name=\"settings_su_request_45\">45 sekundės</string>\n    <string name=\"settings_su_request_60\">60 sekundžių</string>\n    <string name=\"superuser_access\">Prieigos lygis</string>\n    <string name=\"auto_response\">Automatinis atsakymas</string>\n    <string name=\"request_timeout\">Atsakymo laukimas</string>\n    <string name=\"superuser_notification\">Supernaudotojo pranešimas</string>\n    <string name=\"settings_su_reauth_title\">Pakartotinis autentifikavimas</string>\n    <string name=\"settings_su_reauth_summary\">Iš naujo prašyti supernaudotojo teisių po programėlių naujinimo</string>\n    <string name=\"settings_su_tapjack_title\">Bakstelėjimų perėmimo apsauga</string>\n    <string name=\"settings_su_tapjack_summary\">Supernaudotojo užklausos langas bus neaktyvus, kol jis yra užstotas kito lango</string>\n    <string name=\"settings_customization\">Tinkinimas</string>\n    <string name=\"setting_add_shortcut_summary\">Pridėti nuorodą į pagrindinį ekraną, jei po programėlės paslėpimo yra sunku atpažinti jos pavadinimą ir piktogramą</string>\n    <string name=\"settings_doh_title\">DNS virš HTTPS</string>\n    <string name=\"settings_doh_description\">Aktyvinti DoH (naudokite, jei kyla problemų jungiantis prie tinklo</string>\n\n    <string name=\"multiuser_mode\">Daugelio naudotojų režimas</string>\n    <string name=\"settings_owner_only\">Tik įrenginio savininkas</string>\n    <string name=\"settings_owner_manage\">Valdoma įrenginio savininko</string>\n    <string name=\"settings_user_independent\">Naudotojų taisyklės</string>\n    <string name=\"owner_only_summary\">Tik savininkas turi root prieigą</string>\n    <string name=\"owner_manage_summary\">Tik savininkas gali tvarkyti root prieigą ir gauti užklausas</string>\n    <string name=\"user_independent_summary\">Kiekvienas naudotojas turi savo atskiras root taisykles</string>\n\n    <string name=\"mount_namespace_mode\">Pavadinimų erdvės režimas</string>\n    <string name=\"settings_ns_global\">Bendra pavadinimų erdvė</string>\n    <string name=\"settings_ns_requester\">Paveldima pavadinimų erdvė</string>\n    <string name=\"settings_ns_isolate\">Izoliuota pavadinimų erdvė</string>\n    <string name=\"global_summary\">Visos root sesijos naudos bendrą pavainimų erdvę</string>\n    <string name=\"requester_summary\">Root sesijos paveldės prašytojų pavadinimų erdvę</string>\n    <string name=\"isolate_summary\">Kiekviena root sesija turės savo izoliuotą pavadinimų erdvę</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk naujinimai</string>\n    <string name=\"progress_channel\">Progreso pranešimai</string>\n    <string name=\"updated_channel\">Naujinimas baigtas</string>\n    <string name=\"download_complete\">Atsisiuntimas baigtas</string>\n    <string name=\"download_file_error\">Klaida atsisiunčiant failą</string>\n    <string name=\"magisk_update_title\">Prieinamas Magisk naujinimas!</string>\n    <string name=\"updated_title\">Magisk atnaujintas</string>\n    <string name=\"updated_text\">Bakstelėkite, kad atidarytumėte programėlę</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Taip</string>\n    <string name=\"no\">Ne</string>\n    <string name=\"repo_install_title\">Įdiegimas %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Atsisiųsti</string>\n    <string name=\"reboot\">Paleisti iš naujo</string>\n    <string name=\"release_notes\">Laidos informacija</string>\n    <string name=\"flashing\">Diegiama…</string>\n    <string name=\"done\">Baigta!</string>\n    <string name=\"failure\">Nepavyko!</string>\n    <string name=\"hide_app_title\">Slepiama Magisk programėlė…</string>\n    <string name=\"open_link_failed_toast\">Nerasta programėlių nuorodos atidarymui</string>\n    <string name=\"complete_uninstall\">Visiškas pašalinimas</string>\n    <string name=\"restore_img\">Atkurti vaizdą</string>\n    <string name=\"restore_img_msg\">Atkuriama…</string>\n    <string name=\"restore_done\">Atkūrimas baigtas!</string>\n    <string name=\"restore_fail\">Nėra atsarginės kopijos!</string>\n    <string name=\"setup_fail\">Sąranka nepavyko</string>\n    <string name=\"env_fix_title\">Reikalinga išplėstinė sąranka</string>\n    <string name=\"env_fix_msg\">Jūsų įrenginiui reikalinga išplėstinė sąranka, kad Magisk veiktų tinkamai. Norite tęsti ir įrenginį paleisti iš naujo?</string>\n    <string name=\"env_full_fix_msg\">Jūsų įrenginiui reikia iš naujo įdiegti Magisk, kad jis veiktų tinkamai. Iš naujo įdiekite Magisk su programėle, atkūrimo režimas negali gauti teisingos įrenginio informacijos.</string>\n    <string name=\"setup_msg\">Vykdoma Running aplinkos sąranka…</string>\n    <string name=\"unsupport_magisk_title\">Nepalaikoma Magisk versija</string>\n    <string name=\"unsupport_magisk_msg\">Ši programėlės versija nepalaiko Magisk versijų, žemesnių už %1$s.\\n\\nProgramėlė veiks taip, lyg Magisk yra neįdiegtas. Kuo greičiau atnaujinkite Magisk.</string>\n    <string name=\"unsupport_general_title\">Nenormali būsena</string>\n    <string name=\"unsupport_system_app_msg\">Šios programėlės vykdymas kaip sistemos programėlės yra nepalaikomas. Nustatykite ją atgal į naudotojo programėlę.</string>\n    <string name=\"unsupport_other_su_msg\">Aptiktas dvejetainis failas \\\"su\\\" ne iš Magisk. Pašalinkite pašalinį root teisių tiekėją ir/arba iš naujo įdiekite Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk yra įdiegtas išorinėje saugykloje. Perkelkite programėlę į vidinę saugyklą.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Paslėpta Magisk programėlė negali tęsti darbo, nes root teisės buvo prarastos. Atkurkite originalų APk.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Suteikti prieigą prie saugyklos</string>\n    <string name=\"post_notifications_denied\">Suteikti prieigą prie pranešimų</string>\n    <string name=\"install_unknown_denied\">Suteikti prieigą prie įdiegimo iš nežinomų šaltinių</string>\n    <string name=\"add_shortcut_title\">Pridėti nuorodą į pagrindinį ekraną</string>\n    <string name=\"add_shortcut_msg\">Po šios programėlės paslėpimo jos pavadinimas, piktograma gali būti sunkiai atpažįstamos. Norite pridėti nuorodą į pagrindinį ekraną?</string>\n    <string name=\"app_not_found\">Nerasta programėlė, galinti atlikti šį veiksmą</string>\n    <string name=\"reboot_apply_change\">Paleiskite iš naujo, kad pritaikytumėte pakeitimus</string>\n    <string name=\"restore_app_confirmation\">Šis veiksmas atkurs paslėptą programėlę į originalią būseną. Tikrai norite tai padaryti?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-mk/strings.xml",
    "content": "<resources>\n\n    <!--Welcome Activity-->\n    <string name=\"modules\">Модули</string>\n    <string name=\"superuser\">Супер-корисник</string>\n    <string name=\"logs\">Записник</string>\n    <string name=\"settings\">Поставки</string>\n    <string name=\"install\">Инсталирај</string>\n    <string name=\"unsupport_magisk_title\">Неподдржана верзија на Magisk</string>\n\n    <!--Status Fragment-->\n    <string name=\"invalid_update_channel\">Невалиден канал за ажурирања</string>\n    <string name=\"keep_force_encryption\">Задржи ја присилната енкрипција</string>\n    <string name=\"keep_dm_verity\">Задржи AVB 2.0/dm-verity</string>\n    <string name=\"uninstall_magisk_title\">Деинсталирај го Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Сите модули ќе бидат оневозможени/oтстранети. Рут привилегиите ќе бидат отстранети, а вашите податоци може да се енкриптираат ако моментално не се.</string>\n    <string name=\"update\">Ажурирај</string>\n\n    <!--Module Fragment-->\n    <string name=\"no_info_provided\">(Нема информации)</string>\n    <string name=\"reboot_recovery\">Рестартирај во Recovery режим</string>\n    <string name=\"reboot_bootloader\">Рестартирај во Bootloader режим</string>\n    <string name=\"reboot_download\">Рестартирај во Download режим</string>\n\n    <!--Repo Fragment-->\n    <string name=\"update_available\">Достапно е ажурирање</string>\n    <string name=\"home_installed_version\">Инсталирано</string>\n\n    <!--Log Fragment-->\n    <string name=\"menuSaveLog\">Зачувај записник</string>\n    <string name=\"menuClearLog\">Исчисти го записникот сега</string>\n    <string name=\"logs_cleared\">Записникот е успешно исчистен.</string>\n\n    <!--About Activity-->\n    <string name=\"app_changelog\">Листа на промени</string>\n\n    <!-- System Components, Notifications -->\n    <string name=\"update_channel\">Magisk Ажурирања</string>\n    <string name=\"progress_channel\">Известувања за прогресот</string>\n    <string name=\"download_complete\">Преземањето е завршено</string>\n    <string name=\"download_file_error\">Грешка при преземање на фајлот</string>\n    <string name=\"magisk_update_title\">Достапно е ажурирање на Magisk!</string>\n\n    <!-- Installation -->\n    <string name=\"manager_download_install\">Притисни за преземање и инсталирање.</string>\n    <string name=\"direct_install\">Директна инсталација (Препорачано)</string>\n    <string name=\"install_inactive_slot\">Инсталирај во неактивен слот (по ОТА)</string>\n    <string name=\"install_inactive_slot_msg\">Вашиот уред ќе биде ПРИНУДЕН да се подигне на тековниот неактивен слот по рестартирањето!\\nОваа опција користете ја само откако OTA ажурирање е направено.\\nПродолжи?</string>\n    <string name=\"setup_title\">Дополнителни подесувања</string>\n    <string name=\"select_patch_file\">Избери и обнови ја датотеката</string>\n    <string name=\"patch_file_msg\">Избери само image датотека (*.img) или ODIN tar датотека (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Рестартирање за 5 секунди…</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"repo_install_title\">Инсталирај %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Преземи</string>\n    <string name=\"reboot\">Рестартирај</string>\n    <string name=\"release_notes\">Белешки за изданието</string>\n\n    <string name=\"flashing\">Применувам</string>\n    <string name=\"open_link_failed_toast\">Не е пронајдена апликација за отворање на врската.</string>\n    <string name=\"complete_uninstall\">Целосно деинсталирање</string>\n    <string name=\"restore_img\">Врати image датотеки</string>\n    <string name=\"restore_img_msg\">Враќање…</string>\n    <string name=\"restore_done\">Враќањето заврши!</string>\n    <string name=\"restore_fail\">Не постои оригинална резервна копија!</string>\n    <string name=\"setup_fail\">Поставувањето е неуспешно.</string>\n    <string name=\"env_fix_title\">Потребни е дополнително поставување</string>\n    <string name=\"setup_msg\">Надградба на работната околина…</string>\n\n    <!--Settings Activity -->\n    <string name=\"language\">Јазик</string>\n    <string name=\"system_default\">(Стандарден системски)</string>\n    <string name=\"settings_check_update_title\">Провери за ажурирања</string>\n    <string name=\"settings_check_update_summary\">Периодично проверувај за ажурирања во позадина.</string>\n    <string name=\"settings_update_channel_title\">Канал за ажурирања</string>\n    <string name=\"settings_update_stable\">Стабилен</string>\n    <string name=\"settings_update_beta\">Бета</string>\n    <string name=\"settings_update_custom\">Прилагодено</string>\n    <string name=\"settings_update_custom_msg\">Внеси прилагоден URL линк</string>\n    <string name=\"settings_hosts_title\">Несистемски хостови</string>\n    <string name=\"settings_hosts_summary\">Поддршка за несистемски хостови за Adblock апликации.</string>\n    <string name=\"settings_hosts_toast\">Додаден е модул за несистемски хостови</string>\n\n    <string name=\"settings_su_app_adb\">Апликации и АДБ</string>\n    <string name=\"settings_su_app\">Само апликации</string>\n    <string name=\"settings_su_adb\">Само АДБ</string>\n    <string name=\"settings_su_disable\">Оневозможено</string>\n    <string name=\"settings_su_request_10\">10 секунди</string>\n    <string name=\"settings_su_request_15\">15 секунди</string>\n    <string name=\"settings_su_request_20\">20 секунди</string>\n    <string name=\"settings_su_request_30\">30 секунди</string>\n    <string name=\"settings_su_request_45\">45 секунди</string>\n    <string name=\"settings_su_request_60\">60 секунди</string>\n    <string name=\"superuser_access\">Пристап за супер-корисник</string>\n    <string name=\"auto_response\">Автоматски одговор</string>\n    <string name=\"request_timeout\">Временско ограничување на барање</string>\n    <string name=\"superuser_notification\">Супер-корисник известување</string>\n    <string name=\"settings_su_reauth_title\">Повторна автентикација по надградба</string>\n    <string name=\"settings_su_reauth_summary\">Повторна автентикација за супер-корисник дозвола по надградбата на апликацијата</string>\n\n    <string name=\"multiuser_mode\">Режим на повеќе корисници</string>\n    <string name=\"settings_owner_only\">Само сопственикот на уредот</string>\n    <string name=\"settings_owner_manage\">Управувано од сопственикот на уредот</string>\n    <string name=\"settings_user_independent\">Независно од корисникот</string>\n    <string name=\"owner_only_summary\">Само сопственикот има рут пристап.</string>\n    <string name=\"owner_manage_summary\">Само сопственикот може да управува со рут пристапот и да ги прима барања за рут пристап.</string>\n    <string name=\"user_independent_summary\">Секој корисник има сопствени рут правила.</string>\n\n    <string name=\"mount_namespace_mode\">Поставете го режимот на именски простор</string>\n    <string name=\"settings_ns_global\">Глобален именски простор</string>\n    <string name=\"settings_ns_requester\">Наследи именски простор</string>\n    <string name=\"settings_ns_isolate\">Изолиран именски простор</string>\n    <string name=\"global_summary\">Сите рут сесии го користат глобалниот именски простор.</string>\n    <string name=\"requester_summary\">Рут сесиите ќе го наследат именскиот простор на нивниот барател.</string>\n    <string name=\"isolate_summary\">Секоја рут сесија ќе има свој изолиран именски простор.</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Супер-корисник барање</string>\n    <string name=\"deny\">Одбиј</string>\n    <string name=\"prompt\">Прашај</string>\n    <string name=\"grant\">Одобри</string>\n    <string name=\"su_warning\">Дава целосен пристап на вашиот уред.\\nОдбијте ако не сте сигурни!</string>\n    <string name=\"forever\">Засекогаш</string>\n    <string name=\"once\">Еднаш</string>\n    <string name=\"tenmin\">10 минути</string>\n    <string name=\"twentymin\">20 минути</string>\n    <string name=\"thirtymin\">30 минути</string>\n    <string name=\"sixtymin\">60 минути</string>\n    <string name=\"su_allow_toast\">На %1$s се доделени правата на супер-корисник</string>\n    <string name=\"su_deny_toast\">На %1$s се одбиени правата на супер-корисник</string>\n    <string name=\"su_snack_grant\">Правата за супер-корисник од %1$s се одобрени</string>\n    <string name=\"su_snack_deny\">Правата за супер-корисник од %1$s се одбиени</string>\n    <string name=\"su_snack_notif_on\">Известувањата од %1$s се овозможени</string>\n    <string name=\"su_snack_notif_off\">Известувањата од %1$s се оневозможени</string>\n    <string name=\"su_snack_log_on\">Записникот на настани е овозможен за %1$s</string>\n    <string name=\"su_snack_log_off\">Записникот на настани е оневозможен %1$s</string>\n    <string name=\"su_revoke_title\">Анулирај?</string>\n    <string name=\"su_revoke_msg\">Дали потврдувате анулирање на поставките за пристап на %1$s?</string>\n    <string name=\"toast\">Тост</string>\n    <string name=\"none\">Ниеден</string>\n\n    <!--Superuser logs-->\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Целен UID: %1$d</string>\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Прикажи ги системските апликации</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ml/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">മൊഡ്യുൾസ്</string>\n    <string name=\"superuser\">സൂപ്പർയുസർ</string>\n    <string name=\"logs\">ലോഗ്സ്</string>\n    <string name=\"settings\">സെറ്റിംഗ്സ്</string>\n    <string name=\"install\">ഇൻസ്റ്റോൾ</string>\n    <string name=\"section_home\">ഹോം</string>\n    <string name=\"section_theme\">തീമുകൾ</string>\n    <string name=\"denylist\">നിഷിദ്ധ പട്ടിക</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">കണക്ഷൻ ലഭ്യമല്ല</string>\n    <string name=\"app_changelog\">മാറ്റങ്ങൾ</string>\n    <string name=\"loading\">ലോഡിംഗ്...</string>\n    <string name=\"update\">അപ്ഡേറ്റ്</string>\n    <string name=\"not_available\">ലഭ്യമല്ല</string>\n    <string name=\"hide\">മറയ്ക്കൂ</string>\n    <string name=\"home_package\">പാക്കേജ്</string>\n    <string name=\"home_app_title\">ആപ്പ്</string>\n\n    <string name=\"home_notice_content\">ഔദ്യോഗിക ഗിറ്റ്ഹബ് പേജിൽ നിന്ന് മാത്രം മജിസ്‌ക് ഡൗൺലോഡ് ചെയ്യുക. അജ്ഞാത ഉറവിടങ്ങളിൽ നിന്നുള്ള ഫയലുകൾ ക്ഷുദ്രകരമാകാം!</string>\n    <string name=\"home_support_title\">ഞങ്ങളെ തുണയ്‌ക്കുക</string>\n    <string name=\"home_follow_title\">ഞങ്ങളെ ഫോളോ ചെയ്യുക</string>\n    <string name=\"home_item_source\">സോഴ്സ്</string>\n    <string name=\"home_support_content\">മജിസ്‌ക് എല്ലായ്പ്പോഴും സ്വതന്ത്രവും ഓപ്പൺ സോഴ്‌സും ആയിരിക്കും. എന്നിരുന്നാലും, ഒരു സംഭാവന നൽകിക്കൊണ്ട് നിങ്ങൾ പിന്തുണയ്ക്കുന്നുവെന്ന് ഞങ്ങളെ കാണിക്കാനാകും.</string>\n    <string name=\"home_installed_version\">ഇൻസ്റ്റാൾ ചെയ്ത പതിപ്പ്</string>\n    <string name=\"home_latest_version\">പുതിയ പതിപ്പ്</string>\n    <string name=\"invalid_update_channel\">അപ്‌ഡേറ്റ് ചാനൽ അസാധുവാണ്</string>\n    <string name=\"uninstall_magisk_title\">മജിസ്‌ക് അൺഇൻസ്റ്റാൾ ചെയ്യുക</string>\n    <string name=\"uninstall_magisk_msg\">എല്ലാ മൊഡ്യൂളുകളും പ്രവർത്തനരഹിതമാക്കും/നീക്കം ചെയ്യപ്പെടും!\\nറൂട്ട് നീക്കം ചെയ്യപ്പെടും!\\nമജിസ്‌ക് വഴി  അൺഎൻക്രിപ്റ്റ്  ചെയ്യപ്പെട്ട ഇന്റെര്ണല് സ്റ്റോറേജുകൾ വീണ്ടും എൻക്രിപ്റ്റ് ചെയ്യപ്പെടും!!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">ഫോഴ്‌സ് എൻക്രിപ്ഷൻ സംരക്ഷിക്കുക</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity സംരക്ഷിക്കുക</string>\n    <string name=\"recovery_mode\">റിക്കവറി മോഡ്</string>\n    <string name=\"install_options_title\">ഓപ്ഷനുകൾ</string>\n    <string name=\"install_method_title\">രീതി</string>\n    <string name=\"install_next\">അടുത്തത്</string>\n    <string name=\"install_start\">നമുക്ക് തുടങ്ങാം</string>\n    <string name=\"manager_download_install\">ഡൗൺലോഡ് ചെയ്ത് ഇൻസ്റ്റാൾ ചെയ്യാൻ അമർത്തുക</string>\n    <string name=\"direct_install\">നേരിട്ടുള്ള ഇൻസ്റ്റാളേഷൻ (ശുപാർശ ചെയ്യുന്നത്)</string>\n    <string name=\"install_inactive_slot\">നിഷ്ക്രിയ സ്ലോട്ടിലേക്ക് ഇൻസ്‌റ്റാൾ ചെയ്യുക (OTA-ന് ശേഷം)</string>\n    <string name=\"install_inactive_slot_msg\">റീബൂട്ടിന് ശേഷം നിങ്ങളുടെ ഉപകരണം നിലവിലെ നിഷ്‌ക്രിയ സ്ലോട്ടിലേക്ക് ബൂട്ട് ചെയ്യാൻ നിർബന്ധിതരാകും!\\nOTA പൂർത്തിയാക്കിയതിന് ശേഷം മാത്രം ഈ ഓപ്ഷൻ ഉപയോഗിക്കുക.\\nതുടരണോ?</string>\n    <string name=\"setup_title\">അധിക സജ്ജീകരണം</string>\n    <string name=\"select_patch_file\">ഒരു ഫയൽ തിരഞ്ഞെടുത്ത് പാച്ച് ചെയ്യുക</string>\n    <string name=\"patch_file_msg\">ഒരു റോ ഇമേജ് (*.img) അല്ലെങ്കിൽ ഒരു ODIN ടാർ ഫയൽ (*.tar) തിരഞ്ഞെടുക്കുക</string>\n    <string name=\"reboot_delay_toast\">5 സെക്കൻഡിൽ റീബൂട്ട് ചെയ്യുന്നു…</string>\n    <string name=\"flash_screen_title\">ഇൻസ്റ്റലേഷൻ</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">സൂപ്പർയൂസർ അഭ്യർത്ഥന</string>\n    <string name=\"touch_filtered_warning\">ഒരു ആപ്പ് ഒരു സൂപ്പർയൂസർ അഭ്യർത്ഥന മറയ്ക്കുന്നതിനാൽ, മജിസ്‌ക്-ന് നിങ്ങളുടെ പ്രതികരണം പരിശോധിക്കാൻ കഴിയില്ല</string>\n    <string name=\"deny\">നിഷേധിക്കുക</string>\n    <string name=\"prompt\">പ്രോംപ്റ്റ്</string>\n    <string name=\"grant\">അനുവദിക്കുക</string>\n    <string name=\"su_warning\">നിങ്ങളുടെ ഉപകരണത്തിലേക്ക് പൂർണ്ണ ആക്‌സസ് നൽകും.\\nനിങ്ങൾക്ക് ഉറപ്പില്ലെങ്കിൽ നിരസിക്കുക!</string>\n    <string name=\"forever\">എന്നേക്കും</string>\n    <string name=\"once\">ഒരിക്കൽ</string>\n    <string name=\"tenmin\">10 മിനിറ്റ്</string>\n    <string name=\"twentymin\">20 മിനിറ്റ്</string>\n    <string name=\"thirtymin\">30 മിനിറ്റ്</string>\n    <string name=\"sixtymin\">60 മിനിറ്റ്</string>\n    <string name=\"su_allow_toast\">%1$s-ന് സൂപ്പർയൂസർ അവകാശം ലഭിച്ചു</string>\n    <string name=\"su_deny_toast\">%1$s-ന് സൂപ്പർയൂസർ അവകാശം നിഷേധിച്ചു </string>\n    <string name=\"su_snack_grant\">%1$s-ന്റെ സൂപ്പർയൂസർ അവകാശം അനുവദിച്ചിരിക്കുന്നു </string>\n    <string name=\"su_snack_deny\">1$s-ന്റെ സൂപ്പർയൂസർ അവകാശം നിഷേധിക്കപ്പെട്ടിരിക്കുന്നു</string>\n    <string name=\"su_snack_notif_on\">%1$s-ന്റെ അറിയിപ്പുകൾ പ്രവർത്തനക്ഷമമാക്കി</string>\n    <string name=\"su_snack_notif_off\">%1$s-ന്റെ അറിയിപ്പുകൾ പ്രവർത്തനരഹിതമാക്കി</string>\n    <string name=\"su_snack_log_on\">%1$s-ന്റെ ലോഗിംഗ് പ്രവർത്തനക്ഷമമാക്കി</string>\n    <string name=\"su_snack_log_off\">%1$s-ന്റെ ലോഗിംഗ് പ്രവർത്തനരഹിതമാക്കി</string>\n    <string name=\"su_revoke_title\">പിൻവലിക്കണോ?</string>\n    <string name=\"su_revoke_msg\">%1$s-ന്റെ സൂപ്പർയൂസർ അവകാശം അസാധുവാക്കാൻ സ്ഥിരീകരിക്കുക</string>\n    <string name=\"toast\">ടോസ്റ്റ്</string>\n    <string name=\"none\">ഒന്നുമില്</string>\n\n    <string name=\"superuser_toggle_notification\">അറിയിപ്പുകൾ</string>\n    <string name=\"superuser_toggle_revoke\">പിന്‍വലിക്കുക</string>\n    <string name=\"superuser_policy_none\">ആപ്പുകളൊന്നും ഇതുവരെ സൂപ്പർയൂസർ അനുമതി ചോദിച്ചിട്ടില്ല.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">നിങ്ങൾക്ക് ലോഗുകൾ ഒന്നുമില്ലലോ, റൂട്ട് ആപ്പുകൾ കൂടുതൽ ഉപയോഗിക്കൂ</string>\n    <string name=\"log_data_magisk_none\">മജിസ്‌ക് ലോഗുകൾ കാലിയാണ്, \\nഎവിടെയോ എന്തോ ഒരു തകരാറു പോലെ</string>\n    <string name=\"menuSaveLog\">ലോഗ് സേവ് ചെയ്യുക</string>\n    <string name=\"menuClearLog\">ലോഗ് മായ്‌ക്കുക</string>\n    <string name=\"logs_cleared\">ലോഗ് മായ്ച്ചു</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">ടാർഗെറ്റ് UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">സിസ്റ്റം ആപ്പുകൾ കാണിക്കുക</string>\n    <string name=\"show_os_app\">OS ആപ്പുകൾ കാണിക്കുക</string>\n    <string name=\"hide_filter_hint\">പേര് പ്രകാരം ഫിൽട്ടർ ചെയ്യുക</string>\n    <string name=\"hide_search\">തിരയുക</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(വിവരങ്ങളൊന്നും നൽകിയിട്ടില്ല)</string>\n    <string name=\"reboot_userspace\">സോഫ്റ്റ് റീബൂട്ട്</string>\n    <string name=\"reboot_recovery\">റിക്കവറിയിലേക്ക് റീബൂട്ട് ചെയ്യുക</string>\n    <string name=\"reboot_bootloader\">ബൂട്ട്ലോഡറിലേക്ക് റീബൂട്ട് ചെയ്യുക</string>\n    <string name=\"reboot_download\">ഡൗൺലോഡ് മോഡിലേക്ക് റീബൂട്ട് ചെയ്യുക</string>\n    <string name=\"reboot_edl\">EDL-ലേക്ക് റീബൂട്ട് ചെയ്യുക</string>\n    <string name=\"module_version_author\">%1$s നിർമ്മിച്ചത് %2$s</string>\n    <string name=\"module_state_remove\">നീക്കുക</string>\n    <string name=\"module_state_restore\">പുനഃസ്ഥാപിക്കുക</string>\n    <string name=\"module_action_install_external\">സ്റ്റോറേജിൽ നിന്ന് ഇൻസ്റ്റാൾ ചെയ്യുക</string>\n    <string name=\"update_available\">അപ്ഡേറ്റ് ലഭ്യമാണ്</string>\n    <string name=\"suspend_text_riru\">%1$s പ്രവർത്തനക്ഷമമാക്കിയതിനാൽ മൊഡ്യൂൾ താൽക്കാലികമായി നിർത്തി</string>\n    <string name=\"suspend_text_zygisk\">%1$s പ്രവർത്തനക്ഷമമാക്കാത്തതിനാൽ മൊഡ്യൂൾ താൽക്കാലികമായി നിർത്തി</string>\n    <string name=\"zygisk_module_unloaded\">പൊരുത്തക്കേട് കാരണം Zygisk മൊഡ്യൂൾ ലോഡ് ചെയ്തിട്ടില്ല</string>\n    <string name=\"module_empty\">മൊഡ്യൂൾ ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ല</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">തീം മോഡ്</string>\n    <string name=\"settings_dark_mode_message\">നിങ്ങളുടെ ശൈലിക്ക് ഏറ്റവും അനുയോജ്യമായ മോഡ് തിരഞ്ഞെടുക്കുക!</string>\n    <string name=\"settings_dark_mode_light\">എപ്പോഴും ലൈറ്റ്</string>\n    <string name=\"settings_dark_mode_system\">സിസ്റ്റം തീം</string>\n    <string name=\"settings_dark_mode_dark\">എപ്പോഴും ഡാർക്ക്</string>\n    <string name=\"settings_download_path_title\">ഡൗൺലോഡ് സ്ഥലം</string>\n    <string name=\"settings_download_path_message\">ഫയലുകൾ %1$s-ലേക്ക് സേവ് ചെയ്യപ്പെടും</string>\n    <string name=\"settings_hide_app_title\">മജിസ്‌ക് ആപ്പ് മറയ്ക്കുക</string>\n    <string name=\"settings_hide_app_summary\">ക്രമരഹിതമായ പാക്കേജ് ഐഡിയും ഇഷ്‌ടാനുസൃത ആപ്പ് ലേബലും ഉള്ള ഒരു പ്രോക്‌സി ആപ്പ് ഇൻസ്‌റ്റാൾ ചെയ്യുക</string>\n    <string name=\"settings_restore_app_title\">മജിസ്‌ക് ആപ്പ് പുനഃസ്ഥാപിക്കുക</string>\n    <string name=\"settings_restore_app_summary\">ആപ്പ് മറച്ചത് മാറ്റി യഥാർത്ഥ APK പുനഃസ്ഥാപിക്കുക</string>\n    <string name=\"language\">ഭാഷ</string>\n    <string name=\"system_default\">(സിസ്റ്റം ക്രമാനുസാരം)</string>\n    <string name=\"settings_check_update_title\">അപ്ഡേറ്റുകൾക്കായി നോക്കുക</string>\n    <string name=\"settings_check_update_summary\">പശ്ചാത്തലത്തിൽ അപ്ഡേറ്റുകൾക്കായി ഇടയ്ക്കിടെ പരിശോധിക്കുക</string>\n    <string name=\"settings_update_channel_title\">അപ്ഡേറ്റിനുള്ള ചാനൽ</string>\n    <string name=\"settings_update_stable\">സ്റ്റേബിൾ</string>\n    <string name=\"settings_update_beta\">ബീറ്റ</string>\n    <string name=\"settings_update_custom\">കസ്റ്റം</string>\n    <string name=\"settings_update_custom_msg\">കസ്റ്റം ചാനലിന്റെ URL ചേർക്കുക</string>\n    <string name=\"settings_zygisk_summary\">സൈഗോട്ട് ഡെമണിൽ മജിസ്കിന്റെ ഭാഗങ്ങൾ പ്രവർത്തിപ്പിക്കുക</string>\n    <string name=\"settings_denylist_title\">നിഷിദ്ധ പട്ടിക പ്രാബല്യത്തിലാകുക</string>\n    <string name=\"settings_denylist_summary\">നിഷിദ്ധ പട്ടികയിലെ പ്രോസസ്സുകൾക്കായി എല്ലാ മജിസ്‌ക് പരിഷ്‌ക്കരണങ്ങളും പഴയപടിയാക്കും</string>\n    <string name=\"settings_denylist_config_title\">നിഷിദ്ധ പട്ടിക കോൺഫിഗർ ചെയ്യുക</string>\n    <string name=\"settings_denylist_config_summary\">നിഷിദ്ധ പട്ടികയിൽ ഉൾപ്പെടുത്തേണ്ട പ്രോസസ്സുകൾ തിരഞ്ഞെടുക്കുക</string>\n    <string name=\"settings_hosts_title\">സിസ്റ്റംലെസ്സ് ഹോസ്റ്റ്</string>\n    <string name=\"settings_hosts_summary\">പരസ്യം തടയുന്ന ആപ്പുകൾക്കായി സിസ്റ്റംലെസ്സ് ഹോസ്റ്റ്</string>\n    <string name=\"settings_hosts_toast\">സിസ്റ്റംലെസ്സ് ഹോസ്റ്റ് മൊഡ്യൂൾ ചേർത്തു</string>\n    <string name=\"settings_app_name_hint\">പുതിയ പേര്</string>\n    <string name=\"settings_app_name_helper\">ആപ്പ് ഈ പേരിൽ വീണ്ടും പാക്ക് ചെയ്യും</string>\n    <string name=\"settings_app_name_error\">അസാധുവായ ഫോർമാറ്റ്</string>\n    <string name=\"settings_su_app_adb\">ആപ്പുകളും ADB</string>\n    <string name=\"settings_su_app\">ആപ്പുകൾ മാത്രം</string>\n    <string name=\"settings_su_adb\">ADB മാത്രം</string>\n    <string name=\"settings_su_disable\">അപ്രാപ്തമാക്കി</string>\n    <string name=\"settings_su_request_10\">10 സെക്കന്റുകൾ</string>\n    <string name=\"settings_su_request_15\">15 സെക്കന്റുകൾ</string>\n    <string name=\"settings_su_request_20\">20 സെക്കന്റുകൾ</string>\n    <string name=\"settings_su_request_30\">30 സെക്കന്റുകൾ</string>\n    <string name=\"settings_su_request_45\">45 സെക്കന്റുകൾ</string>\n    <string name=\"settings_su_request_60\">60 സെക്കന്റുകൾ</string>\n    <string name=\"superuser_access\">സൂപ്പർയൂസർ ആക്സസ്</string>\n    <string name=\"auto_response\">സ്വയമേവ പ്രതികരണം</string>\n    <string name=\"request_timeout\">അഭ്യർത്ഥന സമയപരിധി</string>\n    <string name=\"superuser_notification\">സൂപ്പർയൂസർ അറിയിപ്പ്</string>\n    <string name=\"settings_su_reauth_title\">അപ്‌ഗ്രേഡിന് ശേഷം വീണ്ടും പ്രാമാണീകരിക്കുക</string>\n    <string name=\"settings_su_reauth_summary\">ആപ്പുകൾ അപ്‌ഗ്രേഡ് ചെയ്‌തതിന് ശേഷം സൂപ്പർയൂസർ അനുമതികൾക്കായി വീണ്ടും ആവശ്യപ്പെടുക</string>\n    <string name=\"settings_su_tapjack_title\">ടാപ്പ്ജാക്കിംഗ് സംരക്ഷണം</string>\n    <string name=\"settings_su_tapjack_summary\">സൂപ്പർയൂസർ പ്രോംപ്റ്റ് ഡയലോഗ് മറ്റേതെങ്കിലും വിൻഡോയോ ഓവർലേയോ മറയ്ക്കുമ്പോൾ ഇൻപുട്ടിനോട് പ്രതികരിക്കില്</string>\n    <string name=\"settings_customization\">കസ്റ്റമയിസേഷൻ</string>\n    <string name=\"setting_add_shortcut_summary\">ആപ്പ് മറച്ചതിന് ശേഷം പേരും ഐക്കണും തിരിച്ചറിയാൻ പ്രയാസമാണെങ്കിൽ ഹോം സ്‌ക്രീനിലേക്ക് മനോഹരമായ ഒരു ഷോർട്ട്ക്കട് ചേർക്കുക</string>\n    <string name=\"settings_doh_title\">DNS ഓവർ HTTPS</string>\n    <string name=\"settings_doh_description\">ചില രാജ്യങ്ങളിലെ DNS പോയ്സണിങിന് പരിഹാരം</string>\n\n    <string name=\"multiuser_mode\">മൾട്ടിയൂസർ മോഡ്</string>\n    <string name=\"settings_owner_only\">ഉപകരണ ഉടമ മാത്രം</string>\n    <string name=\"settings_owner_manage\">ഉപകരണ ഉടമ നിയന്ത്രിക്കുന്നു</string>\n    <string name=\"settings_user_independent\">ഉപയോക്താവിനെ ആശ്രയിക്കുന്നില്</string>\n    <string name=\"owner_only_summary\">ഉടമയ്ക്ക് മാത്രമേ റൂട്ട് ആക്സസ് ഉള്ളൂ</string>\n    <string name=\"owner_manage_summary\">റൂട്ട് ആക്‌സസ് നിയന്ത്രിക്കാനും അഭ്യർത്ഥന നിർദ്ദേശങ്ങൾ സ്വീകരിക്കാനും ഉടമയ്ക്ക് മാത്രമേ കഴിയൂ</string>\n    <string name=\"user_independent_summary\">ഓരോ ഉപയോക്താവിനും അവരുടേതായ പ്രത്യേക റൂട്ട് നിയമങ്ങൾ ഉണ്ടാവാം</string>\n\n    <string name=\"mount_namespace_mode\">മൗണ്ട് നെയിംസ്പേസ് മോഡ്</string>\n    <string name=\"settings_ns_global\">ഗ്ലോബൽ നെയിംസ്പേസ്</string>\n    <string name=\"settings_ns_requester\">പൂര്‍വ്വാര്‍ജ്ജിതമായ് നെയിംസ്പേസ്</string>\n    <string name=\"settings_ns_isolate\">ഒറ്റപ്പെട് നെയിംസ്പേസ്</string>\n    <string name=\"global_summary\">എല്ലാ റൂട്ട് സെഷനുകളും ഗ്ലോബൽ മൗണ്ട് നെയിംസ്പേസ് ഉപയോഗിക്കും</string>\n    <string name=\"requester_summary\">റൂട്ട് സെഷനുകൾ അവരുടെ അഭ്യർത്ഥനയുടെ നെയിംസ്പേസ് ഉപയോഗിക്കും</string>\n    <string name=\"isolate_summary\">ഓരോ റൂട്ട് സെഷനും അതിന്റേതായ ഒറ്റപ്പെട്ട നെയിംസ്പേസ് ഉണ്ടായിരിക്കും</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">മജിസ്‌ക് അപ്‌ഡേറ്റുകൾ</string>\n    <string name=\"progress_channel\">പുരോഗതി അറിയിപ്പുകൾ</string>\n    <string name=\"updated_channel\">അപ്‌ഡേറ്റ് പൂർത്തിയായി</string>\n    <string name=\"download_complete\">ഡൗൺലോഡ് പൂർത്തിയായി</string>\n    <string name=\"download_file_error\">ഫയൽ ഡൗൺലോഡ് ചെയ്യുന്നതിൽ പിശക്</string>\n    <string name=\"magisk_update_title\">മജിസ്‌ക് അപ്‌ഡേറ്റ് ലഭ്യമാണ്!</string>\n    <string name=\"updated_title\">മജിസ്‌ക് അപ്ഡേറ്റ് ചെയ്തു</string>\n    <string name=\"updated_text\">ആപ്പ് തുറക്കാൻ ടാപ്പ് ചെയ്യുക</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">അതെ</string>\n    <string name=\"no\">ഇല്ല</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) ഇൻസ്റ്റാൾ ചെയ്യുക</string>\n    <string name=\"download\">ഡൗൺലോഡ്</string>\n    <string name=\"reboot\">റീബൂട്ട്</string>\n    <string name=\"release_notes\">റിലീസ് നോട്ടുകൾ</string>\n    <string name=\"flashing\">ഫ്ലാഷ് ചെയ്യുന്നു...</string>\n    <string name=\"done\">ചെയ്തു!</string>\n    <string name=\"failure\">പരാജയപ്പെട്ടു!</string>\n    <string name=\"hide_app_title\">മജിസ്‌ക് ആപ്പ് മറയ്ക്കുന്നു...</string>\n    <string name=\"open_link_failed_toast\">ലിങ്ക് തുറക്കാൻ ആപ്പൊന്നും കണ്ടെത്തിയില്ല</string>\n    <string name=\"complete_uninstall\">പൂർണ്ണമായും അൺഇൻസ്റ്റാൾ ചെയ്യുക</string>\n    <string name=\"restore_img\">ഇമേജുകൾ റിസ്റ്റോർ ചെയ്യുക</string>\n    <string name=\"restore_img_msg\">റിസ്റ്റോർ ചെയ്യുന്നു…</string>\n    <string name=\"restore_done\">റിസ്റ്റോർ ചെയ്തു!</string>\n    <string name=\"restore_fail\">സ്റ്റോക്ക് ബാക്കപ്പ് നിലവിലില്ല!</string>\n    <string name=\"setup_fail\">സെറ്റപ്പ് പരാജയപ്പെട്ടു</string>\n    <string name=\"env_fix_title\">കൂടുതൽ സജ്ജീകരണം ആവശ്യമാണ്</string>\n    <string name=\"env_fix_msg\">മജിസ്‌ക് ശരിയായി പ്രവർത്തിക്കാൻ നിങ്ങളുടെ ഉപകരണത്തിന് കൂടുതൽ സജ്ജീകരണം ആവശ്യമാണ്. നിങ്ങൾക്ക് തുടരാനും റീബൂട്ട് ചെയ്യാനും താൽപ്പര്യമുണ്ടോ?</string>\n    <string name=\"setup_msg\">ഇൻവൈറൻമൻറ്റ് സജ്ജീകരണം പ്രവർത്തിക്കുന്നു...</string>\n    <string name=\"unsupport_magisk_title\">പിന്തുണയ്ക്കാത്ത മജിസ്‌ക് പതിപ്പ്</string>\n    <string name=\"unsupport_magisk_msg\">ആപ്പിന്റെ ഈ പതിപ്പ് %1$s-ൽ താഴെയുള്ള മജിസ്‌ക് പതിപ്പുകളെ പിന്തുണയ്‌ക്കുന്നില്ല.\\n\\nമജിസ്‌ക് ഇൻസ്റ്റാൾ ചെയ്തിട്ടില്ലെന്ന മട്ടിൽ ആപ്പ് പ്രവർത്തിക്കും, ദയവായി മജിസ്‌ക് എത്രയും വേഗം അപ്‌ഗ്രേഡ് ചെയ്യുക.</string>\n    <string name=\"unsupport_general_title\">അസാധാരണമായ നില</string>\n    <string name=\"unsupport_system_app_msg\">ഒരു സിസ്റ്റം ആപ്പായി ഈ ആപ്പ് പ്രവർത്തിപ്പിക്കുന്നത് സാധ്യമല്ല. ഒരു യൂസർ ആപ്പിലേക്ക് ആപ്പ് പുനഃസ്ഥാപിക്കുക.</string>\n    <string name=\"unsupport_other_su_msg\">മജിസ്‌ക്-ൽ നിന്ന് അല്ലാത്ത ഒരു \\\"su\\\" ബൈനറി കണ്ടെത്തി. അധിക റൂട്ട് സൊല്യൂഷൻ നീക്കം ചെയ്യുക കൂടാതെ/അല്ലെങ്കിൽ മാജിസ്ക് വീണ്ടും ഇൻസ്റ്റാൾ ചെയ്യുക.</string>\n    <string name=\"unsupport_external_storage_msg\">മജിസ്‌ക് ഇക്സ്റ്റർനൽ സ്റ്റോറേജിൽ ഇൻസ്റ്റാൾ ചെയ്തിരിക്കുകയാണ്. ആപ്പ് ഇൻറ്റർനൽ സ്റ്റോറേജിലെക് നീക്കുക.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">റൂട്ട് നഷ്‌ടമായതിനാൽ മറഞ്ഞിരിക്കുന്ന മജിസ്‌ക് ആപ്പിന് തുടർന്നും പ്രവർത്തിക്കാനാകില്ല. യഥാർത്ഥ APK പുനഃസ്ഥാപിക്കുക.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">ഈ പ്രവർത്തനം പ്രവർത്തനക്ഷമമാക്കാൻ സ്റ്റോറജ് ​​അനുമതി നൽകുക</string>\n    <string name=\"post_notifications_denied\">ഈ പ്രവർത്തനം പ്രവർത്തനക്ഷമമാക്കാൻ അറിയിപ്പ്‌ അനുമതി നൽകുക</string>\n    <string name=\"install_unknown_denied\">ഈ പ്രവർത്തനം പ്രവർത്തനക്ഷമമാക്കാൻ \"install unknown apps\" അനുവദിക്കുക</string>\n    <string name=\"add_shortcut_title\">ഹോം സ്ക്രീനിലേക്ക് ഷോർട്ട്കട്ട് ചേർക്കുക</string>\n    <string name=\"add_shortcut_msg\">ഈ ആപ്പ് മറച്ചതിന് ശേഷം, അതിന്റെ പേരും ഐക്കണും തിരിച്ചറിയാൻ ബുദ്ധിമുട്ടായേക്കാം. ഹോം സ്‌ക്രീനിലേക്ക് മനോഹരമായ ഒരു ഷോർറ്റ്കറ്റ ചേർക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ?</string>\n    <string name=\"app_not_found\">ഈ പ്രവർത്തനം കൈകാര്യം ചെയ്യാൻ ഒരു ആപ്പും കണ്ടെത്തിയില്ല</string>\n    <string name=\"reboot_apply_change\">മാറ്റങ്ങൾ പ്രയോഗിക്കാൻ റീബൂട്ട് ചെയ്യുക</string>\n    <string name=\"restore_app_confirmation\">ഇത് മറച്ച ആപ്പിനെ യഥാർത്ഥ ആപ്പിലേക്ക് തിരികെ കൊണ്ടുവരും. നിങ്ങൾ ശരിക്കും ഇത് ചെയ്യാൻ ആഗ്രഹിക്കുന്നുണ്ടോ?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-nb/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduler</string>\n    <string name=\"superuser\">Superbruker</string>\n    <string name=\"logs\">Logg</string>\n    <string name=\"settings\">Innstillinger</string>\n    <string name=\"install\">Install</string>\n    <string name=\"section_home\">Hjem</string>\n    <string name=\"section_theme\">Drakter</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Mangler tilkobling</string>\n    <string name=\"app_changelog\">Endringslogg</string>\n    <string name=\"loading\">Laster inn…</string>\n    <string name=\"update\">Oppgrader</string>\n    <string name=\"not_available\">I/T</string>\n    <string name=\"hide\">Skjul</string>\n    <string name=\"home_package\">Pakke</string>\n    <string name=\"home_app_title\">Program</string>\n\n    <string name=\"home_notice_content\">Kun last ned Magisk fra den offisielle GitHub siden. Filer fra ukjente kilder kan være skadelige!</string>\n    <string name=\"home_support_title\">Kronerulling</string>\n    <string name=\"home_item_source\">Kildekode</string>\n    <string name=\"home_support_content\">Magisk er og vil alltid forbli fritt.\\nDu kan vise at du setter pris på programmet ved å sende en slant.</string>\n    <string name=\"home_installed_version\">Installert</string>\n    <string name=\"home_latest_version\">Seneste</string>\n    <string name=\"invalid_update_channel\">Ugyldig installasjonskanal</string>\n    <string name=\"uninstall_magisk_title\">Avinstaller Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Alle moduler vil bli avskrudd/fjernet.\\nRot-tigang vil bli fjernet!\\nKrypter din data hvis den ikke er det allerede.</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Bevar påvunget kryptering</string>\n    <string name=\"keep_dm_verity\">Bevar AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Gjenopprettingsmodus</string>\n    <string name=\"install_options_title\">Innstillinger</string>\n    <string name=\"install_method_title\">Metode</string>\n    <string name=\"install_next\">Neste</string>\n    <string name=\"install_start\">Start</string>\n    <string name=\"manager_download_install\">Trykk for å laste ned og installere</string>\n    <string name=\"direct_install\">Direkte installasjon (anbefalt)</string>\n    <string name=\"install_inactive_slot\">Installer til inaktiv plass (etter OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Din enhet vil bli tvunget til å starte opp i inaktiv plass etter omstart!\\nKun bruk dette etter at OTA er ferdig.\\Fortsett?</string>\n    <string name=\"setup_title\">Ytterligere oppsett</string>\n    <string name=\"select_patch_file\">Velg og utfør programfiks av en fil</string>\n    <string name=\"patch_file_msg\">Velg et rå-avtrykk (*.img) eller en ODIN-tjæreballfil (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Starter på ny om 5 sek …</string>\n    <string name=\"flash_screen_title\">Installasjon</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superbrukerforespørsel</string>\n    <string name=\"touch_filtered_warning\">Fordi et program tilslører en superbrukerforespørsel kan ikke Magisk bekrefte</string>\n    <string name=\"deny\">Avslå</string>\n    <string name=\"prompt\">Spør</string>\n    <string name=\"grant\">Innvilg</string>\n    <string name=\"su_warning\">Gir full tilgang til enheten din.\\nAvslå hvis du ikke er sikker!</string>\n    <string name=\"forever\">Permanent</string>\n    <string name=\"once\">Én gang</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s ble innvilget superbruker-rettigheter</string>\n    <string name=\"su_deny_toast\">%1$s ble nektet superbruker-rettigheter</string>\n    <string name=\"su_snack_grant\">Superbruker-rettigheter for %1$s ble innvilget</string>\n    <string name=\"su_snack_deny\">Superbruker-rettigheter for %1$s ble nektet</string>\n    <string name=\"su_snack_notif_on\">Merknader for %1$s er påskrudd</string>\n    <string name=\"su_snack_notif_off\">Merknader for %1$s er avskrudd</string>\n    <string name=\"su_snack_log_on\">Logging av %1$s er påskrudd</string>\n    <string name=\"su_snack_log_off\">Logging av %1$s er avskrudd</string>\n    <string name=\"su_revoke_title\">Trekk tilbake?</string>\n    <string name=\"su_revoke_msg\">Bekreft tilbaketrekking av %1$s-rettigheter?</string>\n    <string name=\"toast\">Oppsprettsmerknad</string>\n    <string name=\"none\">Ingen</string>\n\n    <string name=\"superuser_toggle_notification\">Merknader</string>\n    <string name=\"superuser_toggle_revoke\">Tilbakekall</string>\n    <string name=\"superuser_policy_none\">Ingen programmer har forespurt superbrukertilgang enda.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Ingen loggføring. Prøv å bruke superbrukerprogrammene dine oftere.</string>\n    <string name=\"log_data_magisk_none\">Magisk-loggene er tomme, det er rart</string>\n    <string name=\"menuSaveLog\">Lagre logg</string>\n    <string name=\"menuClearLog\">Tøm logg nå</string>\n    <string name=\"logs_cleared\">Loggføring tømt</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Mål-UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Vis systemprogrammer</string>\n    <string name=\"show_os_app\">Vis systemprogrammer</string>\n    <string name=\"hide_filter_hint\">Filtrer etter navn</string>\n    <string name=\"hide_search\">Søk</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Ingen info angitt)</string>\n    <string name=\"reboot_userspace\">Myk omstart</string>\n    <string name=\"reboot_recovery\">Omstart til gjenoppretting</string>\n    <string name=\"reboot_bootloader\">Omstart til oppstartslaster</string>\n    <string name=\"reboot_download\">Omstart til nedlasting</string>\n    <string name=\"reboot_edl\">Omstart til EDL</string>\n    <string name=\"module_version_author\">%1$s av %2$s</string>\n    <string name=\"module_state_remove\">Fjern</string>\n    <string name=\"module_state_restore\">Gjenopprett</string>\n    <string name=\"module_action_install_external\">Installer fra lagring</string>\n    <string name=\"update_available\">Oppgradering tilgjengelig</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Draktmodus</string>\n    <string name=\"settings_dark_mode_message\">Velg det snittet som kler deg best.</string>\n    <string name=\"settings_dark_mode_light\">Alltid lys</string>\n    <string name=\"settings_dark_mode_system\">Følg systemet</string>\n    <string name=\"settings_dark_mode_dark\">Alltid mørk</string>\n    <string name=\"settings_download_path_title\">Nedlastingssti</string>\n    <string name=\"settings_download_path_message\">Filer vil lagres i %1$s</string>\n    <string name=\"settings_hide_app_title\">Skjul Magisk-programmet</string>\n    <string name=\"settings_hide_app_summary\">Installer et mellomtjenerprogram med tilfeldig pakke-ID og egendefinert program-etikett</string>\n    <string name=\"settings_restore_app_title\">Gjenopprett Magisk-programmet</string>\n    <string name=\"settings_restore_app_summary\">Opphev skjuling av programmet og gjenopprett det tilbake til opprinnelig APK</string>\n    <string name=\"language\">Språk</string>\n    <string name=\"system_default\">(Systemforvalg)</string>\n    <string name=\"settings_check_update_title\">Finn nye versjoner</string>\n    <string name=\"settings_check_update_summary\">Periodisk søken etter nye oppgraderinger i bakgrunnen</string>\n    <string name=\"settings_update_channel_title\">Oppgraderingskanal</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Egendefinert kanal</string>\n    <string name=\"settings_update_custom_msg\">Sett inn en egendefinert nettadresse</string>\n    <string name=\"settings_hosts_title\">Systemløs vertsfil</string>\n    <string name=\"settings_hosts_summary\">Systemløs vertsfilstøtte for reklameblokkeringsprogrammer</string>\n    <string name=\"settings_hosts_toast\">La til modul for systemløs vertsfil</string>\n    <string name=\"settings_app_name_hint\">Nytt navn</string>\n    <string name=\"settings_app_name_helper\">Programmet vil bli ompakket til dette navnet</string>\n    <string name=\"settings_app_name_error\">Ugyldig format</string>\n    <string name=\"settings_su_app_adb\">Programmer og ADB</string>\n    <string name=\"settings_su_app\">Kun programmer</string>\n    <string name=\"settings_su_adb\">Kun ADB</string>\n    <string name=\"settings_su_disable\">Avskrudd</string>\n    <string name=\"settings_su_request_10\">10 sekunder</string>\n    <string name=\"settings_su_request_15\">15 sekunder</string>\n    <string name=\"settings_su_request_20\">20 sekunder</string>\n    <string name=\"settings_su_request_30\">30 sekunder</string>\n    <string name=\"settings_su_request_45\">45 sekunder</string>\n    <string name=\"settings_su_request_60\">60 sekunder</string>\n    <string name=\"superuser_access\">Superbruker-tilgang</string>\n    <string name=\"auto_response\">Automatisk svar</string>\n    <string name=\"request_timeout\">Tidsavbrudd for forespørsel</string>\n    <string name=\"superuser_notification\">Superbruker-merknad</string>\n    <string name=\"settings_su_reauth_title\">Ny identitetsbekreftelse etter oppgradering</string>\n    <string name=\"settings_su_reauth_summary\">Identitetsbekreft superbrukertilganger etter programoppgraderinger</string>\n    <string name=\"settings_su_tapjack_title\">Trykkstjelingsbeskyttelse</string>\n    <string name=\"settings_su_tapjack_summary\">Superbrukings-forespørselsdialogen vil ikke svare på inndata mens den dekkes av et annet vindu eller lag</string>\n    <string name=\"settings_customization\">Tilpasning</string>\n    <string name=\"setting_add_shortcut_summary\">Legg til fin snarvei på hjemmeskjermen i fall navnet og ikonet er vanskelig å gjenkjenne etter skjuling av programmet</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Unngåelse av DNS-forgiftelse i noen land</string>\n\n    <string name=\"multiuser_mode\">Multibrukermodus</string>\n    <string name=\"settings_owner_only\">Kun for enhetseier</string>\n    <string name=\"settings_owner_manage\">Enhetseier, håndtert</string>\n    <string name=\"settings_user_independent\">Brukeruavhengig</string>\n    <string name=\"owner_only_summary\">Rot-tilgang kun for eier</string>\n    <string name=\"owner_manage_summary\">Kun eier kan håndtere rot-tilgang og motta forespørsler</string>\n    <string name=\"user_independent_summary\">Hver bruker har egne rot-regler</string>\n\n    <string name=\"mount_namespace_mode\">Modus for montering av navnerom</string>\n    <string name=\"settings_ns_global\">Omspennede navnerom</string>\n    <string name=\"settings_ns_requester\">Nedarv navnerom</string>\n    <string name=\"settings_ns_isolate\">Isolert navnerom</string>\n    <string name=\"global_summary\">Alle rot-økter bruker det omspennende monteringsnavnerommet</string>\n    <string name=\"requester_summary\">Rot-økter nedarver forespørrers navnerom</string>\n    <string name=\"isolate_summary\">Hver rot-økt får eget isolert navnerom</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk-oppgraderinger</string>\n    <string name=\"progress_channel\">Framdriftsmerknader</string>\n    <string name=\"download_complete\">Nedlasting fullført</string>\n    <string name=\"download_file_error\">Kunne ikke laste ned fil</string>\n    <string name=\"magisk_update_title\">Ny versjon av Magisk tilgjengelig.</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nei</string>\n    <string name=\"repo_install_title\">Installer %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Last ned</string>\n    <string name=\"reboot\">Omstart</string>\n    <string name=\"release_notes\">Utgivelsesnotater</string>\n    <string name=\"flashing\">Innbrenner …</string>\n    <string name=\"done\">Ferdig!</string>\n    <string name=\"failure\">Mislykket!</string>\n    <string name=\"hide_app_title\">Skjuler Magisk-programmet …</string>\n    <string name=\"open_link_failed_toast\">Ingen programmer kunne åpne lenken</string>\n    <string name=\"complete_uninstall\">Fullfør avinstallasjon</string>\n    <string name=\"restore_img\">Gjenopprett avtrykk</string>\n    <string name=\"restore_img_msg\">Gjenoppretter …</string>\n    <string name=\"restore_done\">Gjenoppretting fullført.</string>\n    <string name=\"restore_fail\">Fabrikk-sikkerhetskopi finnes ikke!</string>\n    <string name=\"setup_fail\">Oppsett mislyktes</string>\n    <string name=\"env_fix_title\">Krever ytterligere oppsett</string>\n    <string name=\"env_fix_msg\">Enheten din krever ytterligere oppsett for at Magisk skal fungere rett. Fortsett med omstart?</string>\n    <string name=\"setup_msg\">Kjører oppsett av miljø…</string>\n    <string name=\"unsupport_magisk_title\">Ustøttet Magisk-versjon</string>\n    <string name=\"unsupport_magisk_msg\">Denne versjonen av programmet støtter ikke Magisk før versjon %1$s.\\n\\nProgrammet vil oppføre seg som om Magisk ikke er installert. Oppgrader Magisk så snart som mulig.</string>\n    <string name=\"unsupport_general_title\">Unormal tilstand</string>\n    <string name=\"unsupport_system_app_msg\">Kjøring av dette programmet som systemprogram støttes ikke. Gjør om programmet til et brukerprogram igjen.</string>\n    <string name=\"unsupport_other_su_msg\">En \\\"su\\\"-kommando som ikke tilhører Magisk ble oppdaget. Fjern dette andre ustøttede opphavet.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk er installert på eksternlagring. Flytt det til internlagring.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Programmet kan ikke fortsette å fungere i skjult tilstand siden rot-tilgang gikk tapt. Gjenopprett det tilbake til opprinnelig APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Innvilg lagringstilgang for å skru på denne funksjonaliteten</string>\n    <string name=\"add_shortcut_title\">Legg til snarvei på hjemmeskjermen</string>\n    <string name=\"add_shortcut_msg\">Etter å ha skjult programmet kan det vli vanskelig å gjenkjenne navn og ikon. Legg til fin snarvei på hjemmeskjermen?</string>\n    <string name=\"app_not_found\">Ingen programmer kan håndtere denne forespørselen</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"splash_background\">#0D0D0D</color>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-nl/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modules</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Logboek</string>\n    <string name=\"settings\">Instellingen</string>\n    <string name=\"install\">Installeren</string>\n    <string name=\"section_home\">Overzicht</string>\n    <string name=\"section_theme\">Thema\\'s</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Geen internetverbinding</string>\n    <string name=\"app_changelog\">Wijzigingslog</string>\n    <string name=\"loading\">Bezig met laden…</string>\n    <string name=\"update\">Updaten</string>\n    <string name=\"not_available\">Niet beschikbaar</string>\n    <string name=\"hide\">Verbergen</string>\n    <string name=\"home_package\">Pakket</string>\n\n    <string name=\"home_support_title\">Ondersteun ons</string>\n    <string name=\"home_item_source\">Broncode</string>\n    <string name=\"home_support_content\">Magisk is en zal altijd gratis en open source blijven, maar als je wilt, kun je een kleine donatie doen.</string>\n    <string name=\"home_installed_version\">Geïnstalleerd</string>\n    <string name=\"home_latest_version\">Actueel</string>\n    <string name=\"invalid_update_channel\">Ongeldig updatekanaal</string>\n    <string name=\"uninstall_magisk_title\">Magisk deïnstalleren</string>\n    <string name=\"uninstall_magisk_msg\">Alle modules worden uitgeschakeld/verwijderd!\\nRoot wordt verwijderd!\\nJe gegevens worden, als ze dat nog niet waren, mogelijk versleuteld!</string>\n\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Afgedwongen versleuteling behouden</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity behouden</string>\n    <string name=\"recovery_mode\">Herstelmodus</string>\n    <string name=\"install_options_title\">Opties</string>\n    <string name=\"install_method_title\">Methode</string>\n    <string name=\"install_next\">Volgende</string>\n    <string name=\"install_start\">Aan de slag</string>\n    <string name=\"manager_download_install\">Druk om te downloaden en installeren</string>\n    <string name=\"direct_install\">Direct installeren (aanbevolen)</string>\n    <string name=\"install_inactive_slot\">Installeren in inactieve sleuf (na OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Je apparaat wordt GEDWONGEN opgestart in de huidige inactieve sleuf na het herstarten!\\nGebruik deze optie alleen NA het installeren van een OTA-update.\\nWil je doorgaan?</string>\n    <string name=\"setup_title\">Installatie afronden</string>\n    <string name=\"select_patch_file\">Kies en patch een bestand</string>\n    <string name=\"patch_file_msg\">Kies een schijfkopie (*.img) of ODIN-tarbestand (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Je apparaat wordt over 5 seconden herstart…</string>\n    <string name=\"flash_screen_title\">Installatie</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuserverzoek</string>\n    <string name=\"deny\">Weigeren</string>\n    <string name=\"prompt\">Vraag</string>\n    <string name=\"grant\">Toestaan</string>\n    <string name=\"su_warning\">Verleent volledige toegang tot je apparaat.\\nBij twijfel, altijd weigeren!</string>\n    <string name=\"forever\">Permanent</string>\n    <string name=\"once\">Eenmalig</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s beschikt nu over superuserrechten</string>\n    <string name=\"su_deny_toast\">Superuserrecht van %1$s geweigerd</string>\n    <string name=\"su_snack_grant\">%1$s beschikt nu over superuserrechten</string>\n    <string name=\"su_snack_deny\">Superuserrecht van %1$s geweigerd</string>\n    <string name=\"su_snack_notif_on\">%1$s-meldingen ingeschakeld</string>\n    <string name=\"su_snack_notif_off\">%1$s-meldingen uitgeschakeld</string>\n    <string name=\"su_snack_log_on\">%1$s wordt nu gelogd</string>\n    <string name=\"su_snack_log_off\">%1$s wordt niet gelogd</string>\n    <string name=\"su_revoke_title\">Intrekken?</string>\n    <string name=\"su_revoke_msg\">Wil je de rechten van %1$s intrekken?</string>\n    <string name=\"toast\">Ballon</string>\n    <string name=\"none\">Geen</string>\n\n    <string name=\"superuser_toggle_notification\">Meldingen</string>\n    <string name=\"superuser_toggle_revoke\">Intrekken</string>\n    <string name=\"superuser_policy_none\">Er zijn nog geen superuserverzoeken geweest.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Er is nog niks gelogd. Gebruik meer apps met superuserrechten!</string>\n    <string name=\"log_data_magisk_none\">Het Magisk-logboek is leeg. Dit is ongebruikelijk.</string>\n    <string name=\"menuSaveLog\">Logboek opslaan</string>\n    <string name=\"menuClearLog\">Logboek wissen</string>\n    <string name=\"logs_cleared\">Het logboek is gewist.</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Doel-uid: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">Systeemapps tonen</string>\n    <string name=\"hide_filter_hint\">Filteren op naam</string>\n    <string name=\"hide_search\">Zoeken</string>\n\n    <!--Module Fragment-->\n    <string name=\"no_info_provided\">(geen informatie verstrekt)</string>\n    <string name=\"reboot_recovery\">Recovery starten</string>\n    <string name=\"reboot_bootloader\">Bootloader starten</string>\n    <string name=\"reboot_download\">Downloadmodus starten</string>\n    <string name=\"reboot_edl\">EDL starten</string>\n    <string name=\"module_version_author\">%1$s van %2$s</string>\n    <string name=\"module_state_remove\">Verwijderen</string>\n    <string name=\"module_state_restore\">Herstellen</string>\n    <string name=\"module_action_install_external\">Gedownloade module installeren</string>\n    <string name=\"update_available\">Update beschikbaar</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">Thema</string>\n    <string name=\"settings_dark_mode_message\">Kies het thema dat het best bij je past!</string>\n    <string name=\"settings_dark_mode_light\">Licht</string>\n    <string name=\"settings_dark_mode_system\">Systeemstandaard</string>\n    <string name=\"settings_dark_mode_dark\">Donker</string>\n    <string name=\"settings_download_path_title\">Downloadpad</string>\n    <string name=\"settings_download_path_message\">Bestanden worden opgeslagen in %1$s</string>\n    <string name=\"language\">Taal</string>\n    <string name=\"system_default\">(systeemstandaard)</string>\n    <string name=\"settings_check_update_summary\">Controleer automatisch op updates op de achtergrond.</string>\n    <string name=\"settings_update_channel_title\">Updatekanaal</string>\n    <string name=\"settings_update_stable\">Stabiel</string>\n    <string name=\"settings_update_beta\">Bèta</string>\n    <string name=\"settings_update_custom\">Aangepast</string>\n    <string name=\"settings_update_custom_msg\">Voer een aangepaste url in</string>\n    <string name=\"settings_hosts_title\">Systeemloze hosts</string>\n    <string name=\"settings_hosts_summary\">Systeemloze hosts-ondersteuning voor advertentieblokkeringsapps.</string>\n    <string name=\"settings_hosts_toast\">De systeemloze hosts-module is toegevoegd</string>\n    <string name=\"settings_app_name_hint\">Nieuwe naam</string>\n    <string name=\"settings_app_name_helper\">De app wordt opnieuw ingepakt onder deze naam</string>\n    <string name=\"settings_app_name_error\">Onjuiste opmaak</string>\n    <string name=\"settings_su_app_adb\">Apps en ADB</string>\n    <string name=\"settings_su_app\">Alleen apps</string>\n    <string name=\"settings_su_adb\">Alleen ADB</string>\n    <string name=\"settings_su_disable\">Uitgeschakeld</string>\n    <string name=\"settings_su_request_10\">10 seconden</string>\n    <string name=\"settings_su_request_15\">15 seconden</string>\n    <string name=\"settings_su_request_20\">20 seconden</string>\n    <string name=\"settings_su_request_30\">30 seconden</string>\n    <string name=\"settings_su_request_45\">45 seconden</string>\n    <string name=\"settings_su_request_60\">60 seconden</string>\n    <string name=\"superuser_access\">Superusertoegang</string>\n    <string name=\"auto_response\">Automatisch antwoord</string>\n    <string name=\"request_timeout\">Verzoektime-out</string>\n    <string name=\"superuser_notification\">Superusermelding</string>\n    <string name=\"settings_su_reauth_title\">Opnieuw goedkeuren na update</string>\n    <string name=\"settings_su_reauth_summary\">Superuserrechten opnieuw goedkeuren na app-updates.</string>\n    <string name=\"settings_customization\">Aanpassingen</string>\n\n    <string name=\"multiuser_mode\">Meerdere gebruikers</string>\n    <string name=\"settings_owner_only\">Alleen apparaateigenaar</string>\n    <string name=\"settings_owner_manage\">Beheerd door apparaateigenaar</string>\n    <string name=\"settings_user_independent\">Gebruikeronafhankelijk</string>\n    <string name=\"owner_only_summary\">Alleen de apparaateigenaar heeft roottoegang</string>\n    <string name=\"owner_manage_summary\">Alleen de eigenaar kan roottoegang goedkeuren en verzoeken ontvangen</string>\n    <string name=\"user_independent_summary\">Elke gebruiker heeft zijn/haar eigen rootregels</string>\n\n    <string name=\"mount_namespace_mode\">Naamruimtemodus aankoppelen</string>\n    <string name=\"settings_ns_global\">Globale naamruimte</string>\n    <string name=\"settings_ns_requester\">Naamruimte overnemen</string>\n    <string name=\"settings_ns_isolate\">Geïsoleerde naamruimte</string>\n    <string name=\"global_summary\">Alle rootsessies gebruiken de globale naamruimte</string>\n    <string name=\"requester_summary\">Rootsessies gebruiken de naamruimte van de verzoeker</string>\n    <string name=\"isolate_summary\">Elke rootsessie heeft zijn eigen geïsoleerde naamruimte</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk-updates</string>\n    <string name=\"progress_channel\">Voortgangsmeldingen</string>\n    <string name=\"download_complete\">Downloaden voltooid</string>\n    <string name=\"download_file_error\">Het bestand kan niet worden gedownload</string>\n    <string name=\"magisk_update_title\">Er is een Magisk-update beschikbaar!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nee</string>\n    <string name=\"repo_install_title\">%1$s installeren %2$s(%3$d)</string>\n    <string name=\"download\">Downloaden</string>\n    <string name=\"reboot\">Herstarten</string>\n    <string name=\"release_notes\">Wijzigingslog</string>\n    <string name=\"flashing\">Bezig met flashen…</string>\n    <string name=\"done\">Voltooid!</string>\n    <string name=\"failure\">Mislukt</string>\n    <string name=\"open_link_failed_toast\">Er is geen app op je apparaat die deze link kan openen</string>\n    <string name=\"complete_uninstall\">Volledig deïnstalleren</string>\n    <string name=\"restore_img\">Schijfkopieën herstellen</string>\n    <string name=\"restore_img_msg\">Bezig met herstellen…</string>\n    <string name=\"restore_done\">Herstellen voltooid!</string>\n    <string name=\"restore_fail\">Er is geen back-up beschikbaar.</string>\n    <string name=\"setup_fail\">Installatie mislukt</string>\n    <string name=\"env_fix_title\">Installatie nog niet afgerond</string>\n    <string name=\"setup_msg\">Bezig met toepassen van omgevingsinstellingen…</string>\n    <string name=\"unsupport_magisk_title\">Niet-ondersteunde Magisk-versie</string>\n    <string name=\"external_rw_permission_denied\">Verleen het opslagrecht om deze functie gebruiken</string>\n    <string name=\"add_shortcut_title\">Snelkoppeling maken op startscherm</string>\n    <string name=\"app_not_found\">Er is geen app op je apparaat die deze handeling kan uitvoeren</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-pa/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">ਮੋਡੀਊਲ</string>\n    <string name=\"superuser\">ਸੁਪਰਯੂਜ਼ਰ</string>\n    <string name=\"logs\">ਲੋਗਜ਼</string>\n    <string name=\"settings\">ਸੈਟਿੰਗਜ਼</string>\n    <string name=\"install\">ਇੰਸਟਾਲ</string>\n    <string name=\"section_home\">ਹੋਮ</string>\n    <string name=\"section_theme\">ਥੀਮ</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">ਕੋਈ ਸੰਪਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ</string>\n    <string name=\"app_changelog\">ਬਦਲਾਓ</string>\n    <string name=\"loading\">ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ</string>\n    <string name=\"update\">ਅੱਪਡੇਟ</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">ਓਹਲੇ</string>\n    <string name=\"home_package\">ਪੈਕੇਜ</string>\n\n    <string name=\"home_support_title\">ਸਾਡਾ ਸਮਰਥਨ ਕਰੋ</string>\n    <string name=\"home_item_source\">ਸਰੋਤ</string>\n    <string name=\"home_support_content\">ਮੈਜਿਸਕ ਮੁਫਤ ਅਤੇ ਖੁੱਲਾ ਸਰੋਤ ਹੈ, ਅਤੇ ਹਮੇਸ਼ਾਂ ਰਹੇਗਾ। ਹਾਲਾਂਕਿ, ਤੁਸੀਂ ਦਿਖਾ ਸਕਦੇ ਹੋ ਕਿ ਤੁਸੀਂ ਇੱਕ ਛੋਟਾ ਜਿਹਾ ਦਾਨ ਭੇਜ ਕੇ ਦੇਖਭਾਲ ਕਰਦੇ ਹੋ।</string>\n    <string name=\"home_installed_version\">ਇੰਸਟਾਲਡ</string>\n    <string name=\"home_latest_version\">ਨਵੀਨਤਮ</string>\n    <string name=\"invalid_update_channel\">ਅਵੈਧ ਅਪਡੇਟ ਚੈਨਲ</string>\n    <string name=\"uninstall_magisk_title\">ਮੈਜਿਸਕ ਨੂੰ ਅਣਇੰਸਟੌਲ ਕਰੋ</string>\n    <string name=\"uninstall_magisk_msg\">ਸਾਰੇ ਮੋਡੀਊਲ ਅਯੋਗ/ਮਿਟਾ ਦਿੱਤੇ ਜਾਣਗੇ!\\n ਰੂਟ ਨੂੰ ਹਟਾਇਆ ਜਾਏਗਾ!\\n ਤੁਹਾਡਾ ਡਾਟਾ ਸੰਭਾਵਤ ਤੌਰ ਤੇ ਐਨਕ੍ਰਿਪਟ ਕੀਤਾ ਗਿਆ ਹੈ ਜੇ ਪਹਿਲਾਂ ਹੀ ਨਹੀਂ!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">ਫੋਰਸ ਇਨਕ੍ਰਿਪਸ਼ਨ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖੋ</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖੋ</string>\n    <string name=\"recovery_mode\">ਰਿਕਵਰੀ ਮੋਡ</string>\n    <string name=\"install_options_title\">ਚੋਣ</string>\n    <string name=\"install_method_title\">ਤਰੀਕਾ</string>\n    <string name=\"install_next\">ਅੱਗੇ</string>\n    <string name=\"install_start\">ਤਾਂ ਆਓ ਸ਼ੁਰੂ ਕਰੀਏ!</string>\n    <string name=\"manager_download_install\">ਡਾਊਨਲੋਡ ਅਤੇ ਇੰਸਟੌਲ ਕਰਨ ਲਈ ਦਬਾਓ</string>\n    <string name=\"direct_install\">ਸਿੱਦਾ ਇੰਸਟਾਲ (ਸਿਫਾਰਸ਼ੀ)</string>\n    <string name=\"install_inactive_slot\">ਨਾ-ਸਰਗਰਮ ਸਲਾਟ ਵਿੱਚ ਇੰਸਟਾਲ ਕਰੋ (OTA ਤੋਂ ਬਾਅਦ)</string>\n    <string name=\"install_inactive_slot_msg\">ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਨੂੰ ਮੁੜ ਚਾਲੂ ਹੋਣ ਤੋਂ ਬਾਅਦ ਮੌਜੂਦਾ ਨਿਸ਼ਕਿਰਿਆ ਨੰਬਰ ਤੇ ਬੂਟ ਕਰਨ ਲਈ ਮਜਬੂਰ ਕੀਤਾ ਜਾਏਗਾ!\\n OTA ਪੂਰਾ ਹੋਣ ਤੋਂ ਬਾਅਦ ਹੀ ਇਸ ਵਿਕਲਪ ਦੀ ਵਰਤੋਂ ਕਰੋ।\\n ਜਾਰੀ ਰੱਖਣਾ ਹੈ?</string>\n    <string name=\"setup_title\">ਅਤਿਰਿਕਤ ਸੈਟਅਪ</string>\n    <string name=\"select_patch_file\">ਇੱਕ ਫਾਈਲ ਚੁਣੋ ਅਤੇ ਪੈਚ ਕਰੋ</string>\n    <string name=\"patch_file_msg\">ਇੱਕ ਰੋ ਛੱਞੀ ਨੂੰ (* .img) ਜਾਂ ਇੱਕ Odin tarfile (* .tar) ਦੀ ਚੋਣ ਕਰੋ</string>\n    <string name=\"reboot_delay_toast\">5 ਸਕਿੰਟਾਂ ਵਿੱਚ ਰੀਬੂਟ ਹੋ ਰਿਹਾ ਹੈ …</string>\n    <string name=\"flash_screen_title\">ਇੰਸਟਾਲੇਸ਼ਨ</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">ਸੁਪਰਯੂਜ਼ਰ ਮੰਗ</string>\n    <string name=\"deny\">ਇਨਕਾਰ ਕਰੋ</string>\n    <string name=\"prompt\">ਸੰਕੇਤ ਦਿਖਾਓ</string>\n    <string name=\"grant\">ਆਗਿਆ ਦਿਓ</string>\n    <string name=\"su_warning\">ਇਹ ਤੁਹਾਡੀ ਡਿਵਾਈਸ ਤੇ ਪੂਰੀ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦੇਵੇਗਾ,\\n ਜੇ ਤੁਹਾਨੂੰ ਯਕੀਨ ਨਹੀਂ ਹੈ ਤਾਂ ਇਨਕਾਰ ਕਰੋ!</string>\n    <string name=\"forever\">ਹਮੇਸ਼ਾ</string>\n    <string name=\"once\">ਇਕ ਵਾਰ</string>\n    <string name=\"tenmin\">10 ਮਿੰਟ</string>\n    <string name=\"twentymin\">20 ਮਿੰਟ</string>\n    <string name=\"thirtymin\">30 ਮਿੰਟ</string>\n    <string name=\"sixtymin\">60 ਮਿੰਟ</string>\n    <string name=\"su_allow_toast\"> %1$s ਨੂੰ ਸੁਪਰਯੂਜ਼ਰ ਅਧਿਕਾਰ ਦਿੱਤੇ ਗਏ ਸਨ</string>\n    <string name=\"su_deny_toast\"> %1$s ਸੁਪਰਯੂਜ਼ਰ ਅਧਿਕਾਰਾਂ ਤੋਂ ਇਨਕਾਰ ਕੀਤਾ ਗਿਆ ਸੀ</string>\n    <string name=\"su_snack_grant\"> %1$s ਨੂੰ ਸੁਪਰਯੂਜ਼ਰ ਅਧਿਕਾਰ ਦਿੱਤੇ ਗਏ ਸਨ</string>\n    <string name=\"su_snack_deny\"> %1$s ਸੁਪਰ ਯੂਜ਼ਰ ਅਧਿਕਾਰਾਂ ਤੋਂ ਇਨਕਾਰ ਕੀਤਾ ਗਿਆ ਹੈ</string>\n    <string name=\"su_snack_notif_on\"> %1$s ਦੀਆਂ ਸੂਚਨਾਵਾਂ ਯੋਗ ਹਨ</string>\n    <string name=\"su_snack_notif_off\"> %1$s ਦੀਆਂ ਸੂਚਨਾਵਾਂ ਅਯੋਗ ਹਨ</string>\n    <string name=\"su_snack_log_on\"> %1$s ਦੀਆਂ ਲੌਗਿੰਗ ਯੋਗ ਹਨ</string>\n    <string name=\"su_snack_log_off\"> %1$s ਦੀਆਂ ਲੌਗਿੰਗ ਅਯੋਗ ਹਨ</string>\n    <string name=\"su_revoke_title\">ਅਧਿਕਾਰ ਵਾਪਸ ਲਓ?</string>\n    <string name=\"su_revoke_msg\"> %1$s ਦੇ ਅਧਿਕਾਰਾਂ ਨੂੰ ਰੱਦ ਕਰਨ ਦੀ ਪੁਸ਼ਟੀ ਕਰਦੇ ਹੋ?</string>\n    <string name=\"toast\">ਪੌਪ-ਅੱਪ ਸੂਚਨਾ</string>\n    <string name=\"none\">ਕੋਈ ਨਹੀਂ</string>\n\n    <string name=\"superuser_toggle_notification\">ਸੂਚਨਾ</string>\n    <string name=\"superuser_toggle_revoke\">ਅਧਿਕਾਰ ਵਾਪਸ ਲਓ</string>\n    <string name=\"superuser_policy_none\">ਅਜੇ ਤੱਕ ਕਿਸੇ ਵੀ ਐਪ ਨੇ ਸੁਪਰਯੂਜ਼ਰ ਲਈ ਆਗਿਆ ਨਹੀਂ ਮੰਗੀ ਹੈ।</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">ਕੋਈ ਲੌਗਸ ਨਹੀਂ, ਆਪਣੀ ਸੁਪਰਯੂਜ਼ਰ ਸਮਰਥਿਤ ਐਪਲੀਕੇਸ਼ਨ ਨੂੰ ਹੋਰ ਵਰਤਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ।</string>\n    <string name=\"log_data_magisk_none\">ਮੈਜਿਸਕ ਲੌਗਸ ਖਾਲੀ ਹਨ, ਇਹ ਅਜੀਬ ਹੈ।</string>\n    <string name=\"menuSaveLog\">ਲੌਗ ਸੇਵ ਕਰੋ</string>\n    <string name=\"menuClearLog\">ਲੌਗ ਸਾਫ ਕਰੋ</string>\n    <string name=\"logs_cleared\">ਲੌਗ ਸਫਲਤਾਪੂਰਕ ਸਾਫ ਹੋ ਗਏ ਹਨ।</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">ਟੀਚਾ UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">ਸਿਸਟਮ ਐਪਸ ਦਿਖਾਓ</string>\n    <string name=\"show_os_app\">OS ਐਪਲੀਕੇਸ਼ਨ ਦਿਖਾਓ</string>\n    <string name=\"hide_filter_hint\">ਨਾਮ ਦੁਆਰਾ ਫਿਲਟਰ ਕਰੋ</string>\n    <string name=\"hide_search\">ਖੋਜੋ</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(ਕੋਈ ਜਾਣਕਾਰੀ ਨਹੀਂ ਦਿੱਤੀ ਗਈ)</string>\n    <string name=\"reboot_userspace\">ਸਾਫਟ ਰੀਬੂਟ</string>\n    <string name=\"reboot_recovery\">ਰਿਕਵਰੀ ਰੀਬੂਟ</string>\n    <string name=\"reboot_bootloader\">ਬੂਟਲੋਡਰ ਰੀਬੂਟ</string>\n    <string name=\"reboot_download\">ਡਾਊਨਲੋਡ ਮੋਡ ਰੀਬੂਟ</string>\n    <string name=\"reboot_edl\">EDL ਰੀਬੂਟ</string>\n    <string name=\"module_version_author\"> %2$s ਦੇ ਦੁਆਰਾ %1$s</string>\n    <string name=\"module_state_remove\">ਹਟਾਓ</string>\n    <string name=\"module_state_restore\">ਰੀਸਟੋਰ ਕਰੋ</string>\n    <string name=\"module_action_install_external\">ਸਟੋਰੇਜ ਤੋਂ ਇੰਸਟਾਲ ਕਰੋ</string>\n    <string name=\"update_available\">ਅਪਡੇਟ ਉਪਲੱਬਧ ਹੈ</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">ਥੀਮ ਮੋਡ</string>\n    <string name=\"settings_dark_mode_message\">ਕੋਈ ਥੀਮ ਚੁਣੋ ਜੋ ਤੁਹਾਡੀ ਸ਼ੈਲੀ ਦੇ ਅਨੁਕੂਲ ਹੋਵੇ!</string>\n    <string name=\"settings_dark_mode_light\">ਹਮੇਸ਼ਾਂ ਚਿੱਟੇ ਰੰਗ ਦੀ</string>\n    <string name=\"settings_dark_mode_system\">ਸਿਸਟਮ ਦੇ ਅਨੁਸਾਰ</string>\n    <string name=\"settings_dark_mode_dark\">ਹਮੇਸ਼ਾ ਗਹਿਰੇ ਰੰਗ ਦੀ</string>\n    <string name=\"settings_download_path_title\">ਡਾਊਨਲੋਡ ਮਾਰਗ</string>\n    <string name=\"settings_download_path_message\">ਫਾਈਲਾਂ ਨੂੰ %1$s ਵਿੱਚ ਸੇਵ ਕੀਤਾ ਜਾਏਗਾ</string>\n    <string name=\"language\">ਭਾਸ਼ਾ</string>\n    <string name=\"system_default\">(ਸਿਸਟਮ ਡਿਫੌਲਟ)</string>\n    <string name=\"settings_check_update_title\">ਚੈੱਕ ਅੱਪਡੇਟ</string>\n    <string name=\"settings_check_update_summary\">ਸਮੇਂ-ਸਮੇਂ \\'ਤੇ ਪਿਛੋਕੜ ਦੇ ਅਪਡੇਟਾਂ ਦੀ ਜਾਂਚ ਕਰੋ</string>\n    <string name=\"settings_update_channel_title\">ਅਪਡੇਟ ਚੈਨਲ</string>\n    <string name=\"settings_update_stable\">ਸਥਿਰ</string>\n    <string name=\"settings_update_beta\">ਬੀਟਾ</string>\n    <string name=\"settings_update_custom\">ਕਸਟਮ ਚੈਨਲ</string>\n    <string name=\"settings_update_custom_msg\">ਇੱਕ ਕਸਟਮ URL ਦਾਖਲ ਕਰੋ</string>\n    <string name=\"settings_hosts_title\">Systemless ਹੋਸਟ</string>\n    <string name=\"settings_hosts_summary\">Adblock ਐਪਸ ਲਈ Systemless ਹੋਸਟ ਦੀ ਸਹਿਯੋਗ</string>\n    <string name=\"settings_hosts_toast\">Systemless ਹੋਸਟ ਮੋਡੀਊਲ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ</string>\n    <string name=\"settings_app_name_hint\">ਨਵਾਂ ਨਾਮ</string>\n    <string name=\"settings_app_name_helper\">ਇਸ ਨਾਮ ਨਾਲ ਐਪ ਦੁਬਾਰਾ ਇੰਸਟਾਲ ਕੀਤਾ ਜਾਵੇਗਾ</string>\n    <string name=\"settings_app_name_error\">ਗਲਤ ਫਾਰਮੈਟ</string>\n    <string name=\"settings_su_app_adb\">ਐਪਸ ਅਤੇ ਐਡਬੀ</string>\n    <string name=\"settings_su_app\">ਸਿਰਫ ਐਪਸ</string>\n    <string name=\"settings_su_adb\">ਸਿਰਫ ਐਡਬੀ</string>\n    <string name=\"settings_su_disable\">ਬੰਦ ਹੈ</string>\n    <string name=\"settings_su_request_10\">10 ਸਕਿੰਟ</string>\n    <string name=\"settings_su_request_15\">15 ਸਕਿੰਟ</string>\n    <string name=\"settings_su_request_20\">20 ਸਕਿੰਟ</string>\n    <string name=\"settings_su_request_30\">30 ਸਕਿੰਟ</string>\n    <string name=\"settings_su_request_45\">45 ਸਕਿੰਟ</string>\n    <string name=\"settings_su_request_60\">60 ਸਕਿੰਟ</string>\n    <string name=\"superuser_access\">ਸੁਪਰਯੂਜ਼ਰ ਐਕਸੈਸ</string>\n    <string name=\"auto_response\">ਆਟੋਮੈਟਿਕ ਜਵਾਬ</string>\n    <string name=\"request_timeout\">ਬੇਨਤੀ ਦਾ ਸਮਾਂ ਸਮਾਪਤ</string>\n    <string name=\"superuser_notification\">ਸੁਪਰਯੂਜ਼ਰ ਸੂਚਨਾ</string>\n    <string name=\"settings_su_reauth_title\">ਅਪਗ੍ਰੇਡ ਹੋਣ ਤੋਂ ਬਾਅਦ ਪ੍ਰਮਾਣਿਕਤਾ</string>\n    <string name=\"settings_su_reauth_summary\">ਇੱਕ ਐਪ ਦੇ ਅਪਡੇਟ ਹੋਣ ਤੋਂ ਬਾਅਦ ਸੁਪਰਯੂਜ਼ਰ ਆਗਿਆ ਪ੍ਰਮਾਣਿਤ ਕਰੋ</string>\n    <string name=\"settings_customization\">ਕਸਟਮਾਈਜੇਸ਼ਨ</string>\n    <string name=\"setting_add_shortcut_summary\">ਐਪ ਨੂੰ ਲੁਕਾਉਣ ਤੋਂ ਬਾਅਦ ਨਾਮ ਅਤੇ ਆਈਕਾਨ ਨੂੰ ਪਛਾਣਨਾ ਮੁਸ਼ਕਲ ਹੈ, ਤਾਂ ਹੋਮ ਸਕ੍ਰੀਨ ਵਿਚ ਇਕ ਸੁੰਦਰ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰੋ</string>\n    <string name=\"settings_doh_title\">DNS ਉੱਤੇ HTTPS</string>\n    <string name=\"settings_doh_description\">ਕੁਝ ਦੇਸ਼ਾਂ ਵਿੱਚ ਚੱਲ ਰਹੇ DNS ਵਿਸ਼ਾਕਤਤਾ ਦਾ ਹੱਲ</string>\n\n    <string name=\"multiuser_mode\">ਮਲਟੀ ਯੂਜ਼ਰ ਮੋਡ</string>\n    <string name=\"settings_owner_only\">ਸਿਰਫ ਡਿਵਾਈਸ ਮਾਲਕ</string>\n    <string name=\"settings_owner_manage\">ਡਿਵਾਈਸ ਮਾਲਕ ਦੁਆਰਾ ਪ੍ਰਬੰਧਿਤ</string>\n    <string name=\"settings_user_independent\">ਸੁਤੰਤਰ ਉਪਭੋਗਤਾ</string>\n    <string name=\"owner_only_summary\">ਸਿਰਫ ਮਾਲਕ ਕੋਲ ਰੂਟ ਐਕਸੈਸ ਹੈ</string>\n    <string name=\"owner_manage_summary\">ਸਿਰਫ ਮਾਲਕ ਰੂਟ ਐਕਸੈਸ ਦਾ ਪ੍ਰਬੰਧ ਕਰ ਸਕਦੇ ਹਨ ਅਤੇ ਬੇਨਤੀ ਸਿਗਨਲ ਪ੍ਰਾਪਤ ਕਰ ਸਕਦੇ ਹਨ</string>\n    <string name=\"user_independent_summary\">ਹਰੇਕ ਉਪਭੋਗਤਾ ਦਾ ਆਪਣਾ ਵੱਖਰਾ ਰੂਟ ਨਿਯਮ ਹੁੰਦਾ ਹੈ</string>\n\n    <string name=\"mount_namespace_mode\">ਮਾਉਂਟ ਨੇਮਸਪੇਸ ਮੋਡ</string>\n    <string name=\"settings_ns_global\">ਗਲੋਬਲ ਨੇਮਸਪੇਸ</string>\n    <string name=\"settings_ns_requester\">ਇਨਹੇਰਟ ਨੇਮਸਪੇਸ</string>\n    <string name=\"settings_ns_isolate\">ਆਈਸੋਲੇਟਿਡ ਨੇਮਸਪੇਸ</string>\n    <string name=\"global_summary\">ਸਾਰੇ ਰੂਟ ਸੈਸ਼ਨ ਗਲੋਬਲ ਨੇਮਸਪੇਸ ਦੀ ਵਰਤੋਂ ਕਰਦੇ ਹਨ</string>\n    <string name=\"requester_summary\">ਰੂਟ ਸੈਸ਼ਨ ਨਾਮਸਪੇਸ ਬੇਨਤੀਆਂ ਨੂੰ ਪ੍ਰਾਪਤ ਕਰਨਗੇ</string>\n    <string name=\"isolate_summary\">ਹਰੇਕ ਰੂਟ ਸੈਸ਼ਨ ਦਾ ਆਪਣਾ ਵੱਖਰਾ ਨਾਮਸਪੇਸ ਹੋਵੇਗਾ</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">ਮੈਜਿਸਕ ਅਪਡੇਟ</string>\n    <string name=\"progress_channel\">ਤਰੱਕੀ ਦੀਆਂ ਸੂਚਨਾਵਾਂ</string>\n    <string name=\"download_complete\">ਡਾਊਨਲੋਡ ਪੂਰਾ</string>\n    <string name=\"download_file_error\">ਫਾਈਲ ਡਾਊਨਲੋਡ ਕਰਨ ਦੌਰਾਨ ਗਲਤੀ</string>\n    <string name=\"magisk_update_title\">ਮੈਜਿਸਕ ਅਪਡੇਟ ਉਪਲੱਬਧ!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">ਹਾਂ</string>\n    <string name=\"no\">ਨਹੀਂ</string>\n    <string name=\"repo_install_title\">ਇੰਸਟਾਲ %1$s %2$s(%3$d)</string>\n    <string name=\"download\">ਡਾਊਨਲੋਡ</string>\n    <string name=\"reboot\">ਰੀਬੂਟ</string>\n    <string name=\"release_notes\">ਰੀਲਿਜ਼ ਨੋਟਿਸ</string>\n    <string name=\"flashing\">ਫਲੈਸ਼ਿੰਗ …</string>\n    <string name=\"done\">ਸਫਲ ਹੋਇਆ!</string>\n    <string name=\"failure\">ਅਸਫਲ ਹੋਇਆ</string>\n    <string name=\"open_link_failed_toast\">ਲਿੰਕ ਖੋਲ੍ਹਣ ਲਈ ਕੋਈ ਐਪਲੀਕੇਸ਼ਨ ਨਹੀਂ ਮਿਲੀ</string>\n    <string name=\"complete_uninstall\">ਪੂਰੀ ਤਰ੍ਹਾਂ ਅਣਇੰਸਟੌਲ ਕਰੋ</string>\n    <string name=\"restore_img\">ਇਮੇਜ ਰਿਸਟੋਰ ਕਰੋ</string>\n    <string name=\"restore_img_msg\">ਰਿਸਟੋਰ ਹੋ ਰਿਹਾ ਹੈ …</string>\n    <string name=\"restore_done\">रिस्टोर ਸਫਲ ਹੋਇਆ!</string>\n    <string name=\"restore_fail\">ਸਟਾਕ ਬੈਕਅਪ ਮੌਜੂਦ ਨਹੀਂ ਹੈ!</string>\n    <string name=\"setup_fail\">ਸੈਟਅਪ ਅਸਫਲ ਹੋਇਆ</string>\n    <string name=\"env_fix_title\">ਵਾਧੂ ਸੈਟਅਪ ਚਾਹੀਦਾ ਹੈ</string>\n    <string name=\"setup_msg\">ਵਾਤਾਵਰਣ ਸੈਟਅਪ ਚੱਲ ਰਿਹਾ ਹੈ …</string>\n    <string name=\"unsupport_magisk_title\">ਗ਼ੈਰ ਮੈਜਿਸਕ ਵਰਜਨ</string>\n    <string name=\"external_rw_permission_denied\">ਇਸ ਦੀ ਵਰਤੋਂ ਕਰਨ ਲਈ ਸਟੋਰੇਜ ਦੀ ਆਗਿਆ ਦਿਓ</string>\n    <string name=\"add_shortcut_title\">ਹੋਮਸਕ੍ਰੀਨ \\'ਤੇ ਸ਼ਾਰਟਕੱਟ ਸ਼ਾਮਲ ਕਰੋ</string>\n    <string name=\"app_not_found\">ਇਸ ਐਕਸ਼ਨ ਨੂੰ ਸੰਭਾਲਣ ਲਈ ਕੋਈ ਐਪਲੀਕੇਸ਼ਨ ਨਹੀਂ ਮਿਲੀ</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-pl/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduły</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Logi</string>\n    <string name=\"settings\">Ustawienia</string>\n    <string name=\"install\">Instaluj</string>\n    <string name=\"section_home\">Strona główna</string>\n    <string name=\"section_theme\">Motywy</string>\n    <string name=\"denylist\">Lista odmów (DenyList)</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Połączenie niedostępne</string>\n    <string name=\"app_changelog\">Dziennik zmian</string>\n    <string name=\"loading\">Ładowanie…</string>\n    <string name=\"update\">Zaktualizuj</string>\n    <string name=\"not_available\">N/D</string>\n    <string name=\"hide\">Ukryj</string>\n    <string name=\"home_package\">Pakiet</string>\n    <string name=\"home_app_title\">Aplikacja</string>\n\n    <string name=\"home_notice_content\">Pobieraj Magisk tylko z oficjalnej strony GitHub. Pliki z nieznanych źródeł mogą być szkodliwe!</string>\n    <string name=\"home_support_title\">Wesprzyj nas</string>\n    <string name=\"home_follow_title\">Śledź nas</string>\n    <string name=\"home_item_source\">Źródło</string>\n    <string name=\"home_support_content\">Magisk jest i zawsze będzie darmowy i otwarty. Niemniej możesz nam pokazać, że Ci zależy, wysyłając drobną darowiznę.</string>\n    <string name=\"home_installed_version\">Zainstalowany</string>\n    <string name=\"home_latest_version\">Ostatni</string>\n    <string name=\"invalid_update_channel\">Nieprawidłowy kanał aktualizacji</string>\n    <string name=\"uninstall_magisk_title\">Odinstalowywanie Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Wszystkie moduły zostaną wyłączone/usunięte!\\nRoot zostanie usunięty!\\nWszelka wewnętrzna pamięć niezaszyfrowana w związku z użytkowaniem Magisk zostanie ponownie zaszyfrowana!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Zachowaj force encryption (wymuszenie szyfrowania)</string>\n    <string name=\"keep_dm_verity\">Zachowaj AVB 2.0/dm-verity (weryfikację rozruchu)</string>\n    <string name=\"recovery_mode\">Tryb Recovery</string>\n    <string name=\"install_options_title\">Opcje</string>\n    <string name=\"install_method_title\">Metoda</string>\n    <string name=\"install_next\">Dalej</string>\n    <string name=\"install_start\">Zaczynajmy</string>\n    <string name=\"manager_download_install\">Kliknij, aby pobrać i zainstalować</string>\n    <string name=\"direct_install\">Bezpośrednia instalacja (zalecana)</string>\n    <string name=\"install_inactive_slot\">Zainstaluj w nieaktywnym slocie (po OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Po ponownym uruchomieniu twoje urządzenie zostanie ZMUSZONE do rozruchu z aktualnie nieaktywnego slotu!\\nUżywaj tej opcji tylko po ukończeniu aktualizacji OTA.\\nKontynuować?</string>\n    <string name=\"setup_title\">Dodatkowa konfiguracja</string>\n    <string name=\"select_patch_file\">Wybierz i załataj plik</string>\n    <string name=\"patch_file_msg\">Wybierz obraz raw (*.img) lub plik tar ODIN (*.tar) lub payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Ponowne uruchomienie za 5 sekund…</string>\n    <string name=\"flash_screen_title\">Instalacja</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Żądanie dostępu do Superusera</string>\n    <string name=\"touch_filtered_warning\">Ponieważ aplikacja zasłania żądanie o Superusera, Magisk nie może zweryfikować twojej odpowiedzi</string>\n    <string name=\"deny\">Odmów</string>\n    <string name=\"prompt\">Zapytaj</string>\n    <string name=\"grant\">Zezwól</string>\n    <string name=\"su_warning\">Udziela pełnego dostępu do urządzenia.\\nOdmów, jeżeli nie jesteś pewien!</string>\n    <string name=\"forever\">Zawsze</string>\n    <string name=\"once\">Raz</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s otrzymał uprawnienia Superusera</string>\n    <string name=\"su_deny_toast\">%1$s odmówiono uprawnień Superusera</string>\n    <string name=\"su_snack_grant\">Przyznano uprawnienia Superusera dla %1$s</string>\n    <string name=\"su_snack_deny\">Unieważniono uprawnienia Superusera dla %1$s</string>\n    <string name=\"su_snack_notif_on\">Włączono powiadomienia dla %1$s</string>\n    <string name=\"su_snack_notif_off\">Wyłączono powiadomienia dla %1$s</string>\n    <string name=\"su_snack_log_on\">Włączono logowanie dla %1$s</string>\n    <string name=\"su_snack_log_off\">Wyłączono logowanie dla %1$s</string>\n    <string name=\"su_revoke_title\">Unieważnić?</string>\n    <string name=\"su_revoke_msg\">Potwierdź, aby unieważnić uprawnienia Superusera dla %1$s</string>\n    <string name=\"toast\">Wyskakujące powiadomienie</string>\n    <string name=\"none\">Brak</string>\n\n    <string name=\"superuser_toggle_notification\">Powiadomienia</string>\n    <string name=\"superuser_toggle_revoke\">Unieważnij</string>\n    <string name=\"superuser_policy_none\">Żadna aplikacja nie poprosiła jeszcze o uprawnienia Superusera.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nie masz żadnych logów, spróbuj użyć aplikacji wymagających roota</string>\n    <string name=\"log_data_magisk_none\">Logi Magisk są puste, to jest dziwne</string>\n    <string name=\"menuSaveLog\">Zapisz log</string>\n    <string name=\"menuClearLog\">Wyczyść log</string>\n    <string name=\"logs_cleared\">Log został pomyślnie wyczyszczony</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Docelowy UID: %1$d</string>\n    <string name=\"target_pid\">Docelowy PID p.n. montowania: %s</string>\n    <string name=\"selinux_context\">Kontekst SELinux: %s</string>\n    <string name=\"supp_group\">Grupa uzupełniająca: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide -->\n    <string name=\"show_system_app\">Pokaż aplikacje systemowe</string>\n    <string name=\"show_os_app\">Pokaż aplikacje OS</string>\n    <string name=\"hide_filter_hint\">Filtruj według nazwy</string>\n    <string name=\"hide_search\">Szukaj</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">(Brak informacji)</string>\n    <string name=\"reboot_userspace\">Miękki reboot</string>\n    <string name=\"reboot_recovery\">Reboot do trybu Recovery</string>\n    <string name=\"reboot_bootloader\">Reboot do trybu Bootloader</string>\n    <string name=\"reboot_download\">Reboot do trybu Download</string>\n    <string name=\"reboot_edl\">Reboot do trybu EDL</string>\n    <string name=\"module_version_author\">%1$s od %2$s</string>\n    <string name=\"module_state_remove\">Usuń</string>\n    <string name=\"module_state_restore\">Przywróć</string>\n    <string name=\"module_action_install_external\">Zainstaluj z pamięci</string>\n    <string name=\"update_available\">Dostępna aktualizacja</string>\n    <string name=\"suspend_text_riru\">Moduł jest zawieszony, ponieważ %1$s jest włączony</string>\n    <string name=\"suspend_text_zygisk\">Moduł jest zawieszony, ponieważ %1$s nie jest włączony</string>\n    <string name=\"zygisk_module_unloaded\">Moduł Zygisk nie został załadowany z powodu niekompatybilności</string>\n    <string name=\"module_empty\">Brak zainstalowanych modułów</string>\n    <string name=\"confirm_install\">Zainstalować moduł %1$s?</string>\n    <string name=\"confirm_install_title\">Potwierdzenie instalacji</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">Tryb motywu</string>\n    <string name=\"settings_dark_mode_message\">Wybierz tryb, który najbardziej ci odpowiada!</string>\n    <string name=\"settings_dark_mode_light\">Zawsze jasny</string>\n    <string name=\"settings_dark_mode_system\">Ustawienie systemowe</string>\n    <string name=\"settings_dark_mode_dark\">Zawsze ciemny</string>\n    <string name=\"settings_download_path_title\">Ścieżka pobierania plików</string>\n    <string name=\"settings_download_path_message\">Pliki zostaną zapisane do %1$s</string>\n    <string name=\"settings_hide_app_title\">Ukryj aplikację Magisk</string>\n    <string name=\"settings_hide_app_summary\">Zainstaluj aplikację proxy z losowym ID pakietu i własną etykietą</string>\n    <string name=\"settings_restore_app_title\">Przywróć aplikację Magisk</string>\n    <string name=\"settings_restore_app_summary\">Przestań ukrywać aplikację i przywróć oryginalny plik APK</string>\n    <string name=\"language\">Język</string>\n    <string name=\"system_default\">(Domyślny systemowy)</string>\n    <string name=\"settings_check_update_title\">Sprawdzanie aktualizacji</string>\n    <string name=\"settings_check_update_summary\">Okresowo sprawdzaj dostępność aktualizacji w tle</string>\n    <string name=\"settings_update_channel_title\">Kanał aktualizacji</string>\n    <string name=\"settings_update_stable\">Stabilny</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Własny kanał</string>\n    <string name=\"settings_update_custom_msg\">Wprowadź adres URL własnego kanału</string>\n    <string name=\"settings_zygisk_summary\">Uruchom część Magisk w demonie zygote</string>\n    <string name=\"settings_denylist_title\">Egzekwowanie listy odmów (DenyList)</string>\n    <string name=\"settings_denylist_summary\">Procesy znajdujące się na liście odmów (DenyList) będą miały cofnięte wszystkie modyfikacje Magisk</string>\n    <string name=\"settings_denylist_config_title\">Konfiguracja listy odmów (DenyList)</string>\n    <string name=\"settings_denylist_config_summary\">Wybierz procesy, które mają się znajdować na liście odmów (DenyList)</string>\n    <string name=\"settings_hosts_title\">Hosty Systemless</string>\n    <string name=\"settings_hosts_summary\">Wsparcie hostów Systemless dla aplikacji blokujących reklamy</string>\n    <string name=\"settings_hosts_toast\">Dodano moduł hostów Systemless</string>\n    <string name=\"settings_app_name_hint\">Nowa nazwa</string>\n    <string name=\"settings_app_name_helper\">Aplikacja zostanie przepakowana z tą nazwą</string>\n    <string name=\"settings_app_name_error\">Nieprawidłowy format</string>\n    <string name=\"settings_su_app_adb\">Aplikacje i ADB</string>\n    <string name=\"settings_su_app\">Tylko aplikacje</string>\n    <string name=\"settings_su_adb\">Tylko ADB</string>\n    <string name=\"settings_su_disable\">Wyłączone</string>\n    <string name=\"settings_su_request_10\">10 sekund</string>\n    <string name=\"settings_su_request_15\">15 sekund</string>\n    <string name=\"settings_su_request_20\">20 sekund</string>\n    <string name=\"settings_su_request_30\">30 sekund</string>\n    <string name=\"settings_su_request_45\">45 sekund</string>\n    <string name=\"settings_su_request_60\">60 sekund</string>\n    <string name=\"superuser_access\">Dostęp do Superusera</string>\n    <string name=\"auto_response\">Automatyczna reakcja</string>\n    <string name=\"request_timeout\">Limit czasu żądania</string>\n    <string name=\"superuser_notification\">Powiadomienia Superusera</string>\n    <string name=\"settings_su_reauth_title\">Ponowienie uwierzytelniania po aktualizacji</string>\n    <string name=\"settings_su_reauth_summary\">Zapytaj ponownie o uprawnienia Superusera po uaktualnieniu aplikacji</string>\n    <string name=\"settings_su_tapjack_title\">Ochrona przed Tapjackingiem (niezamierzonym kliknięciem)</string>\n    <string name=\"settings_su_tapjack_summary\">Okno dialogowe z żądaniem Superusera nie będzie reagować na odpowiedź, jeżeli będzie zasłonięte przez inne okno lub nakładkę</string>\n    <string name=\"settings_su_auth_title\">Uwierzytelnianie użytkownika</string>\n    <string name=\"settings_su_auth_summary\">Pytaj o uwierzytelnienie użytkownika podczas żądań Superusera</string>\n    <string name=\"settings_su_auth_insecure\">Nie skonfigurowano żadnej metody uwierzytelniania na tym urządzeniu</string>\n    <string name=\"settings_customization\">Personalizacja</string>\n    <string name=\"setting_add_shortcut_summary\">Umieść ładny skrót na ekranie głównym na wypadek, gdyby nazwa i ikona były trudne do rozpoznania po ukryciu aplikacji</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Zapobiegaj zatruwaniu DNS (DNS poisoning) w niektórych krajach</string>\n\n    <string name=\"multiuser_mode\">Tryb wielu użytkowników (Multiuser)</string>\n    <string name=\"settings_owner_only\">Tylko właściciel urządzenia</string>\n    <string name=\"settings_owner_manage\">Zarządzanie przez właściciela urządzenia</string>\n    <string name=\"settings_user_independent\">Niezależne ustawienia użytkowników</string>\n    <string name=\"owner_only_summary\">Tylko właściciel ma dostęp do roota</string>\n    <string name=\"owner_manage_summary\">Tylko właściciel może zarządzać dostępem do roota i otrzymywać żądania dostępu do roota</string>\n    <string name=\"user_independent_summary\">Każdy użytkownik ma niezależne ustawienia dostępu do roota</string>\n\n    <string name=\"mount_namespace_mode\">Tryb przestrzeni nazw montowania</string>\n    <string name=\"settings_ns_global\">Globalna przestrzeń nazw</string>\n    <string name=\"settings_ns_requester\">Dziedziczona przestrzeń nazw</string>\n    <string name=\"settings_ns_isolate\">Izolowana przestrzeń nazw</string>\n    <string name=\"global_summary\">Wszystkie sesje roota będą używać globalnej przestrzeni nazw montowania</string>\n    <string name=\"requester_summary\">Sesje roota odziedziczą przestrzeń nazw żądającego dostępu</string>\n    <string name=\"isolate_summary\">Każda sesja roota otrzyma własną, izolowaną przestrzeń nazw</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Aktualizacja Magisk</string>\n    <string name=\"progress_channel\">Powiadomienia o postępach</string>\n    <string name=\"updated_channel\">Aktualizacja zakończona</string>\n    <string name=\"download_complete\">Pobieranie zakończone</string>\n    <string name=\"download_file_error\">Błąd pobierania pliku</string>\n    <string name=\"magisk_update_title\">Aktualizacja Magisk dostępna!</string>\n    <string name=\"updated_title\">Magisk zaktualizowany</string>\n    <string name=\"updated_text\">Kliknij, aby otworzyć aplikację</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Tak</string>\n    <string name=\"no\">Nie</string>\n    <string name=\"repo_install_title\">Instalacja %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Pobierz</string>\n    <string name=\"reboot\">Reboot</string>\n    <string name=\"release_notes\">Lista zmian</string>\n    <string name=\"flashing\">Flashowanie…</string>\n    <string name=\"done\">Gotowe!</string>\n    <string name=\"failure\">Błąd</string>\n    <string name=\"hide_app_title\">Ukrywanie aplikacji Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nie znaleziono aplikacji do otwarcia linku</string>\n    <string name=\"complete_uninstall\">Odinstaluj całkowicie</string>\n    <string name=\"restore_img\">Przywróć obrazy</string>\n    <string name=\"restore_img_msg\">Przywracanie…</string>\n    <string name=\"restore_done\">Przywracanie zakończone!</string>\n    <string name=\"restore_fail\">Kopia zapasowa nie istnieje!</string>\n    <string name=\"setup_fail\">Konfiguracja nieudana</string>\n    <string name=\"env_fix_title\">Wymagana dodatkowa konfiguracja</string>\n    <string name=\"env_fix_msg\">Twoje urządzenie wymaga dodatkowej konfiguracji, aby Magisk działał poprawnie. Czy chcesz kontynuować i uruchomić ponownie?</string>\n    <string name=\"env_full_fix_msg\">Twoje urządzenie wymaga ponownej instalacji Magisk, aby działać poprawnie. Zainstaluj ponownie Magisk używając aplikacji, tryb recovery nie może uzyskać prawidłowych informacji o urządzeniu.</string>\n    <string name=\"setup_msg\">Uruchamianie środowiska konfiguracji…</string>\n    <string name=\"unsupport_magisk_title\">Nieobsługiwana wersja Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Ta wersja aplikacji nie obsługuje Magisk w wersji niższej niż %1$s.\\n\\nAplikacja będzie zachowywać się tak, jakby Magisk nie był zainstalowany, zaktualizuj Magisk tak szybko, jak to możliwe.</string>\n    <string name=\"unsupport_general_title\">Nieprawidłowy stan</string>\n    <string name=\"unsupport_system_app_msg\">Uruchomienie tej aplikacji jako aplikacja systemowa nie jest obsługiwane. Przywróć aplikację do stanu aplikacji użytkownika.</string>\n    <string name=\"unsupport_other_su_msg\">Wykryto plik binarny \\\"su\\\" nie należący do Magisk. Usuń wszelkie konkurencyjne rozwiązania root i/lub zainstaluj ponownie Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk jest zainstalowany w pamięci zewnętrznej. Przenieś aplikację do pamięci wewnętrznej.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Ukryta aplikacja Magisk nie może dłużej działać, ponieważ uprawnienia roota zostały utracone. Przywróć oryginalny plik APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Zezwól na dostęp do pamięci, aby włączyć tę funkcję</string>\n    <string name=\"post_notifications_denied\">Zezwól na otrzymywanie powiadomień, aby włączyć tę funkcję</string>\n    <string name=\"install_unknown_denied\">Zezwól na \\\"Instaluj z nieznanych źródeł\\\", aby włączyć tę funkcję</string>\n    <string name=\"add_shortcut_title\">Utwórz skrót na ekranie głównym</string>\n    <string name=\"add_shortcut_msg\">Po ukryciu tej aplikacji, jej nazwa i ikona może być trudna do rozpoznania. Czy chcesz dodać ładny skrót na ekranie głównym?</string>\n    <string name=\"app_not_found\">Nie znaleziono aplikacji do wykonania tej czynności</string>\n    <string name=\"reboot_apply_change\">Uruchom ponownie, aby zastosować zmiany</string>\n    <string name=\"restore_app_confirmation\">Spowoduje to przywrócenie ukrytej aplikacji z powrotem do stanu oryginalnej aplikacji. Czy na pewno chcesz to zrobić?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-pt-rBR/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Módulos</string>\n    <string name=\"superuser\">SuperUsuário</string>\n    <string name=\"logs\">Registros</string>\n    <string name=\"settings\">Configurações</string>\n    <string name=\"install\">Instalar</string>\n    <string name=\"section_home\">Início</string>\n    <string name=\"section_theme\">Temas</string>\n    <string name=\"denylist\">Lista de negação</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nenhuma conexão disponível</string>\n    <string name=\"app_changelog\">Registro de alterações</string>\n    <string name=\"loading\">Carregando…</string>\n    <string name=\"update\">Atualizar</string>\n    <string name=\"not_available\">Não disponível</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"home_package\">Pacote</string>\n    <string name=\"home_app_title\">App</string>\n    <string name=\"home_notice_content\">Baixe o Magisk SOMENTE pela página oficial do GitHub. Arquivos de fontes desconhecidas podem ser maliciosos!</string>\n    <string name=\"home_support_title\">Apoie-nos</string>\n    <string name=\"home_follow_title\">Siga-nos</string>\n    <string name=\"home_item_source\">Fonte</string>\n    <string name=\"home_support_content\">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode nos ajudar enviando uma pequena doação.</string>\n    <string name=\"home_installed_version\">Instalado</string>\n    <string name=\"home_latest_version\">Recente</string>\n    <string name=\"invalid_update_channel\">Canal de atualização inválido</string>\n    <string name=\"uninstall_magisk_title\">Desinstalar Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Todos os módulos serão desativados/removidos!\\nO root será removido!\\nSeus dados não criptografados devido ao uso do Magisk, serão re-criptografados!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Manter criptografia forçada</string>\n    <string name=\"keep_dm_verity\">Manter AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Modo Recovery</string>\n    <string name=\"install_options_title\">Opções</string>\n    <string name=\"install_method_title\">Método</string>\n    <string name=\"install_next\">Próximo</string>\n    <string name=\"install_start\">Vamos lá</string>\n    <string name=\"manager_download_install\">Toque para baixar e instalar</string>\n    <string name=\"direct_install\">Instalação direta (recomendada)</string>\n    <string name=\"install_inactive_slot\">Instalar no slot inativo (após o OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Seu dispositivo será FORÇADO a inicializar no slot inativo atual após uma reinicialização!\\nSó use esta opção após a conclusão do OTA.\\nDeseja continuar?</string>\n    <string name=\"setup_title\">Configuração adicional</string>\n    <string name=\"select_patch_file\">Selecione e corrija um arquivo</string>\n    <string name=\"patch_file_msg\">Selecione um arquivo imagem (*.img) ou um arquivo tar (*.tar) ou um arquivo payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Reiniciando em 5 segundos…</string>\n    <string name=\"flash_screen_title\">Instalação</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Solicitação de SuperUsuário</string>\n    <string name=\"touch_filtered_warning\">Como um app está ocultando uma solicitação de SuperUsuário, o Magisk não pode verificar sua resposta.</string>\n    <string name=\"deny\">Negar</string>\n    <string name=\"prompt\">Perguntar</string>\n    <string name=\"restrict\">Restringir</string>\n    <string name=\"grant\">Permitir</string>\n    <string name=\"su_warning\">Permite acesso total ao seu dispositivo.\\nNão permita se você não tiver certeza do que está fazendo!</string>\n    <string name=\"forever\">Sempre</string>\n    <string name=\"once\">Uma vez</string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">%1$s foi permitido o acesso de SuperUsuário</string>\n    <string name=\"su_deny_toast\">%1$s foi negado o acesso de SuperUsuário</string>\n    <string name=\"su_snack_grant\">Os acessos de SuperUsuário de %1$s foram permitidos</string>\n    <string name=\"su_snack_deny\">Os acessos de SuperUsuário de %1$s foram negados</string>\n    <string name=\"su_snack_notif_on\">As notificações de %1$s foram ativadas</string>\n    <string name=\"su_snack_notif_off\">As notificações de %1$s foram desativadas</string>\n    <string name=\"su_snack_log_on\">Os registros de %1$s foram ativados</string>\n    <string name=\"su_snack_log_off\">Os registros de %1$s foram desativados</string>\n    <string name=\"su_revoke_title\">Revogar?</string>\n    <string name=\"su_revoke_msg\">Confirme para revogar os acessos de SuperUsuário de %1$s</string>\n    <string name=\"toast\">Notificação (Pop-up)</string>\n    <string name=\"none\">Nenhum</string>\n    <string name=\"superuser_toggle_notification\">Notificações</string>\n    <string name=\"superuser_toggle_revoke\">Revogar</string>\n    <string name=\"superuser_policy_none\">Nenhum app solicitou permissão de SuperUsuário ainda.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Não há registros. Tente usar mais seus apps root.</string>\n    <string name=\"log_data_magisk_none\">Os registros do Magisk estão vazios, isso é estranho.</string>\n    <string name=\"menuSaveLog\">Salvar registros</string>\n    <string name=\"menuClearLog\">Limpar registros agora</string>\n    <string name=\"logs_cleared\">Registros limpo com sucesso</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Alvo UID: %1$d</string>\n    <string name=\"target_pid\">Alvo PID: %s</string>\n    <string name=\"selinux_context\">Contexto SELinux: %s</string>\n    <string name=\"supp_group\">Grupo suplementar: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Mostrar apps do sistema</string>\n    <string name=\"show_os_app\">Mostrar apps do SO</string>\n    <string name=\"hide_filter_hint\">Filtrar pelo nome</string>\n    <string name=\"hide_search\">Pesquisar</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nenhuma informação fornecida)</string>\n    <string name=\"reboot_userspace\">Reinicialização suave</string>\n    <string name=\"reboot_recovery\">Reiniciar em modo Recovery</string>\n    <string name=\"reboot_bootloader\">Reiniciar em modo Bootloader</string>\n    <string name=\"reboot_download\">Reiniciar em modo Download</string>\n    <string name=\"reboot_edl\">Reiniciar em modo EDL</string>\n    <string name=\"reboot_safe_mode\">Modo de segurança</string>\n    <string name=\"module_version_author\">%1$s por %2$s</string>\n    <string name=\"module_state_remove\">Remover</string>\n    <string name=\"module_action\">Ação</string>\n    <string name=\"module_state_restore\">Restaurar</string>\n    <string name=\"module_action_install_external\">Instalar a partir do armazenamento</string>\n    <string name=\"update_available\">Atualização disponível</string>\n    <string name=\"suspend_text_riru\">Módulo suspenso porque %1$s está ativado</string>\n    <string name=\"suspend_text_zygisk\">Módulo suspenso porque %1$s não está ativado</string>\n    <string name=\"zygisk_module_unloaded\">Modulo Zygisk não carregado devido a incompatibilidade</string>\n    <string name=\"module_empty\">Nenhum módulo instalado</string>\n    <string name=\"confirm_install\">Instalar módulo %1$s?</string>\n    <string name=\"confirm_install_title\">Confirmação de instalação</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Modo do tema</string>\n    <string name=\"settings_dark_mode_message\">Selecione o modo mais adequado para você!</string>\n    <string name=\"settings_dark_mode_light\">Sempre claro</string>\n    <string name=\"settings_dark_mode_system\">Seguir sistema</string>\n    <string name=\"settings_dark_mode_dark\">Sempre escuro</string>\n    <string name=\"settings_download_path_title\">Caminho para baixar</string>\n    <string name=\"settings_download_path_message\">Os arquivos serão salvos em %1$s</string>\n    <string name=\"settings_hide_app_title\">Ocultar app do Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instala o app oculto com ID aleatório e nome personalizado</string>\n    <string name=\"settings_restore_app_title\">Restaurar app do Magisk</string>\n    <string name=\"settings_restore_app_summary\">Desoculta o app do Magisk e restaura o APK original</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"system_default\">(Padrão do sistema)</string>\n    <string name=\"settings_check_update_title\">Verificar por atualizações</string>\n    <string name=\"settings_check_update_summary\">Verifique automaticamente se há atualizações ao abrir o app</string>\n    <string name=\"settings_update_channel_title\">Canal de atualização</string>\n    <string name=\"settings_update_stable\">Estável</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Personalizado</string>\n    <string name=\"settings_update_custom_msg\">Insira um URL de canal personalizado</string>\n    <string name=\"settings_zygisk_summary\">Executa partes do Magisk no Zygote</string>\n    <string name=\"settings_denylist_title\">Aplicar lista de negação</string>\n    <string name=\"settings_denylist_summary\">Os processos na lista de negação terão todas as modificações do Magisk revertidas</string>\n    <string name=\"settings_denylist_config_title\">Configurar lista de negação</string>\n    <string name=\"settings_denylist_config_summary\">Selecione os processos a serem incluídos na lista de negação</string>\n    <string name=\"settings_hosts_title\">Hosts sem sistema</string>\n    <string name=\"settings_hosts_summary\">Suporte de hosts sem sistema para apps AdBlock</string>\n    <string name=\"settings_hosts_toast\">Adicionado módulo de hosts sem sistema</string>\n    <string name=\"settings_app_name_hint\">Novo nome</string>\n    <string name=\"settings_app_name_helper\">O app do Magisk será reinstalado com este nome</string>\n    <string name=\"settings_app_name_error\">Formato inválido</string>\n    <string name=\"settings_su_app_adb\">Apps e ADB</string>\n    <string name=\"settings_su_app\">Apenas apps</string>\n    <string name=\"settings_su_adb\">Apenas ADB</string>\n    <string name=\"settings_su_disable\">Desativado</string>\n    <string name=\"settings_su_request_10\">10 segundos</string>\n    <string name=\"settings_su_request_15\">15 segundos</string>\n    <string name=\"settings_su_request_20\">20 segundos</string>\n    <string name=\"settings_su_request_30\">30 segundos</string>\n    <string name=\"settings_su_request_45\">45 segundos</string>\n    <string name=\"settings_su_request_60\">60 segundos</string>\n    <string name=\"superuser_access\">Acesso de SuperUsuário</string>\n    <string name=\"auto_response\">Resposta automática</string>\n    <string name=\"request_timeout\">Tempo limite da solicitação</string>\n    <string name=\"superuser_notification\">Notificação de SuperUsuário</string>\n    <string name=\"settings_su_reauth_title\">Reautenticar após atualização</string>\n    <string name=\"settings_su_reauth_summary\">Solicite permissões de SuperUsuário novamente após atualizar os apps</string>\n    <string name=\"settings_su_tapjack_title\">Proteção contra atividades sobrepostas</string>\n    <string name=\"settings_su_tapjack_summary\">A caixa de diálogo do SuperUsuário não responderá à entrada enquanto estiver oculta por qualquer outra janela ou sobreposição</string>\n    <string name=\"settings_su_auth_title\">Autenticação de usuário</string>\n    <string name=\"settings_su_auth_summary\">Solicite autenticação de usuário durante solicitações de SuperUsuário</string>\n    <string name=\"settings_su_auth_insecure\">Nenhum método de autenticação está configurado no dispositivo</string>\n    <string name=\"settings_su_restrict_title\">Restringir recursos root</string>\n    <string name=\"settings_su_restrict_summary\">Restringirá novos apps de SuperUsuário por padrão. Aviso: isso quebrará a maioria dos apps. Não ative se você não souber o que está fazendo.</string>\n    <string name=\"settings_customization\">Personalizações</string>\n    <string name=\"setting_add_shortcut_summary\">Adicione um atalho na tela inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>\n    <string name=\"settings_doh_title\">DNS sobre HTTPS</string>\n    <string name=\"settings_doh_description\">Solução alternativa para envenenamento de DNS em alguns países</string>\n    <string name=\"settings_random_name_title\">Randomizar nome de saída</string>\n    <string name=\"settings_random_name_description\">Randomize o nome do arquivo de saída de imagens corrigidas e arquivos tar (*.tar) para evitar a detecção</string>\n    <string name=\"multiuser_mode\">Modo multiusuário</string>\n    <string name=\"settings_owner_only\">Somente proprietário do dispositivo</string>\n    <string name=\"settings_owner_manage\">Gerenciado pelo proprietário do dispositivo</string>\n    <string name=\"settings_user_independent\">Independente do usuário</string>\n    <string name=\"owner_only_summary\">Somente o proprietário tem acesso root</string>\n    <string name=\"owner_manage_summary\">Somente o proprietário pode gerenciar o acesso root e receber pedidos de solicitação</string>\n    <string name=\"user_independent_summary\">Cada usuário tem suas próprias regras de root separadas</string>\n    <string name=\"mount_namespace_mode\">Montar namespace</string>\n    <string name=\"settings_ns_global\">Global</string>\n    <string name=\"settings_ns_requester\">Herdado</string>\n    <string name=\"settings_ns_isolate\">Individual</string>\n    <string name=\"global_summary\">Todas as sessões root usam o namespace de montagem global</string>\n    <string name=\"requester_summary\">As sessões root herdarão o namespace do solicitante</string>\n    <string name=\"isolate_summary\">Cada sessão root terá seu próprio namespace individual</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Atualizações do Magisk</string>\n    <string name=\"progress_channel\">Notificações de progresso</string>\n    <string name=\"updated_channel\">Atualização concluída</string>\n    <string name=\"download_complete\">Download concluído</string>\n    <string name=\"download_file_error\">Erro ao baixar arquivo</string>\n    <string name=\"magisk_update_title\">Atualização do Magisk disponível!</string>\n    <string name=\"updated_title\">Magisk atualizado</string>\n    <string name=\"updated_text\">Toque para abrir o app</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Sim</string>\n    <string name=\"no\">Não</string>\n    <string name=\"repo_install_title\">Instalar %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Baixar</string>\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"close\">Fechar</string>\n    <string name=\"release_notes\">Notas da atualização</string>\n    <string name=\"flashing\">Flashando…</string>\n    <string name=\"running\">Executando…</string>\n    <string name=\"done\">Concluído!</string>\n    <string name=\"done_action\">Ação de execução de %1$s concluída</string>\n    <string name=\"failure\">Falhou!</string>\n    <string name=\"hide_app_title\">Ocultando o app do Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nenhum app encontrado para abrir o link</string>\n    <string name=\"complete_uninstall\">Desinstalação completa</string>\n    <string name=\"restore_img\">Restaurar imagens</string>\n    <string name=\"restore_img_msg\">Restaurando…</string>\n    <string name=\"restore_done\">Restauração concluída!</string>\n    <string name=\"restore_fail\">O backup original não existe!</string>\n    <string name=\"setup_fail\">Falha na instalação</string>\n    <string name=\"env_fix_title\">Configuração adicional exigida</string>\n    <string name=\"env_fix_msg\">Seu dispositivo exige uma configuração adicional para o Magisk funcionar corretamente. Deseja continuar e reiniciar?</string>\n    <string name=\"env_full_fix_msg\">Seu dispositivo precisa refazer o flash do Magisk para funcionar corretamente. Por favor, reinstale o Magisk no app, o modo Recovery não pode obter as devidas informações do dispositivo.</string>\n    <string name=\"setup_msg\">Executando a configuração do ambiente…</string>\n    <string name=\"unsupport_magisk_title\">Versão do Magisk não suportada</string>\n    <string name=\"unsupport_magisk_msg\">Esta versão do app não suporta a versão do Magisk inferior a %1$s.\\n\\nO app irá se comportar como se nenhum Magisk estivesse sido instalado. Por favor, atualize o Magisk assim que possível.</string>\n    <string name=\"unsupport_general_title\">Estado anormal</string>\n    <string name=\"unsupport_system_app_msg\">Não há suporte para executar este app como um app do sistema. Por favor, reverta o app para um app de usuário.</string>\n    <string name=\"unsupport_other_su_msg\">Não foi possível detectar o binário \\\"su\\\" do Magisk. Por favor, remova qualquer outro root concorrente e/ou reinstale o Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">O app do Magisk está instalado no armazenamento externo. Por favor, mova o app para o armazenamento interno.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">O app oculto do Magisk não pode continuar funcionando porque o root foi perdido. Por favor, restaure o APK original.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Conceda permissão de armazenamento para ativar esta funcionalidade</string>\n    <string name=\"post_notifications_denied\">Conceda permissão às notificações para ativar esta funcionalidade</string>\n    <string name=\"install_unknown_denied\">Permita a opção \\\"Instalar apps de fontes desconhecidas\\\" para ativar esta funcionalidade</string>\n    <string name=\"add_shortcut_title\">Adicionar atalho à tela inicial</string>\n    <string name=\"add_shortcut_msg\">Após ocultar o app do Magisk, seu nome e ícone ficarão difíceis de reconhecer. Deseja adicionar um atalho na tela inicial?</string>\n    <string name=\"app_not_found\">Nenhum app encontrado para realizar esta ação</string>\n    <string name=\"reboot_apply_change\">Reinicie para aplicar as mudanças</string>\n    <string name=\"restore_app_confirmation\">Isso irá restaurar o app oculto do Magisk de volta para o app original. Deseja realmente fazer isso?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-pt-rPT/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Módulos</string>\n    <string name=\"superuser\">SuperUsuário</string>\n    <string name=\"logs\">Registos</string>\n    <string name=\"settings\">Configurações</string>\n    <string name=\"install\">Instalar</string>\n    <string name=\"section_home\">Início</string>\n    <string name=\"section_theme\">Temas</string>\n    <string name=\"denylist\">Lista de negação</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nenhuma ligação disponível</string>\n    <string name=\"app_changelog\">Registo de alterações</string>\n    <string name=\"loading\">A carregar…</string>\n    <string name=\"update\">Atualizar</string>\n    <string name=\"not_available\">Não disponível</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"home_package\">Pacote</string>\n    <string name=\"home_app_title\">App</string>\n    <string name=\"home_notice_content\">Descarregue o Magisk APENAS na página oficial do GitHub. Os ficheiros de fontes desconhecidas podem ser maliciosos!</string>\n    <string name=\"home_support_title\">Apoie-nos</string>\n    <string name=\"home_follow_title\">Siga-nos</string>\n    <string name=\"home_item_source\">Fonte</string>\n    <string name=\"home_support_content\">Magisk sempre foi e sempre será, gratuito e de código aberto. No entanto, você pode ajudar-nos enviando uma pequena doação.</string>\n    <string name=\"home_installed_version\">Instalado</string>\n    <string name=\"home_latest_version\">Recente</string>\n    <string name=\"invalid_update_channel\">Canal de atualização inválido</string>\n    <string name=\"uninstall_magisk_title\">Desinstalar Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Todos os módulos serão desativados/removidos!\\nO root será removido!\\nOs seus dados não encriptados devido à utilização do Magisk, serão re-encriptados!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Manter encriptação forçada</string>\n    <string name=\"keep_dm_verity\">Manter AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Modo Recovery</string>\n    <string name=\"install_options_title\">Opções</string>\n    <string name=\"install_method_title\">Método</string>\n    <string name=\"install_next\">Próximo</string>\n    <string name=\"install_start\">Vamos lá</string>\n    <string name=\"manager_download_install\">Toque para descarregar e instalar</string>\n    <string name=\"direct_install\">Instalação direta (recomendada)</string>\n    <string name=\"install_inactive_slot\">Instalar no slot inativo (após o OTA)</string>\n    <string name=\"install_inactive_slot_msg\">O seu dispositivo será FORÇADO a inicializar no slot inativo atual após um reinício!\\nSó use esta opção após a conclusão do OTA.\\nDeseja continuar?</string>\n    <string name=\"setup_title\">Configuração adicional</string>\n    <string name=\"select_patch_file\">Selecione e corrija um ficheiro</string>\n    <string name=\"patch_file_msg\">Selecione um ficheiro de imagem (*.img) ou um ficheiro tar (*.tar) ou um ficheiro payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">A reiniciar em 5 segundos…</string>\n    <string name=\"flash_screen_title\">Instalação</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Solicitação de SuperUsuário</string>\n    <string name=\"touch_filtered_warning\">Como um app está a ocultar um pedido de SuperUsuário, o Magisk não consegue verificar a sua resposta.</string>\n    <string name=\"deny\">Negar</string>\n    <string name=\"prompt\">Perguntar</string>\n    <string name=\"restrict\">Restringir</string>\n    <string name=\"grant\">Permitir</string>\n    <string name=\"su_warning\">Permite o acesso total ao seu dispositivo.\\nNão o permita se não tiver a certeza do que está a fazer!</string>\n    <string name=\"forever\">Sempre</string>\n    <string name=\"once\">Uma vez</string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">%1$s foi permitido o acesso de SuperUsuário</string>\n    <string name=\"su_deny_toast\">%1$s foi negado o acesso de SuperUsuário</string>\n    <string name=\"su_snack_grant\">Os acessos de SuperUsuário de %1$s foram permitidos</string>\n    <string name=\"su_snack_deny\">Os acessos de SuperUsuário de %1$s foram negados</string>\n    <string name=\"su_snack_notif_on\">As notificações de %1$s foram ativadas</string>\n    <string name=\"su_snack_notif_off\">As notificações de %1$s foram desativadas</string>\n    <string name=\"su_snack_log_on\">Os registos de %1$s foram ativados</string>\n    <string name=\"su_snack_log_off\">Os registos de %1$s foram desativados</string>\n    <string name=\"su_revoke_title\">Revogar?</string>\n    <string name=\"su_revoke_msg\">Confirme para revogar os acessos de SuperUsuário de %1$s</string>\n    <string name=\"toast\">Notificação (Pop-up)</string>\n    <string name=\"none\">Nenhum</string>\n    <string name=\"superuser_toggle_notification\">Notificações</string>\n    <string name=\"superuser_toggle_revoke\">Revogar</string>\n    <string name=\"superuser_policy_none\">Nenhum app solicitou permissão de SuperUsuário ainda.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Não há registos. Tente utilizar mais seus apps root.</string>\n    <string name=\"log_data_magisk_none\">Os registos do Magisk estão vazios, isto é estranho.</string>\n    <string name=\"menuSaveLog\">Salvar registos</string>\n    <string name=\"menuClearLog\">Limpar registos agora</string>\n    <string name=\"logs_cleared\">Registos limpo com sucesso</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Alvo UID: %1$d</string>\n    <string name=\"target_pid\">Alvo PID: %s</string>\n    <string name=\"selinux_context\">Contexto SELinux: %s</string>\n    <string name=\"supp_group\">Grupo suplementar: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Mostrar apps do sistema</string>\n    <string name=\"show_os_app\">Mostrar apps do SO</string>\n    <string name=\"hide_filter_hint\">Filtrar pelo nome</string>\n    <string name=\"hide_search\">Pesquisar</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nenhuma informação fornecida)</string>\n    <string name=\"reboot_userspace\">Reinicialização suave</string>\n    <string name=\"reboot_recovery\">Reiniciar em modo Recovery</string>\n    <string name=\"reboot_bootloader\">Reiniciar em modo Bootloader</string>\n    <string name=\"reboot_download\">Reiniciar em modo Download</string>\n    <string name=\"reboot_edl\">Reiniciar em modo EDL</string>\n    <string name=\"reboot_safe_mode\">Modo de segurança</string>\n    <string name=\"module_version_author\">%1$s por %2$s</string>\n    <string name=\"module_state_remove\">Remover</string>\n    <string name=\"module_action\">Ação</string>\n    <string name=\"module_state_restore\">Restaurar</string>\n    <string name=\"module_action_install_external\">Instalar a partir do armazenamento</string>\n    <string name=\"update_available\">Atualização disponível</string>\n    <string name=\"suspend_text_riru\">Módulo suspenso porque %1$s está ativado</string>\n    <string name=\"suspend_text_zygisk\">Módulo suspenso porque %1$s não está ativado</string>\n    <string name=\"zygisk_module_unloaded\">Modulo Zygisk não carregado devido a incompatibilidade</string>\n    <string name=\"module_empty\">Nenhum módulo instalado</string>\n    <string name=\"confirm_install\">Instalar módulo %1$s?</string>\n    <string name=\"confirm_install_title\">Confirmação de instalação</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Modo do tema</string>\n    <string name=\"settings_dark_mode_message\">Selecione o modo mais adequado para si!</string>\n    <string name=\"settings_dark_mode_light\">Sempre claro</string>\n    <string name=\"settings_dark_mode_system\">Seguir sistema</string>\n    <string name=\"settings_dark_mode_dark\">Sempre escuro</string>\n    <string name=\"settings_download_path_title\">Caminho para descarregar</string>\n    <string name=\"settings_download_path_message\">Os ficheiros serão salvos em %1$s</string>\n    <string name=\"settings_hide_app_title\">Ocultar app do Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instala o app oculto com ID aleatório e nome personalizado</string>\n    <string name=\"settings_restore_app_title\">Restaurar app do Magisk</string>\n    <string name=\"settings_restore_app_summary\">Desoculta o app do Magisk e restaura o APK original</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"system_default\">(Predefinição do sistema)</string>\n    <string name=\"settings_check_update_title\">Verificar por atualizações</string>\n    <string name=\"settings_check_update_summary\">Verifique automaticamente se há atualizações ao abrir o app</string>\n    <string name=\"settings_update_channel_title\">Canal de atualização</string>\n    <string name=\"settings_update_stable\">Estável</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Personalizado</string>\n    <string name=\"settings_update_custom_msg\">Introduza um URL de canal personalizado</string>\n    <string name=\"settings_zygisk_summary\">Executa partes do Magisk no Zygote</string>\n    <string name=\"settings_denylist_title\">Aplicar lista de negação</string>\n    <string name=\"settings_denylist_summary\">Os processos na lista de negação terão todas as modificações do Magisk revertidas</string>\n    <string name=\"settings_denylist_config_title\">Configurar lista de negação</string>\n    <string name=\"settings_denylist_config_summary\">Selecione os processos a serem incluídos na lista de negação</string>\n    <string name=\"settings_hosts_title\">Hosts sem sistema</string>\n    <string name=\"settings_hosts_summary\">Suporte de hosts sem sistema para apps AdBlock</string>\n    <string name=\"settings_hosts_toast\">Adicionado módulo de hosts sem sistema</string>\n    <string name=\"settings_app_name_hint\">Novo nome</string>\n    <string name=\"settings_app_name_helper\">O app do Magisk será reinstalado com este nome</string>\n    <string name=\"settings_app_name_error\">Formato inválido</string>\n    <string name=\"settings_su_app_adb\">Apps e ADB</string>\n    <string name=\"settings_su_app\">Apenas apps</string>\n    <string name=\"settings_su_adb\">Apenas ADB</string>\n    <string name=\"settings_su_disable\">Desativado</string>\n    <string name=\"settings_su_request_10\">10 segundos</string>\n    <string name=\"settings_su_request_15\">15 segundos</string>\n    <string name=\"settings_su_request_20\">20 segundos</string>\n    <string name=\"settings_su_request_30\">30 segundos</string>\n    <string name=\"settings_su_request_45\">45 segundos</string>\n    <string name=\"settings_su_request_60\">60 segundos</string>\n    <string name=\"superuser_access\">Acesso de SuperUsuário</string>\n    <string name=\"auto_response\">Resposta automática</string>\n    <string name=\"request_timeout\">Tempo limite da solicitação</string>\n    <string name=\"superuser_notification\">Notificação de SuperUsuário</string>\n    <string name=\"settings_su_reauth_title\">Reautenticar após atualização</string>\n    <string name=\"settings_su_reauth_summary\">Solicite permissões de SuperUsuário novamente após atualizar os apps</string>\n    <string name=\"settings_su_tapjack_title\">Proteção contra atividades sobrepostas</string>\n    <string name=\"settings_su_tapjack_summary\">A caixa de diálogo do SuperUsuário não responderá à entrada enquanto estiver oculta por qualquer outra janela ou sobreposição</string>\n    <string name=\"settings_su_auth_title\">Autenticação de usuário</string>\n    <string name=\"settings_su_auth_summary\">Solicite autenticação de usuário durante pedidos de SuperUsuário</string>\n    <string name=\"settings_su_auth_insecure\">Nenhum método de autenticação está configurado no dispositivo</string>\n    <string name=\"settings_su_restrict_title\">Restringir recursos root</string>\n    <string name=\"settings_su_restrict_summary\">Restringirá novos apps de SuperUsuário por predefinição. Aviso: isto quebrará a maioria dos apps. Não ative se você não souber o que está a fazer.</string>\n    <string name=\"settings_customization\">Personalizações</string>\n    <string name=\"setting_add_shortcut_summary\">Adicione um atalho no ecrã inicial, caso o nome e o ícone sejam difíceis de reconhecer logo após ocultar o app.</string>\n    <string name=\"settings_doh_title\">DNS sobre HTTPS</string>\n    <string name=\"settings_doh_description\">Solução alternativa para envenenamento de DNS em alguns países</string>\n    <string name=\"settings_random_name_title\">Randomizar nome de saída</string>\n    <string name=\"settings_random_name_description\">Randomize o nome do ficheiro de saída de imagens corrigidas e ficheiros tar (*.tar) para evitar a deteção</string>\n    <string name=\"multiuser_mode\">Modo multiusuário</string>\n    <string name=\"settings_owner_only\">Somente proprietário do dispositivo</string>\n    <string name=\"settings_owner_manage\">Gerido pelo proprietário do dispositivo</string>\n    <string name=\"settings_user_independent\">Independente do usuário</string>\n    <string name=\"owner_only_summary\">Somente o proprietário tem acesso root</string>\n    <string name=\"owner_manage_summary\">Somente o proprietário pode gerir o acesso root e receber pedidos de solicitação</string>\n    <string name=\"user_independent_summary\">Cada usuário tem suas próprias regras de root separadas</string>\n    <string name=\"mount_namespace_mode\">Montar namespace</string>\n    <string name=\"settings_ns_global\">Global</string>\n    <string name=\"settings_ns_requester\">Herdado</string>\n    <string name=\"settings_ns_isolate\">Individual</string>\n    <string name=\"global_summary\">Todas as sessões root utilizam o namespace de montagem global</string>\n    <string name=\"requester_summary\">As sessões root herdarão o namespace do requerente</string>\n    <string name=\"isolate_summary\">Cada sessão root terá o seu próprio namespace individual</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Atualizações do Magisk</string>\n    <string name=\"progress_channel\">Notificações de progresso</string>\n    <string name=\"updated_channel\">Atualização concluída</string>\n    <string name=\"download_complete\">Download concluído</string>\n    <string name=\"download_file_error\">Erro ao descarregar ficheiro</string>\n    <string name=\"magisk_update_title\">Atualização do Magisk disponível!</string>\n    <string name=\"updated_title\">Magisk atualizado</string>\n    <string name=\"updated_text\">Toque para abrir o app</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Sim</string>\n    <string name=\"no\">Não</string>\n    <string name=\"repo_install_title\">Instalar %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Baixar</string>\n    <string name=\"reboot\">Reiniciar</string>\n    <string name=\"close\">Fechar</string>\n    <string name=\"release_notes\">Notas da atualização</string>\n    <string name=\"flashing\">Flashando…</string>\n    <string name=\"running\">A executar…</string>\n    <string name=\"done\">Concluído!</string>\n    <string name=\"done_action\">Ação de execução de %1$s concluída</string>\n    <string name=\"failure\">Falhou!</string>\n    <string name=\"hide_app_title\">A ocultar o app do Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nenhum app encontrado para abrir o link</string>\n    <string name=\"complete_uninstall\">Desinstalação completa</string>\n    <string name=\"restore_img\">Restaurar imagens</string>\n    <string name=\"restore_img_msg\">A restaurar…</string>\n    <string name=\"restore_done\">Restauração concluída!</string>\n    <string name=\"restore_fail\">O backup original não existe!</string>\n    <string name=\"setup_fail\">Falha na instalação</string>\n    <string name=\"env_fix_title\">Configuração adicional exigida</string>\n    <string name=\"env_fix_msg\">Seu dispositivo exige uma configuração adicional para o Magisk funcionar corretamente. Deseja continuar e reiniciar?</string>\n    <string name=\"env_full_fix_msg\">O seu dispositivo precisa de refazer o flash do Magisk para funcionar corretamente. Por favor, reinstale o Magisk no app, o modo Recovery não consegue obter as devidas informações do dispositivo.</string>\n    <string name=\"setup_msg\">A executar a configuração do ambiente…</string>\n    <string name=\"unsupport_magisk_title\">Versão do Magisk não suportada</string>\n    <string name=\"unsupport_magisk_msg\">Esta versão do app não suporta versões do Magisk inferiores a %1$s.\\n\\nO app irá comportar-se como se nenhum Magisk estivesse instalado. Por favor, atualize o Magisk assim que possível.</string>\n    <string name=\"unsupport_general_title\">Estado anormal</string>\n    <string name=\"unsupport_system_app_msg\">Não há suporte para executar este app como um app do sistema. Por favor, reverta o app para um app de usuário.</string>\n    <string name=\"unsupport_other_su_msg\">Não foi possível detectar o binário \\\"su\\\" do Magisk. Por favor, remova qualquer outro root concorrente e/ou reinstale o Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">O app do Magisk está instalado no armazenamento externo. Por favor, mova o app para o armazenamento interno.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">O app oculto do Magisk não pode continuar a funcionar porque o root foi perdido. Por favor, restaure o APK original.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Conceda permissão de armazenamento para ativar esta funcionalidade</string>\n    <string name=\"post_notifications_denied\">Conceda permissão às notificações para ativar esta funcionalidade</string>\n    <string name=\"install_unknown_denied\">Permita a opção \\\"Instalar apps de fontes desconhecidas\\\" para ativar esta funcionalidade</string>\n    <string name=\"add_shortcut_title\">Adicionar atalho ao ecrã inicial</string>\n    <string name=\"add_shortcut_msg\">Após ocultar o app do Magisk, o seu nome e ícone serão difíceis de reconhecer. Deseja adicionar um atalho no ecrã inicial?</string>\n    <string name=\"app_not_found\">Nenhum app encontrado para realizar esta ação</string>\n    <string name=\"reboot_apply_change\">Reinicie para aplicar as alterações</string>\n    <string name=\"restore_app_confirmation\">Isso irá restaurar o app oculto do Magisk de volta para o app original. Deseja realmente fazer isso?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ro/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Module</string>\n    <string name=\"superuser\">Superutilizator</string>\n    <string name=\"logs\">Jurnale</string>\n    <string name=\"settings\">Setări</string>\n    <string name=\"install\">Instalează</string>\n    <string name=\"section_home\">Start</string>\n    <string name=\"section_theme\">Teme</string>\n    <string name=\"denylist\">Listă de refuzări</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nicio conexiune disponibilă</string>\n    <string name=\"app_changelog\">Jurnalul modificărilor</string>\n    <string name=\"loading\">Se încarcă…</string>\n    <string name=\"update\">Actualizează</string>\n    <string name=\"not_available\">Indisponibil</string>\n    <string name=\"hide\">Ascunde</string>\n    <string name=\"home_package\">Pachet</string>\n    <string name=\"home_app_title\">Aplicație</string>\n\n    <string name=\"home_notice_content\">Descarcă Magisk NUMAI de pe pagina oficială GitHub. Fișierele din surse necunoscute pot fi rău intenționate!</string>\n    <string name=\"home_support_title\">Sprijină-ne</string>\n    <string name=\"home_follow_title\">Urmărește-ne</string>\n    <string name=\"home_item_source\">Sursă</string>\n    <string name=\"home_support_content\">Magisk este și va fi întotdeauna gratuit și open-source. Cu toate acestea, ne poți arăta că îți pasă făcând o donație.</string>\n    <string name=\"home_installed_version\">Versiune instalată</string>\n    <string name=\"home_latest_version\">Ultima versiune</string>\n    <string name=\"invalid_update_channel\">Canal de actualizare nevalid</string>\n    <string name=\"uninstall_magisk_title\">Dezinstalează Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Toate modulele vor fi dezactivate/eliminate!\\nRootul va fi eliminat!\\nOrice spațiu de stocare intern necriptat prin folosirea Magisk va fi recriptat!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Păstrează criptarea forțată</string>\n    <string name=\"keep_dm_verity\">Păstrează AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mod de recuperare</string>\n    <string name=\"install_options_title\">Opțiuni</string>\n    <string name=\"install_method_title\">Metodă</string>\n    <string name=\"install_next\">Înainte</string>\n    <string name=\"install_start\">Să-i dăm drumul</string>\n    <string name=\"manager_download_install\">Apasă pentru a descărca și a instala</string>\n    <string name=\"direct_install\">Instalează direct (Recomandat)</string>\n    <string name=\"install_inactive_slot\">Instalează în slotul inactiv (După OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Dispozitivul va fi FORȚAT să booteze în slotul actual inactiv după repornire!\\nFolosește această opțiune numai după terminarea OTA.\\nContinui?</string>\n    <string name=\"setup_title\">Configurare suplimentară</string>\n    <string name=\"select_patch_file\">Selectează și patchuiește un fișier</string>\n    <string name=\"patch_file_msg\">Selectează o imagine brută (*.img) sau un fișier tar ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Repornire în 5 secunde…</string>\n    <string name=\"flash_screen_title\">Instalare</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Cerere de superutilizator</string>\n    <string name=\"touch_filtered_warning\">Deoarece o aplicație ascunde o cerere de superutilizator, Magisk nu îți poate verifica răspunsul</string>\n    <string name=\"deny\">Refuză</string>\n    <string name=\"prompt\">Solicită</string>\n    <string name=\"grant\">Acordă</string>\n    <string name=\"su_warning\">Acordă acces complet la dispozitivul tău.\\nRefuză dacă nu ești sigur!</string>\n    <string name=\"forever\">Pentru totdeauna</string>\n    <string name=\"once\">O singură dată</string>\n    <string name=\"tenmin\">10 minute</string>\n    <string name=\"twentymin\">20 de minute</string>\n    <string name=\"thirtymin\">30 de minute</string>\n    <string name=\"sixtymin\">60 de minute</string>\n    <string name=\"su_allow_toast\">Aplicației %1$s i-au fost acordate drepturi de superutilizator</string>\n    <string name=\"su_deny_toast\">Aplicației %1$s i-au fost refuzate drepturi de superutilizator</string>\n    <string name=\"su_snack_grant\">Drepturile de superutilizator pentru %1$s au fost acordate</string>\n    <string name=\"su_snack_deny\">Drepturile de superutilizator pentru %1$s au fost refuzate</string>\n    <string name=\"su_snack_notif_on\">Notificările pentru %1$s au fost activate</string>\n    <string name=\"su_snack_notif_off\">Notificările pentru %1$s au fost dezactivate</string>\n    <string name=\"su_snack_log_on\">Jurnalizarea pentru %1$s a fost activată</string>\n    <string name=\"su_snack_log_off\">Jurnalizarea pentru %1$s a fost dezactivată</string>\n    <string name=\"su_revoke_title\">Revoci?</string>\n    <string name=\"su_revoke_msg\">Confirmă revocarea drepturilor de superutilizator pentru %1$s</string>\n    <string name=\"toast\">Mesaj</string>\n    <string name=\"none\">Niciuna</string>\n\n    <string name=\"superuser_toggle_notification\">Notificări</string>\n    <string name=\"superuser_toggle_revoke\">Revocă</string>\n    <string name=\"superuser_policy_none\">Nicio aplicație nu a cerut încă permisiunea de superutilizator.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nu există jurnale, încearcă să folosești ceva mai mult aplicațiile care folosesc root</string>\n    <string name=\"log_data_magisk_none\">Jurnalele Magisk sunt goale, asta-i ciudat</string>\n    <string name=\"menuSaveLog\">Salvează jurnalul</string>\n    <string name=\"menuClearLog\">Golește jurnalul acum</string>\n    <string name=\"logs_cleared\">Jurnal golit cu succes</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID țintă: %1$d</string>\n    <string name=\"target_pid\">PID țintă pentru ns-ul montat: %s</string>\n    <string name=\"selinux_context\">Context SELinux: %s</string>\n    <string name=\"supp_group\">Grup suplimentar: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Afișează aplicațiile de sistem</string>\n    <string name=\"show_os_app\">Afișează aplicațiile SO-ului</string>\n    <string name=\"hide_filter_hint\">Filtrează după nume</string>\n    <string name=\"hide_search\">Căutare</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nu sunt furnizate informații)</string>\n    <string name=\"reboot_userspace\">Repornire software</string>\n    <string name=\"reboot_recovery\">Repornește în modul recovery</string>\n    <string name=\"reboot_bootloader\">Repornește în modul bootloader</string>\n    <string name=\"reboot_download\">Repornește în modul download</string>\n    <string name=\"reboot_edl\">Repornește în EDL</string>\n    <string name=\"reboot_safe_mode\">Mod sigur</string>\n    <string name=\"module_version_author\">%1$s de %2$s</string>\n    <string name=\"module_state_remove\">Elimină</string>\n    <string name=\"module_state_restore\">Restaurează</string>\n    <string name=\"module_action_install_external\">Instalează din spațiul de stocare</string>\n    <string name=\"update_available\">Actualizare disponibilă</string>\n    <string name=\"suspend_text_riru\">Modul suspendat deoarece %1$s este activat</string>\n    <string name=\"suspend_text_zygisk\">Modul suspendat deoarece %1$s nu este activat</string>\n    <string name=\"zygisk_module_unloaded\">Modulul Zygisk nu a fost încărcat din cauza incompatibilității</string>\n    <string name=\"module_empty\">Niciun modul instalat</string>\n    <string name=\"confirm_install\">Instalezi modulul %1$s?</string>\n    <string name=\"confirm_install_title\">Confirmare pentru instalare</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mod pentru temă</string>\n    <string name=\"settings_dark_mode_message\">Selectează modul care ți se pliază cel mai bine stilului!</string>\n    <string name=\"settings_dark_mode_light\">Mereu deschis</string>\n    <string name=\"settings_dark_mode_system\">Urmează modul sistemului</string>\n    <string name=\"settings_dark_mode_dark\">Mereu întunecat</string>\n    <string name=\"settings_download_path_title\">Cale de descărcare</string>\n    <string name=\"settings_download_path_message\">Fișierele vor fi salvate în %1$s</string>\n    <string name=\"settings_hide_app_title\">Ascunde aplicația Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instalează o aplicație proxy cu ID aleatoriu pentru pachet și etichetă personalizată pentru aplicație</string>\n    <string name=\"settings_restore_app_title\">Restaurează aplicația Magisk</string>\n    <string name=\"settings_restore_app_summary\">Dezvăluie aplicația și restaurează APK-ul original</string>\n    <string name=\"language\">Limbă</string>\n    <string name=\"system_default\">(Implicită a sistemului)</string>\n    <string name=\"settings_check_update_title\">Caută actualizări</string>\n    <string name=\"settings_check_update_summary\">Caută periodic actualizări în fundal</string>\n    <string name=\"settings_update_channel_title\">Canal de actualizare</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Personalizat</string>\n    <string name=\"settings_update_custom_msg\">Inserează un URL pentru canal personalizat</string>\n    <string name=\"settings_zygisk_summary\">Rulează părți ale Magisk în daemonul zygote</string>\n    <string name=\"settings_denylist_title\">Impune lista de refuzări</string>\n    <string name=\"settings_denylist_summary\">Procesele din lista de refuzări vor avea toate modificările Magisk anulate</string>\n    <string name=\"settings_denylist_config_title\">Configurează lista de refuzări</string>\n    <string name=\"settings_denylist_config_summary\">Selectează procesele care vor fi incluse în lista de refuzări</string>\n    <string name=\"settings_hosts_title\">Fișier hosts în afara partiției system</string>\n    <string name=\"settings_hosts_summary\">Suport pentru fișierul hosts în afara partiției system, în cazul aplicațiilor care blochează reclame</string>\n    <string name=\"settings_hosts_toast\">Modulul pentru fișierul hosts în afara partiției system, adăugat</string>\n    <string name=\"settings_app_name_hint\">Nume nou</string>\n    <string name=\"settings_app_name_helper\">Aplicația va fi reîmpachetată cu acest nume</string>\n    <string name=\"settings_app_name_error\">Format nevalid</string>\n    <string name=\"settings_su_app_adb\">Aplicații și ADB</string>\n    <string name=\"settings_su_app\">Doar aplicații</string>\n    <string name=\"settings_su_adb\">Doar ADB</string>\n    <string name=\"settings_su_disable\">Dezactivat</string>\n    <string name=\"settings_su_request_10\">10 secunde</string>\n    <string name=\"settings_su_request_15\">15 secunde</string>\n    <string name=\"settings_su_request_20\">20 de secunde</string>\n    <string name=\"settings_su_request_30\">30 de secunde</string>\n    <string name=\"settings_su_request_45\">45 de secunde</string>\n    <string name=\"settings_su_request_60\">60 de secunde</string>\n    <string name=\"superuser_access\">Acces pentru superutilizator</string>\n    <string name=\"auto_response\">Răspuns automat</string>\n    <string name=\"request_timeout\">Expirare pentru cerere</string>\n    <string name=\"superuser_notification\">Notificare de superutilizator</string>\n    <string name=\"settings_su_reauth_title\">Reautentificare după actualizare</string>\n    <string name=\"settings_su_reauth_summary\">Cere din nou permisiunile de superutilizator după actualizarea aplicațiilor</string>\n    <string name=\"settings_su_tapjack_title\">Protecție față de tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">Caseta de dialog pentru solicitarea permisiunilor de superutilizator nu va răspunde la input cât timp este ascunsă de orice altă fereastră sau suprapunere</string>\n    <string name=\"settings_su_auth_title\">Autentificarea utilizatorului</string>\n    <string name=\"settings_su_auth_summary\">Cere autentificarea utilizatorului în timpul cererilor de superutilizator</string>\n    <string name=\"settings_su_auth_insecure\">Nu este configurată nicio metodă de autentificare pe dispozitiv</string>\n    <string name=\"settings_customization\">Personalizare</string>\n    <string name=\"setting_add_shortcut_summary\">Adaugă o comandă rapidă frumoasă în ecranul de pornire în cazul în care numele și pictograma sunt dificil de recunoscut după ascunderea aplicației</string>\n    <string name=\"settings_doh_title\">DNS prin HTTPS</string>\n    <string name=\"settings_doh_description\">Soluție ocolitoare pentru poisoningul DNS în anumite țări</string>\n    <string name=\"settings_random_name_title\">Randomizează numele ieșirii</string>\n    <string name=\"settings_random_name_description\">Randomizează numele fișierului de ieșire a imaginilor patchuite și a fișierelor tar pentru a împiedica detectarea</string>\n\n    <string name=\"multiuser_mode\">Mod de multiutilizator</string>\n    <string name=\"settings_owner_only\">Numai proprietarul dispozitivului</string>\n    <string name=\"settings_owner_manage\">Gestionat de proprietarul dispozitivului</string>\n    <string name=\"settings_user_independent\">Utilizator independent</string>\n    <string name=\"owner_only_summary\">Numai proprietarul are acces la root</string>\n    <string name=\"owner_manage_summary\">Numai proprietarul poate să gestioneze accesul la root și să primească cereri</string>\n    <string name=\"user_independent_summary\">Fiecare utilizator are propriile sale reguli separate pentru root</string>\n\n    <string name=\"mount_namespace_mode\">Mod de montare a spațiului de nume</string>\n    <string name=\"settings_ns_global\">Spațiu de nume global</string>\n    <string name=\"settings_ns_requester\">Spațiu de nume moștenit</string>\n    <string name=\"settings_ns_isolate\">Spațiu de nume izolat</string>\n    <string name=\"global_summary\">Toate sesiunile de root folosesc spațiul de nume global</string>\n    <string name=\"requester_summary\">Sesiunile de root vor moșteni spațiul de nume al solicitantului</string>\n    <string name=\"isolate_summary\">Fiecare sesiune de root va avea propriul spațiu de nume izolat</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Actualizări Magisk</string>\n    <string name=\"progress_channel\">Notificări de progres</string>\n    <string name=\"updated_channel\">Actualizare finalizată</string>\n    <string name=\"download_complete\">Descărcare finalizată</string>\n    <string name=\"download_file_error\">Eroare la descărcarea fișierului</string>\n    <string name=\"magisk_update_title\">Actualizare Magisk disponibilă!</string>\n    <string name=\"updated_title\">Magisk actualizat</string>\n    <string name=\"updated_text\">Atinge pentru a deschide aplicația</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Da</string>\n    <string name=\"no\">Nu</string>\n    <string name=\"repo_install_title\">Instalează %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Descarcă</string>\n    <string name=\"reboot\">Repornește</string>\n    <string name=\"release_notes\">Note privind versiunea</string>\n    <string name=\"flashing\">Se scrie în memoria flash…</string>\n    <string name=\"done\">Terminat!</string>\n    <string name=\"failure\">Eșec!</string>\n    <string name=\"hide_app_title\">Se ascunde aplicația Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nu a fost găsită nicio aplicație pentru a deschide linkul</string>\n    <string name=\"complete_uninstall\">Finalizează dezinstalarea</string>\n    <string name=\"restore_img\">Restaurează imagini</string>\n    <string name=\"restore_img_msg\">Se restaurează…</string>\n    <string name=\"restore_done\">Restaurare terminată!</string>\n    <string name=\"restore_fail\">Nu există backup stock!</string>\n    <string name=\"setup_fail\">Configurare eșuată</string>\n    <string name=\"env_fix_title\">Necesită configurare suplimentară</string>\n    <string name=\"env_fix_msg\">Dispozitivul are nevoie de configurare suplimentară pentru ca Magisk să funcționeze corect. Vrei să continui și să repornești?</string>\n    <string name=\"env_full_fix_msg\">Dispozitivul are nevoie de rescrierea în memoria flash a Magisk pentru a funcționa corect. Te rugăm să reinstalezi Magisk în cadrul aplicației, modul de recuperare nu poate obține informații corecte despre dispozitiv.</string>\n    <string name=\"setup_msg\">Rulează configurarea mediului…</string>\n    <string name=\"unsupport_magisk_title\">Versiune Magisk nesuportată</string>\n    <string name=\"unsupport_magisk_msg\">Această versiune a aplicației nu suportă versiunile Magisk mai mici de %1$s.\\n\\nAplicația se va comporta ca și cum Magisk nu este instalat, te rugăm să actualizezi Magisk cât mai curând posibil.</string>\n    <string name=\"unsupport_general_title\">Stare anormală</string>\n    <string name=\"unsupport_system_app_msg\">Rularea acestei aplicații ca aplicație de sistem nu este suportată. Te rugăm să readuci aplicația într-o aplicație de utilizator.</string>\n    <string name=\"unsupport_other_su_msg\">A fost detectat un binar „su” care nu provine de la Magisk. Te rugăm să elimini orice soluție root concurentă și/sau să reinstalezi Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk este instalat în spațiul de stocare extern. Te rugăm să muți aplicația în spațiul de stocare intern.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Aplicația Magisk ascunsă nu poate continua să funcționeze deoarece rootul a fost pierdut. Te rugăm să restaurezi APK-ul original.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Acordă permisiunea de stocare pentru a activa această funcționalitate</string>\n    <string name=\"post_notifications_denied\">Acordă permisiunea de notificări pentru a activa această funcționalitate</string>\n    <string name=\"install_unknown_denied\">Permite „instalarea de aplicații necunoscute” pentru a activa această funcționalitate</string>\n    <string name=\"add_shortcut_title\">Adaugă comandă rapidă pe ecranul de pornire</string>\n    <string name=\"add_shortcut_msg\">După ascunderea acestei aplicații, numele și pictograma acesteia ar putea deveni dificil de recunoscut. Vrei să adaugi o comandă rapidă frumoasă pe ecranul de pornire?</string>\n    <string name=\"app_not_found\">Nu s-a găsit nicio aplicație care să gestioneze această acțiune</string>\n    <string name=\"reboot_apply_change\">Repornește pentru a aplica modificările</string>\n    <string name=\"restore_app_confirmation\">Acest lucru va restaura aplicația ascunsă în aplicația originală. Sigur vrei să faci asta?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ru/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Модули</string>\n    <string name=\"superuser\">Superuser</string> <!--Do not translate! #6016-->\n    <string name=\"logs\">Логи</string>\n    <string name=\"settings\">Настройки</string>\n    <string name=\"install\">Установка</string>\n    <string name=\"section_home\">Главная</string>\n    <string name=\"section_theme\">Темы</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Нет подключения к сети</string>\n    <string name=\"app_changelog\">Изменения</string>\n    <string name=\"loading\">Загрузка…</string>\n    <string name=\"update\">Обновить</string>\n    <string name=\"not_available\">Не установлен</string>\n    <string name=\"hide\">Скрыть</string>\n    <string name=\"home_package\">Имя пакета</string>\n    <string name=\"home_app_title\">Приложение</string>\n\n    <string name=\"home_notice_content\">Скачивайте Magisk только из официального репозитория на GitHub. Файлы из других источников могут содержать вредоносный код!</string>\n    <string name=\"home_support_title\">Поддержите нас</string>\n    <string name=\"home_follow_title\">Подпишитесь</string>\n    <string name=\"home_item_source\">Исходный код</string>\n    <string name=\"home_support_content\">Magisk был и всегда будет бесплатным и открытым проектом. Однако Вы всегда можете поддержать нас, отправив небольшое пожертвование.</string>\n    <string name=\"home_installed_version\">Текущая версия</string>\n    <string name=\"home_latest_version\">Последняя версия</string>\n    <string name=\"invalid_update_channel\">Некорректный канал обновлений</string>\n    <string name=\"uninstall_magisk_title\">Удаление Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Все модули будут отключены либо удалены!\\nRoot-доступ будет недоступен!\\nБудет активировано шифрование /data (если отключалось при установке)!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Не отключать шифрование /data</string>\n    <string name=\"keep_dm_verity\">Не отключать AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Патчить образ recovery вместо boot</string>\n    <string name=\"install_options_title\">Опции</string>\n    <string name=\"install_method_title\">Способ</string>\n    <string name=\"install_next\">Далее</string>\n    <string name=\"install_start\">Установить</string>\n    <string name=\"manager_download_install\">Нажмите для установки</string>\n    <string name=\"direct_install\">Автоматическая установка (рекомендуется)</string>\n    <string name=\"install_inactive_slot\">Установка во второй слот (после OTA-обновления)</string>\n    <string name=\"install_inactive_slot_msg\">Ваше устройство будет принудительно перезагружено в неактивный (противоположный) слот!\\nИспользуйте эту опцию только при интеграции Magisk после OTA-обновления!\\nПродолжить?</string>\n    <string name=\"setup_title\">Расширенная установка</string>\n    <string name=\"select_patch_file\">Пропатчить boot-образ</string>\n    <string name=\"patch_file_msg\">Выберите файл образа (*.img) или архив ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Перезагрузка через 5 секунд…</string>\n    <string name=\"flash_screen_title\">Установка</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Запрос прав суперпользователя</string>\n    <string name=\"touch_filtered_warning\">Одно из приложений отображается поверх окна Magisk, выдача Root-прав недоступна</string>\n    <string name=\"deny\">Запретить</string>\n    <string name=\"prompt\">Запросить</string>\n    <string name=\"grant\">Разрешить</string>\n    <string name=\"su_warning\">Разрешить полный доступ к устройству?\\nЕсли не уверены - отклоните данный запрос!</string>\n    <string name=\"forever\">Навсегда</string>\n    <string name=\"once\">Единожды</string>\n    <string name=\"tenmin\">10 мин.</string>\n    <string name=\"twentymin\">20 мин.</string>\n    <string name=\"thirtymin\">30 мин.</string>\n    <string name=\"sixtymin\">60 мин.</string>\n    <string name=\"su_allow_toast\">%1$s предоставлены права суперпользователя</string>\n    <string name=\"su_deny_toast\">%1$s отказано в правах суперпользователя</string>\n    <string name=\"su_snack_grant\">%1$s предоставлены права суперпользователя</string>\n    <string name=\"su_snack_deny\">%1$s отказано в правах суперпользователя</string>\n    <string name=\"su_snack_notif_on\">Уведомления для %1$s включены</string>\n    <string name=\"su_snack_notif_off\">Уведомления для %1$s отключены</string>\n    <string name=\"su_snack_log_on\">Логирование для %1$s включено</string>\n    <string name=\"su_snack_log_off\">Логирование для %1$s отключено</string>\n    <string name=\"su_revoke_title\">Сброс настроек прав</string>\n    <string name=\"su_revoke_msg\">Сбросить настройки для %1$s?</string>\n    <string name=\"toast\">Всплывающие уведомления</string>\n    <string name=\"none\">Отключены</string>\n\n    <string name=\"superuser_toggle_notification\">Уведомления</string>\n    <string name=\"superuser_toggle_revoke\">Отозвать</string>\n    <string name=\"superuser_policy_none\">Приложения ещё не запрашивали права суперпользователя</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Логи отсутствуют</string>\n    <string name=\"log_data_magisk_none\">Логи отсутствуют</string>\n    <string name=\"menuSaveLog\">Сохранить логи</string>\n    <string name=\"menuClearLog\">Очистить логи</string>\n    <string name=\"logs_cleared\">Логи успешно очищены</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Целевой UID: %1$d</string>\n    <string name=\"target_pid\">Целевой PID пространства имён: %s</string>\n    <string name=\"selinux_context\">Контекст SELinux: %s</string>\n    <string name=\"supp_group\">Дополнительная группа: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Системные приложения</string>\n    <string name=\"show_os_app\">Встроенные приложения</string>\n    <string name=\"hide_filter_hint\">Введите имя</string>\n    <string name=\"hide_search\">Поиск</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Нет информации)</string>\n    <string name=\"reboot_userspace\">Перезапуск оболочки</string>\n    <string name=\"reboot_recovery\">Перезагрузка в Recovery</string>\n    <string name=\"reboot_bootloader\">Перезагрузка в Bootloader</string>\n    <string name=\"reboot_download\">Перезагрузка в Download</string>\n    <string name=\"reboot_edl\">Перезагрузка в EDL</string>\n    <string name=\"module_version_author\">%1$s от %2$s</string>\n    <string name=\"module_state_remove\">Удалить</string>\n    <string name=\"module_state_restore\">Восстановить</string>\n    <string name=\"module_action_install_external\">Установить из хранилища</string>\n    <string name=\"update_available\">Доступно обновление</string>\n    <string name=\"suspend_text_riru\">Модуль отключён, поскольку активирован %1$s</string>\n    <string name=\"suspend_text_zygisk\">Модуль отключён, поскольку %1$s не активирован</string>\n    <string name=\"zygisk_module_unloaded\">Модуль Zygisk не запущен из-за несовместимости</string>\n    <string name=\"module_empty\">Модули не установлены</string>\n    <string name=\"confirm_install\">Установить модуль %1$s?</string>\n    <string name=\"confirm_install_title\">Подтверждение установки</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Оттенок</string>\n    <string name=\"settings_dark_mode_message\">Настройте оформление под себя!</string>\n    <string name=\"settings_dark_mode_light\">Всегда светлый</string>\n    <string name=\"settings_dark_mode_system\">Как в системе</string>\n    <string name=\"settings_dark_mode_dark\">Всегда тёмный</string>\n    <string name=\"settings_download_path_title\">Папка для загрузок</string>\n    <string name=\"settings_download_path_message\">Файлы будут загружаться в %1$s</string>\n    <string name=\"settings_hide_app_title\">Скрытие приложения Magisk</string>\n    <string name=\"settings_hide_app_summary\">Пересобрать приложение Magisk с другим названием и случайным именем пакета</string>\n    <string name=\"settings_restore_app_title\">Восстановление приложения Magisk</string>\n    <string name=\"settings_restore_app_summary\">Восстановить приложение Magisk к исходному состоянию</string>\n    <string name=\"language\">Язык</string>\n    <string name=\"system_default\">По умолчанию</string>\n    <string name=\"settings_check_update_title\">Проверка обновлений</string>\n    <string name=\"settings_check_update_summary\">Периодически проверять наличие обновлений в фоновом режиме</string>\n    <string name=\"settings_update_channel_title\">Источник обновлений</string>\n    <string name=\"settings_update_stable\">Стабильный канал</string>\n    <string name=\"settings_update_beta\">Beta канал</string>\n    <string name=\"settings_update_custom\">Сторонний канал</string>\n    <string name=\"settings_update_custom_msg\">Укажите ссылку</string>\n    <string name=\"settings_zygisk_summary\">Запускать компоненты Magisk в zygote процессе</string>\n    <string name=\"settings_denylist_title\">Активировать DenyList</string>\n    <string name=\"settings_denylist_summary\">Все изменения, внесённые Magisk-ом, будут скрыты от процессов, отмеченных в DenyList</string>\n    <string name=\"settings_denylist_config_title\">Настройка DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Отметьте процессы для скрытия в DenyList</string>\n    <string name=\"settings_hosts_title\">Внесистемный hosts файл</string>\n    <string name=\"settings_hosts_summary\">Поддержка внесистемного hosts файла для приложений, блокирующих рекламу</string>\n    <string name=\"settings_hosts_toast\">Модуль для внесистемного hosts файла установлен</string>\n    <string name=\"settings_app_name_hint\">Введите имя</string>\n    <string name=\"settings_app_name_helper\">Приложение будет пересобрано с этим именем</string>\n    <string name=\"settings_app_name_error\">Некорректный формат</string>\n    <string name=\"settings_su_app_adb\">Приложения и ADB</string>\n    <string name=\"settings_su_app\">Только приложения</string>\n    <string name=\"settings_su_adb\">Только ADB</string>\n    <string name=\"settings_su_disable\">Отключён</string>\n    <string name=\"settings_su_request_10\">10 секунд</string>\n    <string name=\"settings_su_request_15\">15 секунд</string>\n    <string name=\"settings_su_request_20\">20 секунд</string>\n    <string name=\"settings_su_request_30\">30 секунд</string>\n    <string name=\"settings_su_request_45\">45 секунд</string>\n    <string name=\"settings_su_request_60\">60 секунд</string>\n    <string name=\"superuser_access\">Уровень доступа</string>\n    <string name=\"auto_response\">Автоматический ответ</string>\n    <string name=\"request_timeout\">Ожидание ответа</string>\n    <string name=\"superuser_notification\">Уведомления суперпользователя</string>\n    <string name=\"settings_su_reauth_title\">Повторная аутентификация</string>\n    <string name=\"settings_su_reauth_summary\">Повторный запрос прав суперпользователя после обновления приложений</string>\n    <string name=\"settings_su_tapjack_title\">Защита от перехвата нажатий</string>\n    <string name=\"settings_su_auth_title\">Аутентификация пользователя</string>\n    <string name=\"settings_su_auth_summary\">Требовать аутентификацию пользователя при запросах Superuser</string>\n    <string name=\"settings_su_auth_insecure\">На устройстве не настроен метод аутентификации</string>\n    <string name=\"settings_su_tapjack_summary\">Окно запроса прав суперпользователя будет неактивно пока активированы наложения экрана</string>\n    <string name=\"settings_customization\">Персонализация</string>\n    <string name=\"setting_add_shortcut_summary\">Добавить ярлык на рабочий стол для удобного восприятия приложения после его скрытия</string>\n    <string name=\"settings_doh_title\">DNS поверх HTTPS</string>\n    <string name=\"settings_doh_description\">Активировать DoH (используйте при проблемах с подключением к сети)</string>\n    <string name=\"settings_random_name_title\">Случайное имя образа</string>\n    <string name=\"settings_random_name_description\">Генерировать случайные имена для патченных образов и tar-файлов для предотвращения обнаружения</string>\n\n    <string name=\"multiuser_mode\">Многопользовательский режим</string>\n    <string name=\"settings_owner_only\">Только администратор</string>\n    <string name=\"settings_owner_manage\">Управление администратором</string>\n    <string name=\"settings_user_independent\">Правила пользователей</string>\n    <string name=\"owner_only_summary\">Только администратору доступен Root-доступ</string>\n    <string name=\"owner_manage_summary\">Администратор управляет Root-доступом пользователей и обрабатывает запросы</string>\n    <string name=\"user_independent_summary\">Каждый пользователь управляет собственными правилами Root-доступа</string>\n\n    <string name=\"mount_namespace_mode\">Настройка пространств имён</string>\n    <string name=\"settings_ns_global\">Общее пространство имён</string>\n    <string name=\"settings_ns_requester\">Наследуемое пространство имён</string>\n    <string name=\"settings_ns_isolate\">Изолированное пространство имён</string>\n    <string name=\"global_summary\">Сессии суперпользователя используют общее пространство имён</string>\n    <string name=\"requester_summary\">Сессии суперпользователя наследуют пространство имён запрашивающего</string>\n    <string name=\"isolate_summary\">Сессии суперпользователя используют изолированные пространства имён</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Обновление Magisk</string>\n    <string name=\"progress_channel\">Уведомления о прогрессе</string>\n    <string name=\"updated_channel\">Обновление завершено</string>\n    <string name=\"download_complete\">Загрузка завершена</string>\n    <string name=\"download_file_error\">Ошибка загрузки файла</string>\n    <string name=\"magisk_update_title\">Доступно обновление Magisk!</string>\n    <string name=\"updated_title\">Magisk обновлён</string>\n    <string name=\"updated_text\">Нажмите, чтобы открыть</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Да</string>\n    <string name=\"no\">Нет</string>\n    <string name=\"repo_install_title\">Установка %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Скачать</string>\n    <string name=\"reboot\">Перезагрузка</string>\n    <string name=\"release_notes\">О версии</string>\n    <string name=\"flashing\">Установка…</string>\n    <string name=\"done\">Завершено!</string>\n    <string name=\"failure\">Ошибка!</string>\n    <string name=\"hide_app_title\">Скрытие приложения Magisk…</string>\n    <string name=\"open_link_failed_toast\">Приложения для открытия ссылки не найдены</string>\n    <string name=\"complete_uninstall\">Полное удаление</string>\n    <string name=\"restore_img\">Восстановить разделы</string>\n    <string name=\"restore_img_msg\">Восстановление…</string>\n    <string name=\"restore_done\">Восстановление завершено!</string>\n    <string name=\"restore_fail\">Резервная копия отсутствует!</string>\n    <string name=\"setup_fail\">Ошибка установки</string>\n    <string name=\"env_fix_title\">Требуется расширенная установка</string>\n    <string name=\"env_fix_msg\">Требуется расширенная установка Magisk для корректной работы. Продолжить и выполнить перезагрузку?</string>\n    <string name=\"env_full_fix_msg\">Необходимо переустановить Magisk для корректной работы на вашем устройстве. Пожалуйста, переустановите Magisk в самом приложении (не из recovery).</string>\n    <string name=\"setup_msg\">Настройка рабочей среды…</string>\n    <string name=\"unsupport_magisk_title\">Неподдерживаемая версия Magisk</string>\n    <string name=\"unsupport_magisk_msg\">Эта версия приложения Magisk не поддерживает версию Magisk ниже %1$s.\\n\\nПриложение будет работать так, словно Magisk не установлен, пожалуйста, обновите Magisk как можно скорее.</string>\n    <string name=\"unsupport_general_title\">Неподдерживаемые условия</string>\n    <string name=\"unsupport_system_app_msg\">Запуск приложения в качестве системного невозможен. Пожалуйста, переустановите его как пользовательское.</string>\n    <string name=\"unsupport_other_su_msg\">Обнаружен неподдерживаемый бинарный файл \\\"su\\\". Пожалуйста, удалите сторонний провайдер Root-прав и/или переустановите Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Приложение установлено во внешнее хранилище. Пожалуйста, переустановите его во внутреннее хранилище.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Пересобранное для скрытия приложение Magisk не может дальше работать, поскольку Root-права недоступны. Пожалуйста, восстановите приложение к исходному состоянию.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Предоставьте разрешение на доступ к хранилищу</string>\n    <string name=\"post_notifications_denied\">Предоставьте разрешение на отправку уведомлений</string>\n    <string name=\"install_unknown_denied\">Предоставьте разрешение на \"Установку из неизвестных источников\"</string>\n    <string name=\"add_shortcut_title\">Добавление ярлыка</string>\n    <string name=\"add_shortcut_msg\">После скрытия приложения Magisk его название и иконка могут быть неудобны для восприятия. Хотите создать ярлык на рабочем столе?</string>\n    <string name=\"app_not_found\">Приложение для обработки этого действия не найдено</string>\n    <string name=\"reboot_apply_change\">Перезагрузите устройство для применения изменений</string>\n    <string name=\"restore_app_confirmation\">Это действие восстановит пересобранное для скрытия приложение к исходному состоянию. Вы действительно хотите продолжить?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-sk/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduly</string>\n    <string name=\"superuser\">Superužívateľ</string>\n    <string name=\"logs\">Záznamy</string>\n    <string name=\"settings\">Nastavenia</string>\n    <string name=\"install\">Inštalovať</string>\n    <string name=\"section_home\">Domov</string>\n    <string name=\"section_theme\">Motívy</string>\n    <string name=\"denylist\">Zoznam zamietnutých</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Žiadne pripojenie</string>\n    <string name=\"app_changelog\">Zoznam zmien</string>\n    <string name=\"loading\">Načítava sa…</string>\n    <string name=\"update\">Aktualizovať</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Skryť</string>\n    <string name=\"home_package\">Balíček</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">Magisk sťahujte IBA z oficiálnej stránky na GitHube. Súbory z neznámych zdrojov môžu byť škodlivé!</string>\n    <string name=\"home_support_title\">Podporte nás</string>\n    <string name=\"home_follow_title\">Sledujte nás</string>\n    <string name=\"home_item_source\">Zdroj</string>\n    <string name=\"home_support_content\">Magisk je a vždy bude slobodný a s otvoreným kódom. Môžete nám však dať najavo, že vám na nás záleží, tým, že nám prispejete.</string>\n    <string name=\"home_installed_version\">Nainštalované</string>\n    <string name=\"home_latest_version\">Posledná</string>\n    <string name=\"invalid_update_channel\">Neplatný aktualizačný kanál</string>\n    <string name=\"uninstall_magisk_title\">Odinštalovať Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Všetky moduly budú zakázané/odstránené!\\nRoot bude odstránený!\\nAkékoľvek interné úložisko nešifrované pomocou Magisku bude znovu zašifrované!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Ponechať vynútené šifrovanie</string>\n    <string name=\"keep_dm_verity\">Ponechať AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Režim Recovery</string>\n    <string name=\"install_options_title\">Možnosti</string>\n    <string name=\"install_method_title\">Metóda</string>\n    <string name=\"install_next\">Ďalej</string>\n    <string name=\"install_start\">Poďme na to</string>\n    <string name=\"manager_download_install\">Stlačte pre stiahnutie a inštaláciu</string>\n    <string name=\"direct_install\">Priama inštalácia (Odporúča sa)</string>\n    <string name=\"install_inactive_slot\">Inštalovať na neaktívny slot (Po OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Vaše zariadenie bude po reštarte PRINÚTENÉ nabootovať do aktuálne neaktívneho slotu!\\nTúto voľbu použite iba po skončení OTA.\\nPokračovať?</string>\n    <string name=\"setup_title\">Ďalšie nastavenia</string>\n    <string name=\"select_patch_file\">Vybrať a zaplátať súbor</string>\n    <string name=\"patch_file_msg\">Vyberte raw súbor (*.img) alebo tar súbor ODIN (*.tar) alebo payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Reštart o 5 sekúnd…</string>\n    <string name=\"flash_screen_title\">Inštalácia</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Požiadavka superužívateľa</string>\n    <string name=\"touch_filtered_warning\">Pretože aplikácia zakrýva požiadavku superužívateľa, Magisk nemôže overiť vašu odpoveď</string>\n    <string name=\"deny\">Zamietnuť</string>\n    <string name=\"prompt\">Výzva</string>\n    <string name=\"grant\">Udeliť</string>\n    <string name=\"su_warning\">Udelí úplný prístup k vášmu zariadeniu.\\nZamietnite, ak si nie ste istý!</string>\n    <string name=\"forever\">Navždy</string>\n    <string name=\"once\">Raz</string>\n    <string name=\"tenmin\">10 min</string>\n    <string name=\"twentymin\">20 min</string>\n    <string name=\"thirtymin\">30 min</string>\n    <string name=\"sixtymin\">60 min</string>\n    <string name=\"su_allow_toast\">%1$s boli udelené práva Superužívateľa</string>\n    <string name=\"su_deny_toast\">%1$s boli zamietnuté práva Superužívateľa</string>\n    <string name=\"su_snack_grant\">Práva Superužívateľa pre %1$s sú udelené</string>\n    <string name=\"su_snack_deny\">Práva Superužívateľa pre %1$s sú zamietnuté</string>\n    <string name=\"su_snack_notif_on\">Upozornenia pre %1$s sú povolené</string>\n    <string name=\"su_snack_notif_off\">Upozornenia pre %1$s sú zakázané</string>\n    <string name=\"su_snack_log_on\">Zaznamenávanie %1$s je povolené</string>\n    <string name=\"su_snack_log_off\">Zaznamenávanie %1$s je zakázané</string>\n    <string name=\"su_revoke_title\">Zrušiť?</string>\n    <string name=\"su_revoke_msg\">Potvrdzujete zrušenie práv roota %1$s?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Nič</string>\n\n    <string name=\"superuser_toggle_notification\">Upozornenia</string>\n    <string name=\"superuser_toggle_revoke\">Zrušiť</string>\n    <string name=\"superuser_policy_none\">Žiadna aplikácia zatiaľ nepožiadala o povolenie Superužívateľa.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nie sú žiadne záznamy, skúste nejakú appku vyžadujúcu práva superpoužívateľa</string>\n    <string name=\"log_data_magisk_none\">Záznamy Magisku sú prázdne, to je divné</string>\n    <string name=\"menuSaveLog\">Uložiť záznam</string>\n    <string name=\"menuClearLog\">Odstrániť záznam</string>\n    <string name=\"logs_cleared\">Záznam úspešne odstránený</string>\n    <string name=\"pid\">PID:%1$d</string>\n    <string name=\"target_uid\">Cieľové UID: %1$d</string>\n    <string name=\"target_pid\">Pripojiť ns cieľový PID: %s</string>\n    <string name=\"selinux_context\">SELinux kontext: %s</string>\n    <string name=\"supp_group\">Doplnková skupina: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Zobraziť systémové aplikácie</string>\n    <string name=\"show_os_app\">Zobraziť aplikácie OS</string>\n    <string name=\"hide_filter_hint\">Filtrovať podľa názvu</string>\n    <string name=\"hide_search\">Vyhľadávanie</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nie sú k dispozícii žiadne informácie)</string>\n    <string name=\"reboot_userspace\">Softvérový reštart</string>\n    <string name=\"reboot_recovery\">Reštartovať do Recovery</string>\n    <string name=\"reboot_bootloader\">Reštartovať do Bootloader</string>\n    <string name=\"reboot_download\">Reštartovať do Download</string>\n    <string name=\"reboot_edl\">Reštartovať do EDL</string>\n    <string name=\"reboot_safe_mode\">Núdzový režim</string>\n    <string name=\"module_version_author\">%1$s od %2$s</string>\n    <string name=\"module_state_remove\">Odstrániť</string>\n    <string name=\"module_state_restore\">Obnoviť</string>\n    <string name=\"module_action_install_external\">Inštalácia z úložiska</string>\n    <string name=\"update_available\">Dostupná aktualizácia</string>\n    <string name=\"suspend_text_riru\">Modul bol pozastavený, lebo %1$s je povolené</string>\n    <string name=\"suspend_text_zygisk\">Modul bol pozastavený, lebo %1$s nie je povolené</string>\n    <string name=\"zygisk_module_unloaded\">Modul Zygisk sa nenačítal z dôvodu nekompatibility</string>\n    <string name=\"module_empty\">Nie je nainštalovaný žiadny modul</string>\n    <string name=\"confirm_install\">Nainštalovať modul %1$s?</string>\n    <string name=\"confirm_install_title\">Potvrdenie inštalácie</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Režim motívu</string>\n    <string name=\"settings_dark_mode_message\">Vyberte režim, ktorý najlepšie vyhovuje vášmu štýlu!</string>\n    <string name=\"settings_dark_mode_light\">Vždy svetlý</string>\n    <string name=\"settings_dark_mode_system\">Podľa systému</string>\n    <string name=\"settings_dark_mode_dark\">Vždy tmavý</string>\n    <string name=\"settings_download_path_title\">Cesta na sťahovanie</string>\n    <string name=\"settings_download_path_message\">Súbory budú uložené do %1$s</string>\n    <string name=\"settings_hide_app_title\">Skryť aplikáciu Magisk</string>\n    <string name=\"settings_hide_app_summary\">Inštalovať proxy aplikáciu s náhodným ID balíčka a vlastným názvom aplikácie</string>\n    <string name=\"settings_restore_app_title\">Obnoviť aplikáciu Magisk</string>\n    <string name=\"settings_restore_app_summary\">Odkryť aplikáciu a obnoviť pôvodný súbor APK</string>\n    <string name=\"language\">Jazyk</string>\n    <string name=\"system_default\">(Predvolený systémom)</string>\n    <string name=\"settings_check_update_title\">Kontrolovať aktualizácie</string>\n    <string name=\"settings_check_update_summary\">Aktualizácie sa budú pravidelne kontrolovať na pozadí</string>\n    <string name=\"settings_update_channel_title\">Aktualizačný kanál</string>\n    <string name=\"settings_update_stable\">Stabilný</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Vlastný kanál</string>\n    <string name=\"settings_update_custom_msg\">Zadajte vlastnú URL</string>\n    <string name=\"settings_zygisk_summary\">Spustiť časti Magisku v démonovi zygote</string>\n    <string name=\"settings_denylist_title\">Vynútiť zoznam zamietnutých</string>\n    <string name=\"settings_denylist_summary\">Pre procesy na zozname zamietnutých budú všetky úpravy Magiskom vrátené</string>\n    <string name=\"settings_denylist_config_title\">Nastavenie zoznamu zamietnutých</string>\n    <string name=\"settings_denylist_config_summary\">Vyberte procesy, ktoré majú byť zahrnuté do zoznamu blokovaných</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Podpora pre aplikácie na blokovanie reklamy so systemless hosts</string>\n    <string name=\"settings_hosts_toast\">Pridaný modul systemless hosts</string>\n    <string name=\"settings_app_name_hint\">Nový názov</string>\n    <string name=\"settings_app_name_helper\">Aplikácia bude prebalená s týmto názvom</string>\n    <string name=\"settings_app_name_error\">Neplatný formát</string>\n    <string name=\"settings_su_app_adb\">Aplikácie a ADB</string>\n    <string name=\"settings_su_app\">Iba aplikácie</string>\n    <string name=\"settings_su_adb\">Iba ADB</string>\n    <string name=\"settings_su_disable\">Zakázané</string>\n    <string name=\"settings_su_request_10\">10 sekúnd</string>\n    <string name=\"settings_su_request_15\">15 sekúnd</string>\n    <string name=\"settings_su_request_20\">20 sekúnd</string>\n    <string name=\"settings_su_request_30\">30 sekúnd</string>\n    <string name=\"settings_su_request_45\">45 sekúnd</string>\n    <string name=\"settings_su_request_60\">60 sekúnd</string>\n    <string name=\"superuser_access\">Prístup Superužívateľa</string>\n    <string name=\"auto_response\">Automatická odpoveď</string>\n    <string name=\"request_timeout\">Časový limit na odpoveď</string>\n    <string name=\"superuser_notification\">Upozornenia Superužívateľa</string>\n    <string name=\"settings_su_reauth_title\">Overenie autentifikácie po upgrade</string>\n    <string name=\"settings_su_reauth_summary\">Opätovne vyzve na udelenie oprávnení Superužívateľa po upgrade aplikácie</string>\n    <string name=\"settings_su_tapjack_title\">Ochrana pred tapjackingom</string>\n    <string name=\"settings_su_tapjack_summary\">Dialógové okno superužívateľa nebude reagovať na zadanie, ak je zakryté alebo prekryté iným oknom</string>\n    <string name=\"settings_su_auth_title\">Overenie používateľa</string>\n    <string name=\"settings_su_auth_summary\">Žiadosť o overenie používateľa počas požiadaviek Superužívateľa</string>\n    <string name=\"settings_su_auth_insecure\">V zariadení nie je nakonfigurovaná žiadna metóda overovania</string>\n    <string name=\"settings_customization\">Prispôsobenie</string>\n    <string name=\"setting_add_shortcut_summary\">V prípade, že sa po skrytí apky názov a ikona ťažko rozpoznávajú, pridať na domovskú obrazovku odkaz</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Riešenie otravy DNS v niektorých krajinách</string>\n    <string name=\"settings_random_name_title\">Náhodný názov výstupu</string>\n    <string name=\"settings_random_name_description\">Na zabránenie detekcie sa vygeneruje náhodný názov výstupných zaplátaných obrazov a súborov tar</string>\n\n    <string name=\"multiuser_mode\">Režim viacerých používateľov</string>\n    <string name=\"settings_owner_only\">Iba majiteľ zariadenia</string>\n    <string name=\"settings_owner_manage\">Spravuje majiteľ zariadenia</string>\n    <string name=\"settings_user_independent\">Nezávisí od používateľa</string>\n    <string name=\"owner_only_summary\">Root prístup má iba majiteľ zariadenia</string>\n    <string name=\"owner_manage_summary\">Iba majiteľ zariadenia môže spravovať prístup root a prijímať žiadosti</string>\n    <string name=\"user_independent_summary\">Každý používateľ má vlastné pravidlá pre root</string>\n\n    <string name=\"mount_namespace_mode\">Režim Mount namespace</string>\n    <string name=\"settings_ns_global\">Globálny namespace</string>\n    <string name=\"settings_ns_requester\">Zdedený namespace</string>\n    <string name=\"settings_ns_isolate\">Izolovaný namespace</string>\n    <string name=\"global_summary\">Všetky relácie root použijú globálny mount namespace</string>\n    <string name=\"requester_summary\">Relácie root zdedia namespace od žiadateľa</string>\n    <string name=\"isolate_summary\">Každá relácia root bude mať vlastný izolovaný namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Aktualizácie Magisku</string>\n    <string name=\"progress_channel\">Upozornenia o priebehu</string>\n    <string name=\"updated_channel\">Aktualizácia dokončená</string>\n    <string name=\"download_complete\">Sťahovanie ukončené</string>\n    <string name=\"download_file_error\">Chyba sťahovania súboru</string>\n    <string name=\"magisk_update_title\">Je dostupná aktualizácia Magisku!</string>\n    <string name=\"updated_title\">Magisk aktualizovaný</string>\n    <string name=\"updated_text\">Ťuknutím otvoríte apku</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Áno</string>\n    <string name=\"no\">Nie</string>\n    <string name=\"repo_install_title\">Nainštalovať %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Stiahnuť</string>\n    <string name=\"reboot\">Reštartovať</string>\n    <string name=\"release_notes\">Poznámky k vydaniu</string>\n    <string name=\"flashing\">Flashovanie...</string>\n    <string name=\"done\">Hotovo!</string>\n    <string name=\"failure\">Zlyhalo!</string>\n    <string name=\"hide_app_title\">Skrýva sa aplikácia Magisk…</string>\n    <string name=\"open_link_failed_toast\">Nepodarilo sa nájsť vhodnú aplikáciu na otvorenie odkazu</string>\n    <string name=\"complete_uninstall\">Úplne odinštalovať</string>\n    <string name=\"restore_img\">Obnoviť obrazy</string>\n    <string name=\"restore_img_msg\">Obnovovanie…</string>\n    <string name=\"restore_done\">Obnovovanie ukončené!</string>\n    <string name=\"restore_fail\">Stock záloha neexistuje!</string>\n    <string name=\"setup_fail\">Nastavenie zlyhalo</string>\n    <string name=\"env_fix_title\">Vyžaduje sa ďalšie nastavenie</string>\n    <string name=\"env_fix_msg\">Aby Magisk fungoval správne, vaše zariadenie potrebuje ďalšie nastavenie. Chcete pokračovať a reštartovať?</string>\n    <string name=\"env_full_fix_msg\">Vaše zariadenie potrebuje na správne fungovanie reflash Magisku. Preinštalujte Magisk priamo z aplikácie, režim obnovenia nemôže získať správne informácie o zariadení.</string>\n    <string name=\"setup_msg\">Nastavenie je spustené…</string>\n    <string name=\"unsupport_magisk_title\">Nepodporovaná verzia Magisku</string>\n    <string name=\"unsupport_magisk_msg\">Táto verzia aplikácie nepodporuje Magisk s verziou nižšou ako %1$s.\\n\\nAplikácia sa bude správať, akoby Magisk nebol nainštalový, aktualizujte Magisk čo najskôr.</string>\n    <string name=\"unsupport_general_title\">Abnormálny stav</string>\n    <string name=\"unsupport_system_app_msg\">Spustenie tejto apky ako systémovej nie je podporované. Preveďte apku na používateľskú.</string>\n    <string name=\"unsupport_other_su_msg\">Bol nájdený binárny súbor \\\"su\\\", ktorý nie je pre Magisk. Odstráňte, prosím, konkurenčné root spôsoby a/alebo preinštalujte Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk je nainštalovaný na externom úložisku. Prosím, presuňte apku na interné úložisko.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Skrytá aplikácia Magisk nemôže pokračovať v práci, lebo root sa stratil. Prosím, obnovte ju z pôvodného súboru APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Udeľte povolenie na zapnutie tejto funkcie</string>\n    <string name=\"post_notifications_denied\">Udeľte upozorneniam povolenie na zapnutie tejto funkcie</string>\n    <string name=\"install_unknown_denied\">Povoľte \"inštaláciu neznámych aplikácii\" na povolenie tejto funkcie</string>\n    <string name=\"add_shortcut_title\">Pridať odkaz na domovskú obrazovku</string>\n    <string name=\"add_shortcut_msg\">Po skrytí tejto aplikácie sa jej názov a ikona môžu stať ťažko rozoznateľnými. Chcete pridať krajší odkaz na domovskú obrazovku?</string>\n    <string name=\"app_not_found\">Nenašla sa žiadna aplikácia, ktorá dokáže spracovať túto akciu</string>\n    <string name=\"reboot_apply_change\">Reštartovať počítač, aby sa použili zmeny</string>\n    <string name=\"restore_app_confirmation\">Týmto obnovíte skrytú aplikáciu späť na pôvodnú aplikáciu. Naozaj to chcete urobiť?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-sq/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modulet</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Regjistrimet</string>\n    <string name=\"settings\">Parametrat</string>\n    <string name=\"install\">Instalo</string>\n    <string name=\"section_home\">Shtëpia</string>\n    <string name=\"section_theme\">Temat</string>\n    <string name=\"denylist\">Lista e ndaluar</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Nuk ka lidhje të disponueshme</string>\n    <string name=\"app_changelog\">Shënimet e ndryshimeve</string>\n    <string name=\"loading\">Duke u ngarkuar…</string>\n    <string name=\"update\">Përditëso</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Fshih</string>\n    <string name=\"home_package\">Paketa</string>\n    <string name=\"home_app_title\">Aplikacioni</string>\n    <string name=\"home_notice_content\">Shkarkoni Magisk VETËM nga faqja zyrtare në GitHub. Skedarët nga burime të panjohura mund të jenë të dëmshëm!</string>\n    <string name=\"home_support_title\">Na mbështetni</string>\n    <string name=\"home_follow_title\">Na ndiqni</string>\n    <string name=\"home_item_source\">Burimi</string>\n    <string name=\"home_support_content\">Magisk është dhe do të mbetet gjithmonë falas dhe me burim të hapur. Megjithatë, mund të na mbështesni duke bërë një donacion.</string>\n    <string name=\"home_installed_version\">Instaluar</string>\n    <string name=\"home_latest_version\">Më i fundit</string>\n    <string name=\"invalid_update_channel\">Kanal i pavlefshëm për përditësime</string>\n    <string name=\"uninstall_magisk_title\">Çinstalo Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Të gjitha modulet do të çaktivizohen/hiqen!\nRoot-i do të hiqet!\nÇdo memorie e brendshme që është çenkriptuar përmes Magisk do të rikriptohet!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Ruaj enkriptimin e detyruar</string>\n    <string name=\"keep_dm_verity\">Ruaj AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Mënyra Recovery</string>\n    <string name=\"install_options_title\">Opsionet</string>\n    <string name=\"install_method_title\">Metoda</string>\n    <string name=\"install_next\">Vazhdoni</string>\n    <string name=\"install_start\">Le të fillojmë</string>\n    <string name=\"manager_download_install\">Shtypni për të shkarkuar dhe instaluar</string>\n    <string name=\"direct_install\">Instalim i drejtpërdrejtë (Rekomandohet)</string>\n    <string name=\"install_inactive_slot\">Instalo në slot-in joaktiv (Pas OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Pajisja juaj do të detyrohet të niset në slot-in joaktiv pas rinisjes!\nPërdorni këtë opsion vetëm pasi OTA të ketë përfunduar.\nTë vazhdoj?</string>\n    <string name=\"setup_title\">Konfigurim shtesë</string>\n    <string name=\"select_patch_file\">Zgjidh dhe përpuno një skedar</string>\n    <string name=\"patch_file_msg\">Zgjidh një imazh të papërpunuar (*.img) ose një skedar ODIN (*.tar) ose një payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Rinisja pas 5 sekondash…</string>\n    <string name=\"flash_screen_title\">Instalimi</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Kërkesë Superuser</string>\n    <string name=\"touch_filtered_warning\">Për shkak se një aplikacion po mbivendos kërkesën Superuser, Magisk nuk mund të verifikojë përgjigjen tuaj.</string>\n    <string name=\"deny\">Refuzo</string>\n    <string name=\"prompt\">Pyete</string>\n    <string name=\"restrict\">Kufizo</string>\n    <string name=\"grant\">Lejo</string>\n    <string name=\"su_warning\">Jep akses të plotë në pajisjen tuaj.\nRefuzoni nëse nuk jeni të sigurt!</string>\n    <string name=\"forever\">Përgjithmonë</string>\n    <string name=\"once\">Një herë</string>\n    <string name=\"tenmin\">10 minuta</string>\n    <string name=\"twentymin\">20 minuta</string>\n    <string name=\"thirtymin\">30 minuta</string>\n    <string name=\"sixtymin\">60 minuta</string>\n    <string name=\"su_allow_toast\">%1$s mori të drejtat Superuser</string>\n    <string name=\"su_deny_toast\">%1$s u refuzua të drejtat Superuser</string>\n    <string name=\"su_snack_grant\">%1$s mori të drejtat Superuser</string>\n    <string name=\"su_snack_deny\">%1$s u refuzua të drejtat Superuser</string>\n    <string name=\"su_snack_notif_on\">Njoftimet për %1$s u aktivizuan</string>\n    <string name=\"su_snack_notif_off\">Njoftimet për %1$s u çaktivizuan</string>\n    <string name=\"su_snack_log_on\">Regjistrimi për %1$s u aktivizua</string>\n    <string name=\"su_snack_log_off\">Regjistrimi për %1$s u çaktivizua</string>\n    <string name=\"su_revoke_title\">Të hiqen?</string>\n    <string name=\"su_revoke_msg\">Konfirmoni heqjen e të drejtave Superuser për %1$s</string>\n    <string name=\"toast\">Njoftim</string>\n    <string name=\"none\">Asnjë</string>\n    <string name=\"superuser_toggle_notification\">Njoftimet</string>\n    <string name=\"superuser_toggle_revoke\">Hiq</string>\n    <string name=\"superuser_policy_none\">Asnjë aplikacion nuk ka kërkuar ende leje Superuser.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nuk keni regjistrime. Provojeni të përdorni më shumë aplikacionet me root.</string>\n    <string name=\"log_data_magisk_none\">Regjistrimet e Magisk janë bosh — çuditërisht.</string>\n    <string name=\"menuSaveLog\">Ruaj regjistrimin</string>\n    <string name=\"menuClearLog\">Pastro regjistrimin tani</string>\n    <string name=\"logs_cleared\">Regjistrimet u pastruan me sukses</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID i synuar: %1$d</string>\n    <string name=\"target_pid\">PID i synuar: %s</string>\n    <string name=\"selinux_context\">Konteksti SELinux: %s</string>\n    <string name=\"supp_group\">Grupi shtesë: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Shfaq aplikacionet e sistemit</string>\n    <string name=\"show_os_app\">Shfaq aplikacionet e OS</string>\n    <string name=\"hide_filter_hint\">Filtro sipas emrit</string>\n    <string name=\"hide_search\">Kërko</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Nuk u dha informacion)</string>\n    <string name=\"reboot_userspace\">Rinisje Normale</string>\n    <string name=\"reboot_recovery\">Rinis në Recovery</string>\n    <string name=\"reboot_bootloader\">Rinis në Bootloader</string>\n    <string name=\"reboot_download\">Rinis në Download</string>\n    <string name=\"reboot_edl\">Rinis në EDL</string>\n    <string name=\"reboot_safe_mode\">Mënyra e sigurt</string>\n    <string name=\"module_version_author\">%1$s nga %2$s</string>\n    <string name=\"module_state_remove\">Hiqe</string>\n    <string name=\"module_action\">Veprimi</string>\n    <string name=\"module_state_restore\">Rikthe</string>\n    <string name=\"module_action_install_external\">Instalo nga memoria</string>\n    <string name=\"update_available\">Përditësim i disponueshëm</string>\n    <string name=\"suspend_text_riru\">Moduli u pezullua sepse %1$s është aktiv</string>\n    <string name=\"suspend_text_zygisk\">Moduli u pezullua sepse %1$s nuk është aktiv</string>\n    <string name=\"zygisk_module_unloaded\">Moduli Zygisk nuk u ngarkua për shkak të mospërputhjes</string>\n    <string name=\"module_empty\">Nuk ka module të instaluara</string>\n    <string name=\"confirm_install\">Të instalohet moduli %1$s?</string>\n    <string name=\"confirm_install_title\">Konfirmim instalimi</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Mënyra e temës</string>\n    <string name=\"settings_dark_mode_message\">Zgjidh mënyrën që i përshtatet më shumë stilit tënd!</string>\n    <string name=\"settings_dark_mode_light\">Gjithmonë e ndritshme</string>\n    <string name=\"settings_dark_mode_system\">Ndiq sistemin</string>\n    <string name=\"settings_dark_mode_dark\">Gjithmonë e errët</string>\n    <string name=\"settings_download_path_title\">Rruga e shkarkimit</string>\n    <string name=\"settings_download_path_message\">Skedarët do të ruhen në %1$s</string>\n    <string name=\"settings_hide_app_title\">Fshi aplikacionin Magisk</string>\n    <string name=\"settings_hide_app_summary\">Instalo një aplikacion proxy me një ID pakete të rastësishme dhe emër të personalizuar</string>\n    <string name=\"settings_restore_app_title\">Rikthe aplikacionin Magisk</string>\n    <string name=\"settings_restore_app_summary\">Zbulo aplikacionin dhe rikthe APK-në origjinale</string>\n    <string name=\"language\">Gjuha</string>\n    <string name=\"system_default\">(Parazgjedhja e sistemit)</string>\n    <string name=\"settings_check_update_title\">Kontrollo për përditësime</string>\n    <string name=\"settings_check_update_summary\">Kontrollo periodikisht për përditësimet në sfond</string>\n    <string name=\"settings_update_channel_title\">Kanal për përditësime</string>\n    <string name=\"settings_update_stable\">Stable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Custom</string>\n    <string name=\"settings_update_custom_msg\">Fut një URL të personalizuar të kanalit</string>\n    <string name=\"settings_zygisk_summary\">Ekzekuto pjesë të Magisk në demonin Zygote</string>\n    <string name=\"settings_denylist_title\">Zbato listën e ndaluar</string>\n    <string name=\"settings_denylist_summary\">Proceset në listën e ndaluar do të rikthehen pa modifikimet e Magisk</string>\n    <string name=\"settings_denylist_config_title\">Konfiguro listën e ndaluar</string>\n    <string name=\"settings_denylist_config_summary\">Zgjidh proceset që do të përfshihen në listën e ndaluar</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Mbështetje për systemless hosts për aplikacionet që bllokojnë reklamat</string>\n    <string name=\"settings_hosts_toast\">U shtua moduli systemless hosts</string>\n    <string name=\"settings_app_name_hint\">Emër i ri</string>\n    <string name=\"settings_app_name_helper\">Aplikacioni do të ripaketizohet me këtë emër</string>\n    <string name=\"settings_app_name_error\">Format i pavlefshëm</string>\n    <string name=\"settings_su_app_adb\">Aplikacionet dhe ADB</string>\n    <string name=\"settings_su_app\">Vetëm aplikacionet</string>\n    <string name=\"settings_su_adb\">Vetëm ADB</string>\n    <string name=\"settings_su_disable\">Çaktivizuar</string>\n    <string name=\"settings_su_request_10\">10 sekonda</string>\n    <string name=\"settings_su_request_15\">15 sekonda</string>\n    <string name=\"settings_su_request_20\">20 sekonda</string>\n    <string name=\"settings_su_request_30\">30 sekonda</string>\n    <string name=\"settings_su_request_45\">45 sekonda</string>\n    <string name=\"settings_su_request_60\">60 sekonda</string>\n    <string name=\"superuser_access\">Akses Superuser</string>\n    <string name=\"auto_response\">Përgjigje automatike</string>\n    <string name=\"request_timeout\">Koha e skadimit të kërkesës</string>\n    <string name=\"superuser_notification\">Njoftimi Superuser</string>\n    <string name=\"settings_su_reauth_title\">Riautentifikimi pas përditësimit</string>\n    <string name=\"settings_su_reauth_summary\">Kërko sërish lejet Superuser pas përditësimit të aplikacioneve</string>\n    <string name=\"settings_su_tapjack_title\">Mbrojtje nga mbivendosja e klikimeve</string>\n    <string name=\"settings_su_tapjack_summary\">Dritarja e kërkesës Superuser nuk do të pranojë input kur është e mbuluar nga ndonjë dritare tjetër</string>\n    <string name=\"settings_su_auth_title\">Autentifikimi i përdoruesit</string>\n    <string name=\"settings_su_auth_summary\">Kërko autentifikim të përdoruesit gjatë kërkesave Superuser</string>\n    <string name=\"settings_su_auth_insecure\">Nuk ka asnjë metodë autentifikimi të konfiguruar në pajisje</string>\n    <string name=\"settings_su_restrict_title\">Kufizo aftësitë e root</string>\n    <string name=\"settings_su_restrict_summary\">Do të kufizojë aplikacionet e reja Superuser si parazgjedhje. Kujdes: kjo mund të prishë shumicën e aplikacioneve. Mos e aktivizoni nëse nuk dini çfarë bëni.</string>\n    <string name=\"settings_customization\">Personalizimi</string>\n    <string name=\"setting_add_shortcut_summary\">Shto një shkurtore në ekranin bazë nëse emri/ikona bëhen të vështira për t’u dalluar pas fshehjes së aplikacionit</string>\n    <string name=\"settings_doh_title\">DNS mbi HTTPS</string>\n    <string name=\"settings_doh_description\">Zgjidhje për helmimin e DNS në disa shtete</string>\n    <string name=\"settings_random_name_title\">Emër i rastësishëm</string>\n    <string name=\"settings_random_name_description\">Rastësizo emrin e skedarit të daljes për imazhet e patch-uara dhe skedarët tar për të shmangur detektimin</string>\n    <string name=\"multiuser_mode\">Mënyra multi-përdorues</string>\n    <string name=\"settings_owner_only\">Vetëm pronari i pajisjes</string>\n    <string name=\"settings_owner_manage\">Menaxhuar nga pronari</string>\n    <string name=\"settings_user_independent\">I pavarur për përdoruesit</string>\n    <string name=\"owner_only_summary\">Vetëm pronari ka akses root</string>\n    <string name=\"owner_manage_summary\">Vetëm pronari mund të menaxhojë aksesin root dhe të marrë kërkesat</string>\n    <string name=\"user_independent_summary\">Çdo përdorues ka rregullat e veta të root</string>\n    <string name=\"mount_namespace_mode\">Mënyra e mount namespace</string>\n    <string name=\"settings_ns_global\">Namespace global</string>\n    <string name=\"settings_ns_requester\">Trashëgo namespace</string>\n    <string name=\"settings_ns_isolate\">Namespace i izoluar</string>\n    <string name=\"global_summary\">Të gjitha sesionet root përdorin namespace global</string>\n    <string name=\"requester_summary\">Sesioni root trashëgon namespace-in e kërkuesit</string>\n    <string name=\"isolate_summary\">Çdo sesion root do të ketë namespace të izoluar</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Përditësimet e Magisk</string>\n    <string name=\"progress_channel\">Njoftimet e progresit</string>\n    <string name=\"updated_channel\">Përditësimi përfundoi</string>\n    <string name=\"download_complete\">Shkarkimi përfundoi</string>\n    <string name=\"download_file_error\">Gabim gjatë shkarkimit të skedarit</string>\n    <string name=\"magisk_update_title\">Përditësim i ri i Magisk!</string>\n    <string name=\"updated_title\">Magisk u përditësua</string>\n    <string name=\"updated_text\">Shtypni për të hapur aplikacionin</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Po</string>\n    <string name=\"no\">Jo</string>\n    <string name=\"repo_install_title\">Instalo %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Shkarko</string>\n    <string name=\"reboot\">Rinise</string>\n    <string name=\"close\">Mbyll</string>\n    <string name=\"release_notes\">Shënimet e versionit</string>\n    <string name=\"flashing\">Duke flashuar..</string>\n    <string name=\"running\">Duke u ekzekutuar..</string>\n    <string name=\"done\">U krye!</string>\n    <string name=\"done_action\">Veprimi i %1$s u krye</string>\n    <string name=\"failure\">Dështoi!</string>\n    <string name=\"hide_app_title\">Duke fshehur aplikacionin Magisk..</string>\n    <string name=\"open_link_failed_toast\">Nuk u gjet asnjë aplikacion për të hapur lidhjen</string>\n    <string name=\"complete_uninstall\">Çinstalim i plotë</string>\n    <string name=\"restore_img\">Rikthe imazhet</string>\n    <string name=\"restore_img_msg\">Duke rikthyer..</string>\n    <string name=\"restore_done\">Rikthimi u krye!</string>\n    <string name=\"restore_fail\">Backup-i origjinal nuk ekziston!</string>\n    <string name=\"setup_fail\">Konfigurimi dështoi</string>\n    <string name=\"env_fix_title\">Kërkohet konfigurim shtesë</string>\n    <string name=\"env_fix_msg\">Pajisja ka nevojë për konfigurim shtesë që Magisk të funksionojë si duhet. Dëshironi të vazhdoni dhe të rinisni pajisjen?</string>\n    <string name=\"env_full_fix_msg\">Pajisja ka nevojë për ri-flash të Magisk për të funksionuar saktë. Ju lutemi riinstaloni Magisk brenda aplikacionit; Recovery nuk mund të marrë informacionet e sakta të pajisjes.</string>\n    <string name=\"setup_msg\">Duke ekzekutuar konfigurimin e mjedisit..</string>\n    <string name=\"unsupport_magisk_title\">Version i Magisk i pambështetur</string>\n    <string name=\"unsupport_magisk_msg\">Ky version i aplikacionit nuk mbështet versione të Magisk më të ulëta se %1$s.\n\nAplikacioni do të sillet sikur Magisk nuk është i instaluar. Ju lutemi përditësoni Magisk sa më shpejt të jetë e mundur.</string>\n    <string name=\"unsupport_general_title\">Gjendje jonormale</string>\n    <string name=\"unsupport_system_app_msg\">Ekzekutimi i këtij aplikacioni si aplikacion sistemi nuk mbështetet. Ju lutemi kthejeni në aplikacion përdoruesi.</string>\n    <string name=\"unsupport_other_su_msg\">Është zbuluar një binar \"su\" që nuk është nga Magisk. Ju lutemi hiqni çdo zgjidhje tjetër root dhe/ose riinstaloni Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk është instaluar në memorien e jashtme. Lëvizni aplikacionin në memorien e brendshme.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Aplikacioni i fshehur i Magisk nuk mund të vazhdojë të funksionojë sepse root u humb. Ju lutemi riktheni APK-në origjinale.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Jepni lejen e magazinimit për të aktivizuar këtë funksion</string>\n    <string name=\"post_notifications_denied\">Jepni lejen e njoftimeve për të aktivizuar këtë funksion</string>\n    <string name=\"install_unknown_denied\">Lejoni \"Instalo aplikacione të panjohura\" për të aktivizuar këtë funksion</string>\n    <string name=\"add_shortcut_title\">Shto shkurtore në ekranin bazë</string>\n    <string name=\"add_shortcut_msg\">Pas fshehjes së aplikacionit, emri dhe ikona mund të jenë të vështira për t’u njohur. Dëshironi të shtoni një shkurtore të bukur në ekranin bazë?</string>\n    <string name=\"app_not_found\">Nuk u gjet aplikacion për të kryer këtë veprim</string>\n    <string name=\"reboot_apply_change\">Rinisni për të aplikuar ndryshimet</string>\n    <string name=\"restore_app_confirmation\">Kjo do të rikthejë aplikacionin e fshehur në gjendjen origjinale. Jeni të sigurt që dëshironi ta bëni këtë?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-sr/strings.xml",
    "content": "<resources>\n    <!--Author: Radoš Milićev (https://github.com/rammba)-->\n\n    <!--Sections-->\n    <string name=\"modules\">Модули</string>\n    <string name=\"superuser\">Супер-корисник</string>\n    <string name=\"logs\">Логови</string>\n    <string name=\"settings\">Подешавања</string>\n    <string name=\"install\">Инсталација</string>\n    <string name=\"section_home\">Почетно</string>\n    <string name=\"section_theme\">Теме</string>\n    <string name=\"denylist\">Листа забрана</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Недоступна конекција</string>\n    <string name=\"app_changelog\">Промене у апликацији</string>\n    <string name=\"loading\">Учитавање…</string>\n    <string name=\"update\">Ажурирање</string>\n    <string name=\"not_available\">N/A</string>\n    <string name=\"hide\">Сакриј</string>\n    <string name=\"home_package\">Пакет</string>\n    <string name=\"home_app_title\">Апл.</string>\n    <string name=\"home_notice_content\">Преузмите Magisk САМО са званичне GitHub странице. Фајлови из непознатих извора могу бити малициозни!</string>\n    <string name=\"home_support_title\">Подржите нас</string>\n    <string name=\"home_follow_title\">Запратите нас</string>\n    <string name=\"home_item_source\">Извор</string>\n    <string name=\"home_support_content\">Magisk јесте и увек ће бити бесплатан и open source. Међутим, можете показати да вам је стало својом донацијом.</string>\n    <string name=\"home_installed_version\">Инсталирано</string>\n    <string name=\"home_latest_version\">Најновије</string>\n    <string name=\"invalid_update_channel\">Невалидан канал ажурирања</string>\n    <string name=\"uninstall_magisk_title\">Деинсталирај Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Сви модули ће бити онемогућени/уклоњени!\\nКорен ће бити уклоњен!\\nСвако неенкриптовано интерно складиште ће употребом Magisk-а бити поново енкриптовано!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Задржи форсирану енкрипцију</string>\n    <string name=\"keep_dm_verity\">Задржи AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Режим опоравка</string>\n    <string name=\"install_options_title\">Опције</string>\n    <string name=\"install_method_title\">Метод</string>\n    <string name=\"install_next\">Наредно</string>\n    <string name=\"install_start\">Почнимо</string>\n    <string name=\"manager_download_install\">Притисни да преузмеш и инсталираш</string>\n    <string name=\"direct_install\">Директна инсталација (Препоручено)</string>\n    <string name=\"install_inactive_slot\">Инсталација на неактиван слот (Након OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Ваш уређај ће бити ФОРСИРАН да се покрене на тренутно неактивном слоту након поновног покретања!\\nКористите опцију само кад се OTA заврши.\\nНастави?</string>\n    <string name=\"setup_title\">Додатне поставке</string>\n    <string name=\"select_patch_file\">Изаберите фајл</string>\n    <string name=\"patch_file_msg\">Изаберите слику (*.img) или ODIN tarfile (*.tar) или payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">Поново покретање за 5 секунди…</string>\n    <string name=\"flash_screen_title\">Инсталација</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Супер-кориснички захтев</string>\n    <string name=\"touch_filtered_warning\">Magisk не може да верификује ваш одговор, јер апликација прикрива супер-кориснички захтев.</string>\n    <string name=\"deny\">Забрани</string>\n    <string name=\"prompt\">Захтев</string>\n    <string name=\"restrict\">Ограничи</string>\n    <string name=\"grant\">Дозволи</string>\n    <string name=\"su_warning\">Пружа потпун приступ вашем уређају.\\nЗабраните ако нисте сигурни!</string>\n    <string name=\"forever\">Заувек</string>\n    <string name=\"once\">Једном</string>\n    <string name=\"tenmin\">10 мин</string>\n    <string name=\"twentymin\">20 мин</string>\n    <string name=\"thirtymin\">30 мин</string>\n    <string name=\"sixtymin\">60 мин</string>\n    <string name=\"su_allow_toast\">%1$s је добио права на супер-корисника</string>\n    <string name=\"su_deny_toast\">%1$s није добио права на супер-корисника</string>\n    <string name=\"su_snack_grant\">Супер-корисничка права од %1$s су пружена</string>\n    <string name=\"su_snack_deny\">Супер-корисничка права од %1$s су одбијена</string>\n    <string name=\"su_snack_notif_on\">Нотификације од %1$s су омогућене</string>\n    <string name=\"su_snack_notif_off\">Нотификације од %1$s су онемогућене</string>\n    <string name=\"su_snack_log_on\">Логовање за %1$s је омогућено</string>\n    <string name=\"su_snack_log_off\">Логовање за %1$s је онемогућено</string>\n    <string name=\"su_revoke_title\">Опозови?</string>\n    <string name=\"su_revoke_msg\">Потврди да опозовеш права на супер-корисника од %1$s?</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">Ништа</string>\n    <string name=\"superuser_toggle_notification\">Нотификације</string>\n    <string name=\"superuser_toggle_revoke\">Опозови</string>\n    <string name=\"superuser_policy_none\">Ниједна апликација није тражила пермисије за супер-корисника још увек.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Немате логова. Покушајте користити коренске апликације више.</string>\n    <string name=\"log_data_magisk_none\">Magisk логови су празни, то је чудно.</string>\n    <string name=\"menuSaveLog\">Сачувај лог</string>\n    <string name=\"menuClearLog\">Уклони лог</string>\n    <string name=\"logs_cleared\">Лог успешно уклоњен</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Циљани UID: %1$d</string>\n    <string name=\"target_pid\">Циљани PID: %s</string>\n    <string name=\"selinux_context\">SELinux контекст: %s</string>\n    <string name=\"supp_group\">Допунска група: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Прикажи системске апл.</string>\n    <string name=\"show_os_app\">Прикажи апл. ОС-а</string>\n    <string name=\"hide_filter_hint\">Филтрирај по имену</string>\n    <string name=\"hide_search\">Претрага</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Без информација)</string>\n    <string name=\"reboot_userspace\">Лако поново покретање</string>\n    <string name=\"reboot_recovery\">Поново покрени за опоравак</string>\n    <string name=\"reboot_bootloader\">Поново покрени за bootloader</string>\n    <string name=\"reboot_download\">Поново покрени за преузимање</string>\n    <string name=\"reboot_edl\">Поново покрени за EDL</string>\n    <string name=\"reboot_safe_mode\">Сигуран мод</string>\n    <string name=\"module_version_author\">%1$s од %2$s</string>\n    <string name=\"module_state_remove\">Уклони</string>\n    <string name=\"module_action\">Акција</string>\n    <string name=\"module_state_restore\">Поврати</string>\n    <string name=\"module_action_install_external\">Инсталирај из складишта</string>\n    <string name=\"update_available\">Ажурирање доступно</string>\n    <string name=\"suspend_text_riru\">Модул је суспендован јер је %1$s омогућено</string>\n    <string name=\"suspend_text_zygisk\">Модул је суспендован јер %1$s није омогућено</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk модул није учитан због некомпатибилности</string>\n    <string name=\"module_empty\">Ниједан модул није инсталиран</string>\n    <string name=\"confirm_install\">Инсталирај модул %1$s?</string>\n    <string name=\"confirm_install_title\">Потврда инсталације</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Тема</string>\n    <string name=\"settings_dark_mode_message\">Изаберите тему која вам највише одговара!</string>\n    <string name=\"settings_dark_mode_light\">Увек светло</string>\n    <string name=\"settings_dark_mode_system\">Прати систем</string>\n    <string name=\"settings_dark_mode_dark\">Увек тамно</string>\n    <string name=\"settings_download_path_title\">Путања за преузимање</string>\n    <string name=\"settings_download_path_message\">Фајлови ће бити сачувани на %1$s</string>\n    <string name=\"settings_hide_app_title\">Сакриј Magisk апл.</string>\n    <string name=\"settings_hide_app_summary\">Инсталирај proxy апликацију са насумичним ID-јем пакета и прилагођеном лабелом</string>\n    <string name=\"settings_restore_app_title\">Поврати Magisk апл.</string>\n    <string name=\"settings_restore_app_summary\">Откриј апл. и поврати оригинални APK</string>\n    <string name=\"language\">Језик</string>\n    <string name=\"system_default\">(Подразумевано системски)</string>\n    <string name=\"settings_check_update_title\">Провери ажурирања</string>\n    <string name=\"settings_check_update_summary\">Периодично провери ажурирања у позадини</string>\n    <string name=\"settings_update_channel_title\">Канал ажурирања</string>\n    <string name=\"settings_update_stable\">Стабилно</string>\n    <string name=\"settings_update_beta\">Бета</string>\n    <string name=\"settings_update_debug\">Debug</string>\n    <string name=\"settings_update_custom\">Прилагођено</string>\n    <string name=\"settings_update_custom_msg\">Унеси прилагођени URL канала</string>\n    <string name=\"settings_zygisk_summary\">Покрени делове Magisk-а у Zygote daemon-у</string>\n    <string name=\"settings_denylist_title\">Спроведи листу забрана</string>\n    <string name=\"settings_denylist_summary\">Процеси на листи забрана ће повратити све Magisk измене</string>\n    <string name=\"settings_denylist_config_title\">Конфигуриши листу забрана</string>\n    <string name=\"settings_denylist_config_summary\">Изабери процесе који ће бити на листи забрана</string>\n    <string name=\"settings_hosts_title\">Безсистемски домаћини (hosts)</string>\n    <string name=\"settings_hosts_summary\">Подршка безсистемских домаћина за апликације блокирања реклама</string>\n    <string name=\"settings_hosts_toast\">Модул безсистемских домаћина додат</string>\n    <string name=\"settings_app_name_hint\">Ново име</string>\n    <string name=\"settings_app_name_helper\">Апл. ће бити спакована под овим именом</string>\n    <string name=\"settings_app_name_error\">Невалидан формат</string>\n    <string name=\"settings_su_app_adb\">Апликације и ADB</string>\n    <string name=\"settings_su_app\">Само апликације</string>\n    <string name=\"settings_su_adb\">Само ADB</string>\n    <string name=\"settings_su_disable\">Онемогућено</string>\n    <string name=\"settings_su_request_10\">10 секунди</string>\n    <string name=\"settings_su_request_15\">15 секунди</string>\n    <string name=\"settings_su_request_20\">20 секунди</string>\n    <string name=\"settings_su_request_30\">30 секунди</string>\n    <string name=\"settings_su_request_45\">45 секунди</string>\n    <string name=\"settings_su_request_60\">60 секунди</string>\n    <string name=\"superuser_access\">Приступ супер-корисника</string>\n    <string name=\"auto_response\">Аутоматски одговор</string>\n    <string name=\"request_timeout\">Истек захтева</string>\n    <string name=\"superuser_notification\">Нотификације супер-корисника</string>\n    <string name=\"settings_su_reauth_title\">Поново одобри након ажурирања</string>\n    <string name=\"settings_su_reauth_summary\">Поново тражи пермисије супер-корисника након ажурирања апликација</string>\n    <string name=\"settings_su_tapjack_title\">Заштита од tapjacking-а</string>\n    <string name=\"settings_su_tapjack_summary\">Prompt дијалог супер-корисника неће реаговати док је прикривен другим прозором или overlay-ем</string>\n    <string name=\"settings_su_auth_title\">Аутентификација корисника</string>\n    <string name=\"settings_su_auth_summary\">Тражи аутентификацију корисника током захтева супер-корисника</string>\n    <string name=\"settings_su_auth_insecure\">Ниједан метод аутентификације није подешен на уређају</string>\n    <string name=\"settings_su_restrict_title\">Ограничи коренске способности</string>\n    <string name=\"settings_su_restrict_summary\">Подразумевано ограничава апл. супер-корисника. Упозорење: ово ће већину апликација скршити. Не омогућавај, осим ако знаш шта радиш.</string>\n    <string name=\"settings_customization\">Прилагођавање</string>\n    <string name=\"setting_add_shortcut_summary\">Додај лепу пречицу на почетни екран у случају да се име и иконица не препознају лако након скривања апликације</string>\n    <string name=\"settings_doh_title\">DNS преко HTTPS-а</string>\n    <string name=\"settings_doh_description\">Заобилазно решење DNS тровања у неким нацијама</string>\n    <string name=\"settings_random_name_title\">Насумично име на излазу</string>\n    <string name=\"settings_random_name_description\">Насумично име излазног фајла слика и tar фајлова ради спречавања детекције</string>\n    <string name=\"multiuser_mode\">Вишекориснички режим</string>\n    <string name=\"settings_owner_only\">Само власник уређаја</string>\n    <string name=\"settings_owner_manage\">Одређено од стране власника</string>\n    <string name=\"settings_user_independent\">Независно од корисника</string>\n    <string name=\"owner_only_summary\">Само власник има приступ корену</string>\n    <string name=\"owner_manage_summary\">Само власник може да приступа корену и да прима захтеве за њега</string>\n    <string name=\"user_independent_summary\">Сваки корисник има своја правила корена</string>\n    <string name=\"mount_namespace_mode\">Mount режим namespace-а</string>\n    <string name=\"settings_ns_global\">Глобални namespace</string>\n    <string name=\"settings_ns_requester\">Наслеђени namespace</string>\n    <string name=\"settings_ns_isolate\">Изоловани namespace</string>\n    <string name=\"global_summary\">Све коренске сесије користе глобални mount namespace</string>\n    <string name=\"requester_summary\">Коренске сесије ће наследити namespace од подносиоца захтева</string>\n    <string name=\"isolate_summary\">Свака коренска сесија ће имати свој изоловани namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Ажурирања Magisk-а</string>\n    <string name=\"progress_channel\">Нотификације о прогресу</string>\n    <string name=\"updated_channel\">Ажурирање завршено</string>\n    <string name=\"download_complete\">Преузимање завршено</string>\n    <string name=\"download_file_error\">Грешка при преузимању фајла</string>\n    <string name=\"magisk_update_title\">Ажурирање Magisk-а доступно!</string>\n    <string name=\"updated_title\">Magisk је ажуриран</string>\n    <string name=\"updated_text\">Кликни да отвориш апликацију</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Да</string>\n    <string name=\"no\">Не</string>\n    <string name=\"repo_install_title\">Инсталирај %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Преузми</string>\n    <string name=\"reboot\">Поново покрени</string>\n    <string name=\"close\">Затвори</string>\n    <string name=\"release_notes\">Release notes</string>\n    <string name=\"flashing\">Флешовање…</string>\n    <string name=\"running\">Покретање…</string>\n    <string name=\"done\">Завршено!</string>\n    <string name=\"done_action\">Покретање акције %1$s завршено</string>\n    <string name=\"failure\">Неуспешно!</string>\n    <string name=\"hide_app_title\">Скривање Magisk апликације…</string>\n    <string name=\"open_link_failed_toast\">Није пронађена апликација за отварање линка</string>\n    <string name=\"complete_uninstall\">Комплетна деинсталација</string>\n    <string name=\"restore_img\">Поврати слике</string>\n    <string name=\"restore_img_msg\">Повратак…</string>\n    <string name=\"restore_done\">Повратак успешан!</string>\n    <string name=\"restore_fail\">Фабрички бекап не постоји!</string>\n    <string name=\"setup_fail\">Неуспешна поставка</string>\n    <string name=\"env_fix_title\">Потребно додатно подешавање</string>\n    <string name=\"env_fix_msg\">Ваш уређај захтева додатно подешавање да би Magisk радио како треба. Да ли желите наставити и покренути поново?</string>\n    <string name=\"env_full_fix_msg\">Ваш уређај захтева поновно флешовање да би Magisk радио како треба. Реинсталирајте Magisk кроз апликацију, режим опоравка не може добити тачне информације о уређају.</string>\n    <string name=\"setup_msg\">Покретање подешавања окружења…</string>\n    <string name=\"unsupport_magisk_title\">Неподржана верзија Magisk-а</string>\n    <string name=\"unsupport_magisk_msg\">Ова верзија апликације не подржава Magisk верзије мање од %1$s.\\n\\nАпликација ће се понашати као да Magisk није инсталиран. Молимо ажурирајте Magisk што пре.</string>\n    <string name=\"unsupport_general_title\">Ненормално стање</string>\n    <string name=\"unsupport_system_app_msg\">Покретање апликације као системске није подржано. Молимо поставите апликацију да буде корисничка.</string>\n    <string name=\"unsupport_other_su_msg\">Детектован \\\"su\\\" binary који није Magisk-ов. Молимо уклоните конкурентно коренско решење и/или реинсталирајте Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk је инсталиран на екстерно складиште. Молимо померите апл. у интерно складиште.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Скривена Magisk апликација не може наставити са радом јер је корен изгубљен. Молимо повратите оригинални APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Дозволите пермисију за складиште да бисте омогућили ову функционалност</string>\n    <string name=\"post_notifications_denied\">Дозволите пермисију за нотификације да бисте омогућили ову функционалност</string>\n    <string name=\"install_unknown_denied\">Дозволите \\\"инсталирање непознатих апликација\\\" да бисте омогућили ову функционалност</string>\n    <string name=\"add_shortcut_title\">Додај пречицу на почетни екран</string>\n    <string name=\"add_shortcut_msg\">Након скривања апликације, њено име и иконицу ћете тешко препознати. Желите ли додати лепу пречицу на почетни екран?</string>\n    <string name=\"app_not_found\">Није пронађена апликација за ову акцију</string>\n    <string name=\"reboot_apply_change\">Поново покрени да примениш измене</string>\n    <string name=\"restore_app_confirmation\">Ово ће вратити скривену апликацију на оригиналну. Да ли стварно то желите?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-sv/strings.xml",
    "content": "<resources>\n\t<!--Sections-->\n    <string name=\"modules\">Moduler</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Logg</string>\n    <string name=\"settings\">Inställningar</string>\n    <string name=\"install\">Installera</string>\n    <string name=\"section_home\">Hem</string>\n    <string name=\"section_theme\">Tema</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Ingen anslutning tillgänglig</string>\n    <string name=\"app_changelog\">Ändringslogg</string>\n    <string name=\"loading\">Laddar…</string>\n    <string name=\"update\">Uppdatera</string>\n    <string name=\"not_available\">Inte tillgänglig</string>\n    <string name=\"hide\">Dölj</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">Ladda endast ned magisk från den officiella GitHub-sidan. Filer från okända källor kan innehålla skadlig kod!</string>\n    <string name=\"home_support_title\">Stöd oss</string>\n    <string name=\"home_item_source\">Källa</string>\n    <string name=\"home_support_content\">Magisk är och kommer alltid att vara gratis med öppen källkod. Du kan dock visa din uppskattning genom att skicka en donation</string>\n    <string name=\"home_installed_version\">Installerad</string>\n    <string name=\"home_latest_version\">Senaste</string>\n    <string name=\"invalid_update_channel\">Ogiltig uppdateringskanal</string>\n    <string name=\"uninstall_magisk_title\">Avinstallera Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Alla moduler kommer att bli inaktiverade/borttagna!\\nRoot kommer också att avinstalleras!\\nDin data kan komma att krypteras om den inte är det redan!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Bibehåll tvingad kryptering</string>\n    <string name=\"keep_dm_verity\">Bibehåll AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Recovery-läge</string>\n    <string name=\"install_options_title\">Alternativ</string>\n    <string name=\"install_method_title\">Metod</string>\n    <string name=\"install_next\">Nästa</string>\n    <string name=\"install_start\">Nu kör vi!</string>\n    <string name=\"manager_download_install\">Tryck för att ladda ned och installera install</string>\n    <string name=\"direct_install\">Direkt installation (Rekommenderas)</string>\n    <string name=\"install_inactive_slot\">Installera till inaktiv Slot (Efter OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Din enhet kommer att bli TVINGAD att starta till den nuvarande inaktiva slot efter omstart!\\nAnvänd endast detta alternativ efter att en OTA uppdatering är utförd.\\nFortsätt??</string>\n    <string name=\"setup_title\">Ytterligare installation</string>\n    <string name=\"select_patch_file\">Välj och patcha en fil</string>\n    <string name=\"patch_file_msg\">Välj en rå avbildningsfil (*.img) eller en ODIN tar-fil (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Omstart om 5 sekunder…</string>\n    <string name=\"flash_screen_title\">Installation</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser-förfrågan</string>\n    <string name=\"touch_filtered_warning\">Eftersom en app döljer, eller ritar över en begäran om superuser, så kan inte Magisk verifera ditt svar</string>\n    <string name=\"deny\">Neka</string>\n    <string name=\"prompt\">Fråga</string>\n    <string name=\"grant\">Bevilja</string>\n    <string name=\"su_warning\">Beviljar full tillgång till din enhet.\\nNeka om du är osäker!</string>\n    <string name=\"forever\">För alltid</string>\n    <string name=\"once\">En gång</string>\n    <string name=\"tenmin\">10 minuter</string>\n    <string name=\"twentymin\">20 minuter</string>\n    <string name=\"thirtymin\">30 minuter</string>\n    <string name=\"sixtymin\">60 minuter</string>\n    <string name=\"su_allow_toast\">%1$s beviljades Superuser-rättigheter</string>\n    <string name=\"su_deny_toast\">%1$s nekades Superuser-rättigheter</string>\n    <string name=\"su_snack_grant\">Superuser-rättigheter till %1$s är beviljade</string>\n    <string name=\"su_snack_deny\">Superuser-rättigheter of %1$s är nekade</string>\n    <string name=\"su_snack_notif_on\">Aviseringar för %1$s är aktiverade</string>\n    <string name=\"su_snack_notif_off\">Aviseringar för %1$s är inaktiverade</string>\n    <string name=\"su_snack_log_on\">Loggning av %1$s är aktiverad</string>\n    <string name=\"su_snack_log_off\">Logging av %1$s är inaktiverade</string>\n    <string name=\"su_revoke_title\">Återkalla?</string>\n    <string name=\"su_revoke_msg\">Bekräfta återkallning utav %1$s rättigheter?</string>\n    <string name=\"toast\">Toast-meddelande</string>\n    <string name=\"none\">Inga</string>\n\n    <string name=\"superuser_toggle_notification\">Aviseringar</string>\n    <string name=\"superuser_toggle_revoke\">Återkalla</string>\n    <string name=\"superuser_policy_none\">Inga appar har bett om superuser-rättigheter ännu.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Du är logg-lös, försök att använda dina SU-aktiverade appar mer</string>\n    <string name=\"log_data_magisk_none\">Magisks loggar är tomma. Underligt.</string>\n    <string name=\"menuSaveLog\">Spara loggen</string>\n    <string name=\"menuClearLog\">Rensa loggen</string>\n    <string name=\"logs_cleared\">Loggrensning lyckades</string>\n    <!-- <string name=\"pid\">PID: %1$d</string> -->\n    <!-- <string name=\"target_uid\">Target UID: %1$d</string> -->\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Visa system-appar</string>\n    <string name=\"show_os_app\">Via OS-appar</string>\n    <string name=\"hide_filter_hint\">Filtrera med namn</string>\n    <string name=\"hide_search\">Sök</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Ingen information tillgänglig)</string>\n    <string name=\"reboot_userspace\">Mjuk omstart</string>\n    <string name=\"reboot_recovery\">Starta om till Recovery</string>\n    <string name=\"reboot_bootloader\">Omstart till Bootloader</string>\n    <string name=\"reboot_download\">Omstart till Nedladdningsläge</string>\n    <string name=\"reboot_edl\">Omstart till EDL</string>\n    <string name=\"module_version_author\">%1$s av %2$s</string>\n    <string name=\"module_state_remove\">Ta bort</string>\n    <string name=\"module_state_restore\">Återställ</string>\n    <string name=\"module_action_install_external\">Installera från lagringsmedia</string>\n    <string name=\"update_available\">Uppdatering tillgänglig</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Temaläge</string>\n    <string name=\"settings_dark_mode_message\">Välj det läge som bäst passar din stil!</string>\n    <string name=\"settings_dark_mode_light\">Alltid ljus</string>\n    <string name=\"settings_dark_mode_system\">Följ systeminställningar</string>\n    <string name=\"settings_dark_mode_dark\">Alltid mörk</string>\n    <string name=\"settings_download_path_title\">Nedladdningsmapp</string>\n    <string name=\"settings_download_path_message\">Filer kommer att spara till %1$s</string>\n    <string name=\"settings_hide_app_title\">Göm Magisk-appen</string>\n    <string name=\"settings_hide_app_summary\">Installera en proxy-app med ett slumpmässigt paket-ID och appnamn</string>\n    <string name=\"settings_restore_app_title\">Återställ Magisk-appen</string>\n    <string name=\"settings_restore_app_summary\">Ta bort proxy-appen och återställ den ursprungliga APK filen</string>\n    <string name=\"language\">Språk</string>\n    <string name=\"system_default\">(Följ systeminställningar)</string>\n    <string name=\"settings_check_update_title\">Kontrollera uppdateringar</string>\n    <string name=\"settings_check_update_summary\">Kontrollera regelbundet efter uppdateringar i bakgrunden</string>\n    <string name=\"settings_update_channel_title\">Uppdateringskanal</string>\n    <string name=\"settings_update_stable\">Stabil</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Anpassad kanal</string>\n    <string name=\"settings_update_custom_msg\">Lägg till en anpassad URL</string>\n    <string name=\"settings_hosts_title\">Systemfri hosts</string>\n    <string name=\"settings_hosts_summary\">Stöd för Adblock-appar med systemfri hosts-fil</string>\n    <string name=\"settings_hosts_toast\">Lagt till modul för systemfri hosts-fil</string>\n    <string name=\"settings_app_name_hint\">Nytt namn</string>\n    <string name=\"settings_app_name_helper\">Appen kommer att pacakas om till detta namn</string>\n    <string name=\"settings_app_name_error\">Ogiltigt format</string>\n    <string name=\"settings_su_app_adb\">Appar och ADB</string>\n    <string name=\"settings_su_app\">Bara Appar</string>\n    <string name=\"settings_su_adb\">Bara ADB</string>\n    <string name=\"settings_su_disable\">Inaktiverad</string>\n    <string name=\"settings_su_request_10\">10 sekunder</string>\n    <string name=\"settings_su_request_15\">15 sekunder</string>\n    <string name=\"settings_su_request_20\">20 sekunder</string>\n    <string name=\"settings_su_request_30\">30 sekunder</string>\n    <string name=\"settings_su_request_45\">45 sekunder</string>\n    <string name=\"settings_su_request_60\">60 sekunder</string>\n    <string name=\"superuser_access\">Superuser-tillgång</string>\n    <string name=\"auto_response\">Automatiskt svar</string>\n    <string name=\"request_timeout\">Förfrågnings-timeout</string>\n    <string name=\"superuser_notification\">Superuser-avisering</string>\n    <string name=\"settings_su_reauth_title\">Återautentisera efter uppdatering</string>\n    <string name=\"settings_su_reauth_summary\">Återautentisera superuser-rättigheter efter en applikationsuppdatering</string>\n    <string name=\"settings_su_tapjack_title\">Aktivera Tapjacking-skydd</string>\n    <string name=\"settings_su_tapjack_summary\">Superuser-dialogrutan kommer inte att lyssna på någon input om den är dold eller övertäckt utav något annat fönster.</string>\n    <string name=\"settings_customization\">Anpassning</string>\n    <string name=\"setting_add_shortcut_summary\">Lägg till en snygg genväg på startskärmen om namnet och ikonen är svåra att känna igen efter att appen har döljts</string>\n    <string name=\"settings_doh_title\">DNS över HTTPS</string>\n    <string name=\"settings_doh_description\">Lösning för DNS-förgiftning i vissa länder</string>\n\n    <string name=\"multiuser_mode\">Multiuser-läge</string>\n    <string name=\"settings_owner_only\">Endast enhetsägare</string>\n    <string name=\"settings_owner_manage\">Enhetsägare hanterar</string>\n    <string name=\"settings_user_independent\">Användaroberoende</string>\n    <string name=\"owner_only_summary\">Endast ägare har root-åtkomst</string>\n    <string name=\"owner_manage_summary\">Endast ägare kan hantera root-åtkomst och ta emot bägärda förfrågningar</string>\n    <string name=\"user_independent_summary\">Varje användare har sina egna separata root-regler</string>\n\n    <string name=\"mount_namespace_mode\">Montera namnrymdsläge</string>\n    <string name=\"settings_ns_global\">Global namnrymd</string>\n    <string name=\"settings_ns_requester\">Ärv namnrymden</string>\n    <string name=\"settings_ns_isolate\">Isolerad namnrymd</string>\n    <string name=\"global_summary\">Alla root-sessioner använder den globala namnrymden</string>\n    <string name=\"requester_summary\">Root-sessioner kommer att ärva den sökandes namnrymd</string>\n    <string name=\"isolate_summary\">Varje root-session kommer att ha sin egen isolerade namnrymd</string>\n\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk-uppdatering</string>\n    <string name=\"progress_channel\">Statusmeddelanden</string>\n    <string name=\"download_complete\">Nedladdning slutförd</string>\n    <string name=\"download_file_error\">Fel vid nedladdning utav fil</string>\n    <string name=\"magisk_update_title\">En uppdatering för Magisk finns tillgänglig!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Ja</string>\n    <string name=\"no\">Nej</string>\n    <string name=\"repo_install_title\">Installera %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Ladda ned</string>\n    <string name=\"reboot\">Omstart</string>\n    <string name=\"release_notes\">Utgivningsanmärkningar</string>\n    <string name=\"flashing\">Flashar…</string>\n    <string name=\"done\">Färdig!</string>\n    <string name=\"failure\">Misslyckades!</string>\n    <string name=\"hide_app_title\">Gömmer Magisk-appen…</string>\n    <string name=\"open_link_failed_toast\">Ingen app hittades som kan öppna länken link</string>\n    <string name=\"complete_uninstall\">Komplett avinstallation</string>\n    <string name=\"restore_img\">Återställ avbilder</string>\n    <string name=\"restore_img_msg\">Återställer…</string>\n    <string name=\"restore_done\">Återställning slutförd!</string>\n    <string name=\"restore_fail\">Orörd backup hittades inte!</string>\n    <string name=\"setup_fail\">Installation misslyckades</string>\n    <string name=\"env_fix_title\">Kräver ytterligare installation</string>\n    <string name=\"env_fix_msg\">Din enhet behöver ytterligare installation för att Magisk ska fungera korrekt. Vill du fortsätta och starta om enheten?</string>\n    <string name=\"setup_msg\">Inställning av körmiljö…</string>\n    <string name=\"unsupport_magisk_title\">Magisk-versionen stöds ej</string>\n    <string name=\"unsupport_magisk_msg\">Denna version utav appen stödjer inte Magisk-versioner lägre än 1$s.\\n\\nAppen kommer att bete sig som att Magisk inte är installerat. Vänligen uppgradera din Magisk-version så snart som möjligt.</string>\n    <string name=\"external_rw_permission_denied\">Tillåt tillgång till externt lagrinsmedia för att aktievera denna funktion</string>\n    <string name=\"add_shortcut_title\">Lägg till genväg på hemskärmen</string>\n    <string name=\"add_shortcut_msg\">Efter att appen dolts så kan ikonen och namnet vara svårt att känna igen. Vill du lägga till en snygg genväg till din hemskärm?</string>\n    <string name=\"app_not_found\">Ingen applikation hittades för att hantera den här åtgärden</string>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-sw/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Moduli</string>\n    <string name=\"superuser\">Mtumiaji mkuu</string>\n    <string name=\"logs\">Kumbukumbu</string>\n    <string name=\"settings\">Mipangilio</string>\n    <string name=\"install\">Sakinisha</string>\n    <string name=\"section_home\">Nyumbanik</string>\n    <string name=\"section_theme\">Mandhari</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Hakuna muunganisho unaopatikana</string>\n    <string name=\"app_changelog\">Kumbukumbu</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"update\">Sasisha</string>\n    <string name=\"not_available\">Haipatikani</string>\n    <string name=\"hide\">Ficha</string>\n    <string name=\"home_package\">Kifurushi</string>\n    <string name=\"home_app_title\">Programu</string>\n\n    <string name=\"home_notice_content\">Download Magisk ONLY from the official GitHub page. Files from unknown sources can be malicious!</string>\n    <string name=\"home_support_title\">Support Us</string>\n    <string name=\"home_item_source\">Source</string>\n    <string name=\"home_support_content\">Magisk is, and always will be, free, and open source. You can however show us that you care by making a donation.</string>\n    <string name=\"home_installed_version\">Installed</string>\n    <string name=\"home_latest_version\">Latest</string>\n    <string name=\"invalid_update_channel\">Invalid Update Channel</string>\n    <string name=\"uninstall_magisk_title\">Uninstall Magisk</string>\n    <string name=\"uninstall_magisk_msg\">All modules will be disabled/removed!\\nRoot will be removed!\\nAny internal storage unencrypted through the use of Magisk will be re-encrypted!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Preserve force encryption</string>\n    <string name=\"keep_dm_verity\">Preserve AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Recovery Mode</string>\n    <string name=\"install_options_title\">Options</string>\n    <string name=\"install_method_title\">Method</string>\n    <string name=\"install_next\">Next</string>\n    <string name=\"install_start\">Let\\'s go</string>\n    <string name=\"manager_download_install\">Press to download and install</string>\n    <string name=\"direct_install\">Direct Install (Recommended)</string>\n    <string name=\"install_inactive_slot\">Install to Inactive Slot (After OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Your device will be FORCED to boot to the current inactive slot after a reboot!\\nOnly use this option after OTA is done.\\nContinue?</string>\n    <string name=\"setup_title\">Additional Setup</string>\n    <string name=\"select_patch_file\">Select and Patch a File</string>\n    <string name=\"patch_file_msg\">Select a raw image (*.img) or an ODIN tarfile (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Rebooting in 5 seconds…</string>\n    <string name=\"flash_screen_title\">Installation</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Superuser Request</string>\n    <string name=\"touch_filtered_warning\">Because an app is obscuring a Superuser request, Magisk can\\'t verify your response</string>\n    <string name=\"deny\">Deny</string>\n    <string name=\"prompt\">Prompt</string>\n    <string name=\"grant\">Grant</string>\n    <string name=\"su_warning\">Grants full access to your device.\\nDeny if you\\'re not sure!</string>\n    <string name=\"forever\">Forever</string>\n    <string name=\"once\">Once</string>\n    <string name=\"tenmin\">10 mins</string>\n    <string name=\"twentymin\">20 mins</string>\n    <string name=\"thirtymin\">30 mins</string>\n    <string name=\"sixtymin\">60 mins</string>\n    <string name=\"su_allow_toast\">%1$s was granted Superuser rights</string>\n    <string name=\"su_deny_toast\">%1$s was denied Superuser rights</string>\n    <string name=\"su_snack_grant\">Superuser rights of %1$s are granted</string>\n    <string name=\"su_snack_deny\">Superuser rights of %1$s are denied</string>\n    <string name=\"su_snack_notif_on\">Notifications of %1$s are enabled</string>\n    <string name=\"su_snack_notif_off\">Notifications of %1$s are disabled</string>\n    <string name=\"su_snack_log_on\">Logging of %1$s is enabled</string>\n    <string name=\"su_snack_log_off\">Logging of %1$s is disabled</string>\n    <string name=\"su_revoke_title\">Revoke?</string>\n    <string name=\"su_revoke_msg\">Confirm to revoke %1$s Superuser rights</string>\n    <string name=\"toast\">Toast</string>\n    <string name=\"none\">None</string>\n\n    <string name=\"superuser_toggle_notification\">Notifications</string>\n    <string name=\"superuser_toggle_revoke\">Revoke</string>\n    <string name=\"superuser_policy_none\">No apps have asked for Superuser permission yet.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">You\\'re log-free, try using your root apps more</string>\n    <string name=\"log_data_magisk_none\">Magisk logs are empty, that\\'s weird</string>\n    <string name=\"menuSaveLog\">Save log</string>\n    <string name=\"menuClearLog\">Clear log now</string>\n    <string name=\"logs_cleared\">Log successfully cleared</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Target UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Show system apps</string>\n    <string name=\"show_os_app\">Show OS apps</string>\n    <string name=\"hide_filter_hint\">Filter by name</string>\n    <string name=\"hide_search\">Search</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(No info provided)</string>\n    <string name=\"reboot_userspace\">Soft reboot</string>\n    <string name=\"reboot_recovery\">Reboot to Recovery</string>\n    <string name=\"reboot_bootloader\">Reboot to Bootloader</string>\n    <string name=\"reboot_download\">Reboot to Download</string>\n    <string name=\"reboot_edl\">Reboot to EDL</string>\n    <string name=\"module_version_author\">%1$s by %2$s</string>\n    <string name=\"module_state_remove\">Remove</string>\n    <string name=\"module_state_restore\">Restore</string>\n    <string name=\"module_action_install_external\">Install from storage</string>\n    <string name=\"update_available\">Update Available</string>\n    <string name=\"suspend_text_riru\">Module suspended because %1$s is enabled</string>\n    <string name=\"suspend_text_zygisk\">Module suspended because %1$s is not enabled</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk module not loaded due to incompatibility</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Theme Mode</string>\n    <string name=\"settings_dark_mode_message\">Select mode which best suits your style!</string>\n    <string name=\"settings_dark_mode_light\">Always Light</string>\n    <string name=\"settings_dark_mode_system\">Follow System</string>\n    <string name=\"settings_dark_mode_dark\">Always Dark</string>\n    <string name=\"settings_download_path_title\">Download path</string>\n    <string name=\"settings_download_path_message\">Files will be saved to %1$s</string>\n    <string name=\"settings_hide_app_title\">Hide the Magisk app</string>\n    <string name=\"settings_hide_app_summary\">Install a proxy app with a random package ID and custom app label</string>\n    <string name=\"settings_restore_app_title\">Restore the Magisk app</string>\n    <string name=\"settings_restore_app_summary\">Unhide the app and restore the original APK</string>\n    <string name=\"language\">Language</string>\n    <string name=\"system_default\">(System Default)</string>\n    <string name=\"settings_check_update_title\">Check Updates</string>\n    <string name=\"settings_check_update_summary\">Periodically check for updates in the background</string>\n    <string name=\"settings_update_channel_title\">Update Channel</string>\n    <string name=\"settings_update_stable\">Stable</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Custom</string>\n    <string name=\"settings_update_custom_msg\">Insert a custom channel URL</string>\n    <string name=\"settings_zygisk_summary\">Run parts of Magisk in the zygote daemon</string>\n    <string name=\"settings_denylist_title\">Enforce DenyList</string>\n    <string name=\"settings_denylist_summary\">Processes on the denylist will have all Magisk modifications reverted</string>\n    <string name=\"settings_denylist_config_title\">Configure DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Select the processes to be included on the denylist</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Systemless hosts support for ad blocking apps</string>\n    <string name=\"settings_hosts_toast\">Added systemless hosts module</string>\n    <string name=\"settings_app_name_hint\">New name</string>\n    <string name=\"settings_app_name_helper\">App will be repackaged with this name</string>\n    <string name=\"settings_app_name_error\">Invalid format</string>\n    <string name=\"settings_su_app_adb\">Apps and ADB</string>\n    <string name=\"settings_su_app\">Apps only</string>\n    <string name=\"settings_su_adb\">ADB only</string>\n    <string name=\"settings_su_disable\">Disabled</string>\n    <string name=\"settings_su_request_10\">10 seconds</string>\n    <string name=\"settings_su_request_15\">15 seconds</string>\n    <string name=\"settings_su_request_20\">20 seconds</string>\n    <string name=\"settings_su_request_30\">30 seconds</string>\n    <string name=\"settings_su_request_45\">45 seconds</string>\n    <string name=\"settings_su_request_60\">60 seconds</string>\n    <string name=\"superuser_access\">Superuser Access</string>\n    <string name=\"auto_response\">Automatic Response</string>\n    <string name=\"request_timeout\">Request Timeout</string>\n    <string name=\"superuser_notification\">Superuser Notification</string>\n    <string name=\"settings_su_reauth_title\">Reauthenticate after upgrade</string>\n    <string name=\"settings_su_reauth_summary\">Ask for Superuser permissions again after upgrading apps</string>\n    <string name=\"settings_su_tapjack_title\">Tapjacking Protection</string>\n    <string name=\"settings_su_tapjack_summary\">The Superuser prompt dialog will not respond to input while obscured by any other window or overlay</string>\n    <string name=\"settings_customization\">Customization</string>\n    <string name=\"setting_add_shortcut_summary\">Add a pretty shortcut to the home screen in case the name and icon are difficult to recognize after hiding the app</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Workaround DNS poisoning in some nations</string>\n\n    <string name=\"multiuser_mode\">Multiuser Mode</string>\n    <string name=\"settings_owner_only\">Device Owner Only</string>\n    <string name=\"settings_owner_manage\">Device Owner Managed</string>\n    <string name=\"settings_user_independent\">User-Independent</string>\n    <string name=\"owner_only_summary\">Only owner has root access</string>\n    <string name=\"owner_manage_summary\">Only owner can manage root access and receive request prompts</string>\n    <string name=\"user_independent_summary\">Each user has their own separate root rules</string>\n\n    <string name=\"mount_namespace_mode\">Mount Namespace Mode</string>\n    <string name=\"settings_ns_global\">Global Namespace</string>\n    <string name=\"settings_ns_requester\">Inherit Namespace</string>\n    <string name=\"settings_ns_isolate\">Isolated Namespace</string>\n    <string name=\"global_summary\">All root sessions use the global mount namespace</string>\n    <string name=\"requester_summary\">Root sessions will inherit their requester\\'s namespace</string>\n    <string name=\"isolate_summary\">Each root session will have its own isolated namespace</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk Updates</string>\n    <string name=\"progress_channel\">Progress Notifications</string>\n    <string name=\"updated_channel\">Update Complete</string>\n    <string name=\"download_complete\">Download complete</string>\n    <string name=\"download_file_error\">Error downloading file</string>\n    <string name=\"magisk_update_title\">Magisk Update Available!</string>\n    <string name=\"updated_title\">Magisk Updated</string>\n    <string name=\"updated_text\">Tap to open app</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"repo_install_title\">Install %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Download</string>\n    <string name=\"reboot\">Reboot</string>\n    <string name=\"release_notes\">Release notes</string>\n    <string name=\"flashing\">Flashing…</string>\n    <string name=\"done\">Done!</string>\n    <string name=\"failure\">Failed!</string>\n    <string name=\"hide_app_title\">Hiding the Magisk app…</string>\n    <string name=\"open_link_failed_toast\">No app found to open the link</string>\n    <string name=\"complete_uninstall\">Complete Uninstall</string>\n    <string name=\"restore_img\">Restore Images</string>\n    <string name=\"restore_img_msg\">Restoring…</string>\n    <string name=\"restore_done\">Restoration done!</string>\n    <string name=\"restore_fail\">Stock backup does not exist!</string>\n    <string name=\"setup_fail\">Setup failed</string>\n    <string name=\"env_fix_title\">Requires Additional Setup</string>\n    <string name=\"env_fix_msg\">Your device needs additional setup for Magisk to work properly. Do you want to proceed and reboot?</string>\n    <string name=\"setup_msg\">Running environment setup…</string>\n    <string name=\"unsupport_magisk_title\">Unsupported Magisk Version</string>\n    <string name=\"unsupport_magisk_msg\">This version of the app does not support Magisk versions lower than %1$s.\\n\\nThe app will behave as if no Magisk is installed, please upgrade Magisk as soon as possible.</string>\n    <string name=\"unsupport_general_title\">Abnormal State</string>\n    <string name=\"unsupport_system_app_msg\">Running this app as a system app is not supported. Please revert the app to a user app.</string>\n    <string name=\"unsupport_other_su_msg\">A \\\"su\\\" binary not from Magisk has been detected. Please remove any competing root solution and/or reinstall Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk is installed to external storage. Please move the app to internal storage.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">The hidden Magisk app cannot continue to work because root was lost. Please restore the original APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Grant storage permission to enable this functionality</string>\n    <string name=\"install_unknown_denied\">Allow \"install unknown apps\" to enable this functionality</string>\n    <string name=\"add_shortcut_title\">Add shortcut to home screen</string>\n    <string name=\"add_shortcut_msg\">After hiding this app, its name and icon might become difficult to recognize. Do you want to add a pretty shortcut to the home screen?</string>\n    <string name=\"app_not_found\">No app found to handle this action</string>\n    <string name=\"reboot_apply_change\">Reboot to apply changes</string>\n    <string name=\"restore_app_confirmation\">This will restore the hidden app back to the original app. Do you really want to do this?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ta/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">தொகுதிகள்</string>\n    <string name=\"superuser\">சூப்பர் யூசர்</string>\n    <string name=\"logs\">பதிவுகள்</string>\n    <string name=\"settings\">அமைப்புகள்</string>\n    <string name=\"install\">நிறுவு</string>\n    <string name=\"section_home\">முகப்புப்பக்கம்</string>\n    <string name=\"section_theme\">வடிவமைப்புகள்</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">இணைப்பு கிடைக்கவில்லை</string>\n    <string name=\"app_changelog\">சேஞ்ச்லாக்</string>\n    <string name=\"loading\">ஏற்றுகிறது...</string>\n    <string name=\"update\">புதுப்பிப்பு</string>\n    <string name=\"not_available\">எதுவும் இல்லை</string>\n    <string name=\"hide\">பார்வையில் இருந்து மறைத்து வை</string>\n    <string name=\"home_package\">தொகுப்பு</string>\n    <string name=\"home_app_title\">செயலி</string>\n\n    <string name=\"home_notice_content\">அதிகாரப்பூர்வ கிட்ஹப் பக்கத்திலிருந்து மட்டுமே மேகிஸ்கை பதிவிறக்கவும். அறியப்படாத மூலங்களிலிருந்து வரும் கோப்புகள் தீங்கிழைக்கும்!</string>\n    <string name=\"home_support_title\">எங்களை ஆதரியுங்கள்</string>\n    <string name=\"home_item_source\">ஆரம்ப இடம்</string>\n    <string name=\"home_support_content\">மேஜிஸ்க் என்பது எப்போதும் இலவசமாகவும் திறந்த மூலமாகவும் இருக்கும். ஒரு சிறிய நன்கொடை அனுப்புவதன் மூலம் நீங்கள் அக்கறை காட்டுகிறீர்கள் என்பதை எங்களுக்குக் காட்டலாம்.</string>\n    <string name=\"home_installed_version\">நிறுவப்பட்டுள்ளது</string>\n    <string name=\"home_latest_version\">அண்மை</string>\n    <string name=\"invalid_update_channel\">புதுப்பிப்பு சேனல் தவறானது</string>\n    <string name=\"uninstall_magisk_title\">மேஜிஸ்க் நிறுவலை நீக்கு</string>\n    <string name=\"uninstall_magisk_msg\">அனைத்து தொகுதிகள் முடக்கப்படும்/அகற்றப்படும்!\\nரூட் அகற்றப்படும்!\\nஏற்கனவே இல்லையென்றால் உங்கள் தரவு குறியாக்கம் செய்யப்படலாம்!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">குறியாக்கத்தைப் பாதுகாக்கவும்</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verityயை பாதுகாக்கவும்</string>\n    <string name=\"recovery_mode\">மீட்பு செயல்முறை</string>\n    <string name=\"install_options_title\">விருப்பங்கள்</string>\n    <string name=\"install_method_title\">செயல்முறை</string>\n    <string name=\"install_next\">அடுத்தது</string>\n    <string name=\"install_start\">போகலாம்</string>\n    <string name=\"manager_download_install\">பதிவிறக்கி நிறுவ இந்த பொத்தானை அழுத்தவும்</string>\n    <string name=\"direct_install\">நேரடி நிறுவல் (பரிந்துரைக்கப்படுகிறது)</string>\n    <string name=\"install_inactive_slot\">செயலற்ற ஸ்லாட்டுக்கு நிறுவவும் (OTA க்குப் பிறகு)</string>\n    <string name=\"install_inactive_slot_msg\">மறுதொடக்கம் செய்தபின் தற்போதைய செயலற்ற ஸ்லாட்டுக்கு துவக்க உங்கள் சாதனம் கட்டாயப்படுத்தப்படும்!\\nOTA முடிந்தபின் மட்டுமே இந்த விருப்பத்தைப் பயன்படுத்தவும்.\\nதொடரவா?</string>\n    <string name=\"setup_title\">கூடுதல் அமைப்பு</string>\n    <string name=\"select_patch_file\">ஒரு கோப்பைத் தேர்ந்தெடுத்து ஒட்டவும்</string>\n    <string name=\"patch_file_msg\">மூல படம் (* .img) அல்லது ODIN டார்ஃபைல் (* .tar) ஐத் தேர்ந்தெடுக்கவும்</string>\n    <string name=\"reboot_delay_toast\">5 வினாடிகளில் மீண்டும் துவக்குகிறது…</string>\n    <string name=\"flash_screen_title\">நிறுவல்</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">சூப்பர் யூசர் கோரிக்கை</string>\n    <string name=\"touch_filtered_warning\">ஒரு பயன்பாடு ஒரு சூப்பர் யூசர் கோரிக்கையை மறைப்பதால், மேகிஸ்கால் உங்கள் பதிலைச் சரிபார்க்க முடியாது</string>\n    <string name=\"deny\">மறு</string>\n    <string name=\"prompt\">உடனடி</string>\n    <string name=\"grant\">வழங்கு</string>\n    <string name=\"su_warning\">உங்கள் சாதனத்திற்கு முழு அணுகலை வழங்குகிறது.\\nஉங்களுக்கு உறுதியாக தெரியவில்லை என்றால் மறுக்கவும்!</string>\n    <string name=\"forever\">என்றென்றும்</string>\n    <string name=\"once\">ஒருமுறை</string>\n    <string name=\"tenmin\">10 நிமிடங்கள்</string>\n    <string name=\"twentymin\">20 நிமிடங்கள்</string>\n    <string name=\"thirtymin\">30 நிமிடங்கள்</string>\n    <string name=\"sixtymin\">ஒரு மணி நேரம்</string>\n    <string name=\"su_allow_toast\">%1$s க்கு சூப்பர் யூசர் உரிமைகள் வழங்கப்பட்டன</string>\n    <string name=\"su_deny_toast\">%1$s க்கு சூப்பர் யூசர் உரிமைகள் மறுக்கப்பட்டன</string>\n    <string name=\"su_snack_grant\">%1$s இன் சூப்பர் யூசர் உரிமைகள் வழங்கப்படுகின்றன</string>\n    <string name=\"su_snack_deny\">%1$s இன் சூப்பர் யூசர் உரிமைகள் மறுக்கப்படுகின்றன</string>\n    <string name=\"su_snack_notif_on\">%1$s இன் அறிவிப்புகள் இயக்கப்பட்டன</string>\n    <string name=\"su_snack_notif_off\">%1$s இன் அறிவிப்புகள் முடக்கப்பட்டுள்ளன</string>\n    <string name=\"su_snack_log_on\">%1$s இன் பதிவுசெய்தல் இயக்கப்பட்டது</string>\n    <string name=\"su_snack_log_off\">%1$s இன் பதிவுசெய்தல் முடக்கப்பட்டுள்ளது</string>\n    <string name=\"su_revoke_title\">ரத்து செய்?</string>\n    <string name=\"su_revoke_msg\">%1$s உரிமைகளை ரத்து செய்ய உறுதிப்படுத்தவும்</string>\n    <string name=\"toast\">செய்தி</string>\n    <string name=\"none\">எதுவும் இல்லை</string>\n\n    <string name=\"superuser_toggle_notification\">அறிவிப்புகள்</string>\n    <string name=\"superuser_toggle_revoke\">ரத்து செய்</string>\n    <string name=\"superuser_policy_none\">எந்த பயன்பாடும் இதுவரை சூப்பர் யூசர் அனுமதி கேட்கவில்லை.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">பதிவு செய்யப்படாது, உங்கள் SU இயக்கப்பட்ட பயன்பாடுகளைப் பயன்படுத்த முயற்சிக்கவும்</string>\n    <string name=\"log_data_magisk_none\">மேஜிஸ்க் பதிவுகள் காலியாக உள்ளன, அது வித்தியாசமானது</string>\n    <string name=\"menuSaveLog\">பதிவைச் சேமிக்கவும்</string>\n    <string name=\"menuClearLog\">இப்போது பதிவை அழிக்கவும்</string>\n    <string name=\"logs_cleared\">Lபதிவு வெற்றிகரமாக அழிக்கப்பட்டது</string>\n    <string name=\"pid\">பிஐடி: %1$d</string>\n    <string name=\"target_uid\">இலக்கு பிஐடி: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">தொலைபேசி பயன்பாடுகளைக் காட்டு</string>\n    <string name=\"show_os_app\">OS பயன்பாடுகளைக் காட்டு</string>\n    <string name=\"hide_filter_hint\">பெயரால் வடிகட்டவும்</string>\n    <string name=\"hide_search\">தேடல்</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(எந்த தகவலும் வழங்கப்படவில்லை)</string>\n    <string name=\"reboot_userspace\">மென்மையான மறுதொடக்கம்</string>\n    <string name=\"reboot_recovery\">மீட்டெடுப்பிற்கு மறுதொடக்கம் செய்</string>\n    <string name=\"reboot_bootloader\">துவக்க ஏற்றிக்கு மீண்டும் துவக்கவும்</string>\n    <string name=\"reboot_download\">பதிவிறக்க பயன்முறையில் மீண்டும் துவக்கவும்</string>\n    <string name=\"reboot_edl\">EDL க்கு மறுதொடக்கம் செய்</string>\n    <string name=\"module_version_author\">%1$s மூலமாக %2$s</string>\n    <string name=\"module_state_remove\">அகற்று</string>\n    <string name=\"module_state_restore\">மீட்டமை</string>\n    <string name=\"module_action_install_external\">சேமிப்பிலிருந்து நிறுவவும்</string>\n    <string name=\"update_available\">புதுப்பிப்பு கிடைக்கிறது</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">தீம் பயன்முறை</string>\n    <string name=\"settings_dark_mode_message\">உங்கள் பாணிக்கு மிகவும் பொருத்தமான பயன்முறையைத் தேர்ந்தெடுக்கவும்!</string>\n    <string name=\"settings_dark_mode_light\">எப்போதும் ஒளி</string>\n    <string name=\"settings_dark_mode_system\">தொலைபேசி அமைப்புகளைப் பின்பற்றவும்</string>\n    <string name=\"settings_dark_mode_dark\">எப்போதும் இருண்டது</string>\n    <string name=\"settings_download_path_title\">பதிவிறக்க இடம்</string>\n    <string name=\"settings_download_path_message\">%1$s ல் கோப்புகள் சேமிக்கப்படும்</string>\n    <string name=\"settings_hide_app_title\">மேஜிஸ்க் செயலியை மறைக்கவும்</string>\n    <string name=\"settings_hide_app_summary\">சீரற்ற தொகுப்பு ஐடி மற்றும் தனிப்பயன் பயன்பாட்டு லேபிளுடன் ப்ராக்ஸி பயன்பாட்டை நிறுவவும்</string>\n    <string name=\"settings_restore_app_title\">மேஜிஸ்க் பயன்பாட்டை மீட்டமை</string>\n    <string name=\"settings_restore_app_summary\">பயன்பாட்டை மறைத்து, அசல் APK க்கு மீண்டும் மீட்டமைக்கவும்</string>\n    <string name=\"language\">மொழி</string>\n    <string name=\"system_default\">(தொலைபேசியின் இயல்புநிலை)</string>\n    <string name=\"settings_check_update_title\">புதுப்பிப்புகளைச் சரிபார்க்கவும்</string>\n    <string name=\"settings_check_update_summary\">பின்னணியில் புதுப்பிப்புகளை அவ்வப்போது சரிபார்க்கவும்</string>\n    <string name=\"settings_update_channel_title\">புதுப்பிப்பு வழி</string>\n    <string name=\"settings_update_stable\">நிலையானது</string>\n    <string name=\"settings_update_beta\">தற்காலிகமானது</string>\n    <string name=\"settings_update_custom\">தனிப்பயன் வழி</string>\n    <string name=\"settings_update_custom_msg\">தனிப்பயன் URL ஐ உள்ளிடவும்</string>\n    <string name=\"settings_hosts_title\">கணினி இல்லாத ஹோஸ்ட்கள்</string>\n    <string name=\"settings_hosts_summary\">கணினி இல்லாத ஹோஸ்ட்கள் Adblock பயன்பாடுகளுக்கு ஆதரவளிக்கின்றன</string>\n    <string name=\"settings_hosts_toast\">கணினி இல்லாத ஹோஸ்ட்கள் தொகுதி சேர்க்கப்பட்டது</string>\n    <string name=\"settings_app_name_hint\">புதிய பெயர்</string>\n    <string name=\"settings_app_name_helper\">பயன்பாடு இந்த பெயருக்கு மீண்டும் தொகுக்கப்படும்</string>\n    <string name=\"settings_app_name_error\">தவறான வடிவம்</string>\n    <string name=\"settings_su_app_adb\">பயன்பாடுகள் மற்றும் ADB</string>\n    <string name=\"settings_su_app\">பயன்பாடுகள் மட்டும்</string>\n    <string name=\"settings_su_adb\">ADB மட்டும்</string>\n    <string name=\"settings_su_disable\">முடக்கப்பட்டது</string>\n    <string name=\"settings_su_request_10\">10 வினாடிகள்</string>\n    <string name=\"settings_su_request_15\">15 வினாடிகள்</string>\n    <string name=\"settings_su_request_20\">20 வினாடிகள்</string>\n    <string name=\"settings_su_request_30\">30 வினாடிகள்</string>\n    <string name=\"settings_su_request_45\">45 வினாடிகள்</string>\n    <string name=\"settings_su_request_60\">60 வினாடிகள்</string>\n    <string name=\"superuser_access\">சூப்பர் யூசர் அணுகல்</string>\n    <string name=\"auto_response\">தானியங்கி பதில்</string>\n    <string name=\"request_timeout\">கோரிக்கை முடிவு நேரம்</string>\n    <string name=\"superuser_notification\">சூப்பர் யூசர் அறிவிப்பு</string>\n    <string name=\"settings_su_reauth_title\">மேம்படுத்தப்பட்ட பிறகு மீண்டும் அங்கீகரிக்கவும்</string>\n    <string name=\"settings_su_reauth_summary\">பயன்பாட்டு மேம்படுத்தல்களுக்குப் பிறகு சூப்பர் யூசர் அனுமதிகளை மீண்டும் அங்கீகரிக்கவும்</string>\n    <string name=\"settings_su_tapjack_title\">டாப்ஜாகிங் பாதுகாப்பை இயக்கு</string>\n    <string name=\"settings_su_tapjack_summary\">வேறு எந்த சாளரத்தாலும் அல்லது மேலடுக்கினாலும் மறைக்கப்படும்போது சூப்பர் யூசர் வரியில் உரையாடல் உள்ளீட்டிற்கு பதிலளிக்காது</string>\n    <string name=\"settings_customization\">தனிப்பயனாக்கம்</string>\n    <string name=\"setting_add_shortcut_summary\">பயன்பாட்டை மறைத்தபின் பெயர் மற்றும் ஐகான் அடையாளம் காண கடினமாக இருந்தால் முகப்புத் திரையில் அழகான குறுக்குவழியைச் சேர்க்கவும்</string>\n    <string name=\"settings_doh_title\">HTTPS வழியாக டி.என்.எஸ்</string>\n    <string name=\"settings_doh_description\">சில நாடுகளில் டி.என்.எஸ் பணிபுரியும்</string>\n\n    <string name=\"multiuser_mode\">மல்டியூசர் பயன்முறை</string>\n    <string name=\"settings_owner_only\">சாதன உரிமையாளர் மட்டுமே</string>\n    <string name=\"settings_owner_manage\">சாதன உரிமையாளர் நிர்வகிக்கப்பட்டார்</string>\n    <string name=\"settings_user_independent\">பயனர்-சுதந்திரம்</string>\n    <string name=\"owner_only_summary\">உரிமையாளருக்கு மட்டுமே ரூட் அணுகல் உள்ளது</string>\n    <string name=\"owner_manage_summary\">உரிமையாளர் மட்டுமே ரூட் அணுகலை நிர்வகிக்க முடியும்</string>\n    <string name=\"user_independent_summary\">ஒவ்வொரு பயனருக்கும் அவரவர் / அவளுக்கு தனித்தனி ரூட் விதிகள் உள்ளன</string>\n\n    <string name=\"mount_namespace_mode\">நேம்ஸ்பேஸ் பயன்முறையை சேர்</string>\n    <string name=\"settings_ns_global\">உலகளாவிய பெயர்வெளி</string>\n    <string name=\"settings_ns_requester\">பெயர்வெளி மரபுரிமை</string>\n    <string name=\"settings_ns_isolate\">தனிமைப்படுத்தப்பட்ட பெயர்வெளி</string>\n    <string name=\"global_summary\">அனைத்து ரூட் அமர்வுகளும் உலகளாவிய மவுண்ட் பெயர்வெளியைப் பயன்படுத்துகின்றன</string>\n    <string name=\"requester_summary\">ரூட் அமர்வுகள் அவற்றின் கோரிக்கையாளரின் பெயர்வெளியைப் பெறும்</string>\n    <string name=\"isolate_summary\">ஒவ்வொரு ரூட் அமர்வுக்கும் அதன் தனிமைப்படுத்தப்பட்ட பெயர்வெளி இருக்கும்</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">மேஜிக் புதுப்பிப்புகள்</string>\n    <string name=\"progress_channel\">முன்னேற்ற அறிவிப்புகள்</string>\n    <string name=\"download_complete\">பதிவிறக்கம் முடிந்தது</string>\n    <string name=\"download_file_error\">கோப்பை பதிவிறக்குவதில் பிழை</string>\n    <string name=\"magisk_update_title\">மேஜிஸ்க் புதுப்பிப்பு கிடைக்கிறது!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">ஆம்</string>\n    <string name=\"no\">இல்லை</string>\n    <string name=\"repo_install_title\">நிறுவு %1$s %2$s(%3$d)</string>\n    <string name=\"download\">பதிவிறக்கம்</string>\n    <string name=\"reboot\">மறுதொடக்கம்</string>\n    <string name=\"release_notes\">வெளியீட்டு குறிப்புகள்</string>\n    <string name=\"flashing\">ஏற்றுகிறது…</string>\n    <string name=\"done\">முடிந்தது!</string>\n    <string name=\"failure\">தோல்வி!</string>\n    <string name=\"hide_app_title\">மேஜிஸ்க் பயன்பாட்டை மறைக்கிறது…</string>\n    <string name=\"open_link_failed_toast\">இணைப்பைத் திறக்க எந்த பயன்பாடும் கிடைக்கவில்லை</string>\n    <string name=\"complete_uninstall\">நிறுவல் நீக்குதல்</string>\n    <string name=\"restore_img\">படங்களை மீட்டமை</string>\n    <string name=\"restore_img_msg\">மீட்டமைக்கிறது…</string>\n    <string name=\"restore_done\">மறுசீரமைப்பு செய்யப்பட்டது!</string>\n    <string name=\"restore_fail\">அசல் பிரதி இல்லை!</string>\n    <string name=\"setup_fail\">அமைப்பு தோல்வியுற்றது</string>\n    <string name=\"env_fix_title\">கூடுதல் அமைப்பு தேவை</string>\n    <string name=\"env_fix_msg\">மேகிஸ்க் சரியாக வேலை செய்ய உங்கள் சாதனத்திற்கு கூடுதல் அமைப்பு தேவை. மறுதொடக்கம் செய்ய விரும்புகிறீர்களா??</string>\n    <string name=\"setup_msg\">சூழல் அமைப்பை இயக்குகிறது…</string>\n    <string name=\"unsupport_magisk_title\">ஆதரிக்கப்படாத மேஜிஸ்க் பதிப்பு</string>\n    <string name=\"unsupport_magisk_msg\">பயன்பாட்டின் இந்த பதிப்பு மேஜிஸ்க் பதிப்பை %1$s ஐ விடக் குறைவாக ஆதரிக்காது.</string>\n    <string name=\"external_rw_permission_denied\">இந்த செயல்பாட்டை இயக்க சேமிப்பக அனுமதி வழங்கவும்</string>\n    <string name=\"add_shortcut_title\">முகப்புத் திரையில் குறுக்குவழியைச் சேர்க்கவும்</string>\n    <string name=\"add_shortcut_msg\">இந்த பயன்பாட்டை மறைத்த பிறகு, அதன் பெயர் மற்றும் ஐகானை அடையாளம் காண்பது கடினமாகிவிடும். முகப்புத் திரையில் அழகான குறுக்குவழியைச் சேர்க்க விரும்புகிறீர்களா??</string>\n    <string name=\"app_not_found\">இந்த செயலைக் கையாள எந்த பயன்பாடும் கிடைக்கவில்லை</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-th/strings.xml",
    "content": "<resources>\n\n    <!--Welcome Activity-->\n    <string name=\"modules\">โมดูล</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">บันทึก</string>\n    <string name=\"settings\">ตั้งค่า</string>\n    <string name=\"install\">ติดตั้ง</string>\n\n    <!--Status Fragment-->\n    <string name=\"invalid_update_channel\">ช่องทางการอัปเดตไม่ถูกต้อง</string>\n    <string name=\"keep_force_encryption\">เก็บค่าบังคับการเข้ารหัส</string>\n    <string name=\"keep_dm_verity\">เก็บค่า AVB 2.0/dm-verity</string>\n    <string name=\"uninstall_magisk_title\">ถอนการติดตั้ง Magisk</string>\n    <string name=\"uninstall_magisk_msg\">โมดูลทั้งหมดจะถูกปิดการใช้งาน/ถูกลบ, สิทธิการเข้าถึง Root จะถูกลบ และข้อมูลของคุณอาจถูกเข้ารหัส</string>\n    <string name=\"update\">อัปเดต</string>\n\n    <!--Module Fragment-->\n    <string name=\"no_info_provided\">(ไม่มีข้อมูล)</string>\n    <string name=\"reboot_recovery\">รีบู๊ตไปโหมดกู้คืน</string>\n    <string name=\"reboot_bootloader\">รีบู๊ตไป Bootloader</string>\n    <string name=\"reboot_download\">รีบู๊ตไป Download</string>\n\n    <!--Repo Fragment-->\n    <string name=\"update_available\">มีการอัปเดต</string>\n    <string name=\"home_installed_version\">ติดตั้งแล้ว</string>\n\n    <!--Log Fragment-->\n    <string name=\"menuSaveLog\">เก็บบันทึก</string>\n    <string name=\"menuClearLog\">ล้างบันทึก</string>\n    <string name=\"logs_cleared\">บันทึกถูกล้างแล้ว</string>\n\n    <!--About Activity-->\n    <string name=\"app_changelog\">สิ่งที่เพิ่มใหม่</string>\n\n    <!-- System Components, Notifications -->\n    <string name=\"update_channel\">การอัปเดต Magisk</string>\n    <string name=\"progress_channel\">สถานะ</string>\n    <string name=\"download_complete\">ดาวน์โหลดสำเร็จ</string>\n    <string name=\"download_file_error\">เกิดข้อผิดพลาดในการดาวน์โหลดไฟล์</string>\n    <string name=\"magisk_update_title\">มีการอัปเดต Magisk!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"repo_install_title\">ติดตั้ง %1$s %2$s(%3$d)</string>\n    <string name=\"download\">ดาวน์โหลด</string>\n    <string name=\"reboot\">รีบู๊ต</string>\n    <string name=\"release_notes\">ข้อมูลเพิ่มเติม</string>\n    <string name=\"manager_download_install\">แตะเพื่อดาวน์โหลดและติดตั้ง</string>\n    <string name=\"flashing\">กำลังแฟลช</string>\n    <string name=\"open_link_failed_toast\">ไม่พบแอปพลิเคชันสำหรับเปิดลิ้งค์</string>\n    <string name=\"direct_install\">การติดตั้งโดยตรง (แนะนำ)</string>\n    <string name=\"install_inactive_slot\">ติดตั้งไปยัง Slot ที่ไม่ใช้งาน (หลังจาก OTA)</string>\n    <string name=\"install_inactive_slot_msg\">อุปกรณ์ของคุณจะถูกบังคับให้บู๊ตไป Slot ที่ไม่ใช่งานหลังจากรีบูต!\\nใช้ตัวเลือกนี้หลังจาก OTA เสร็จเท่านั้น\\nดำเนินการต่อ?</string>\n    <string name=\"complete_uninstall\">ถอนการติดตั้งแบบสมบูรณ์</string>\n    <string name=\"restore_img\">กู้คืน Images</string>\n    <string name=\"restore_img_msg\">กำลังกู้คืน…</string>\n    <string name=\"restore_done\">การกู้คืนสำเร็จ!</string>\n    <string name=\"restore_fail\">ไม่มีไฟล์แบ็คอัพ!</string>\n    <string name=\"setup_fail\">การตั้งค่าล้มเหลว</string>\n    <string name=\"env_fix_title\">ต้องมีการตั้งค่าเพิ่มเติม</string>\n    <string name=\"setup_title\">การตั้งค่าเพิ่มเติม</string>\n    <string name=\"setup_msg\">กำลังรันการตั้งค่า…</string>\n\n    <!--Settings Activity -->\n    <string name=\"language\">ภาษา</string>\n    <string name=\"system_default\">(มาตรฐานระบบ)</string>\n    <string name=\"settings_check_update_title\">ตรวจสอบการอัปเดต</string>\n    <string name=\"settings_check_update_summary\">ตรวจสอบการอัปเดตเป็นระยะในพื้นหลัง</string>\n    <string name=\"settings_update_channel_title\">ช่องทางการอัปเดต</string>\n    <string name=\"settings_update_stable\">เสถียร</string>\n    <string name=\"settings_update_beta\">เบต้า</string>\n    <string name=\"settings_update_custom\">กำหนดเอง</string>\n    <string name=\"settings_update_custom_msg\">ใส่ URL</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">การรองรับ Systemless hosts เพื่อแอพ Adblock ต่างๆ</string>\n    <string name=\"settings_hosts_toast\">ทำการเพิ่มโมดูล systemless hosts แล้ว</string>\n\n    <string name=\"settings_su_app_adb\">แอปและ ADB</string>\n    <string name=\"settings_su_app\">แอปเท่านั้น</string>\n    <string name=\"settings_su_adb\">ADB เท่านั้น</string>\n    <string name=\"settings_su_disable\">ปิด</string>\n    <string name=\"settings_su_request_10\">10 วินาที</string>\n    <string name=\"settings_su_request_15\">15 วินาที</string>\n    <string name=\"settings_su_request_20\">20 วินาที</string>\n    <string name=\"settings_su_request_30\">30 วินาที</string>\n    <string name=\"settings_su_request_45\">45 วินาที</string>\n    <string name=\"settings_su_request_60\">60 วินาที</string>\n    <string name=\"superuser_access\">การเข้าถึง Superuser</string>\n    <string name=\"auto_response\">การตอบกลับ</string>\n    <string name=\"request_timeout\">เวลาการขอใช้งาน</string>\n    <string name=\"superuser_notification\">การแจ้งเตือน Superuser</string>\n    <string name=\"settings_su_reauth_title\">ขอสิทธิ์ใหม่หลังจากอัปเกรด</string>\n    <string name=\"settings_su_reauth_summary\">ขอสิทธิ์ superuser ใหม่หลังจากแอปถูกอัปเกรด</string>\n\n    <string name=\"multiuser_mode\">โหมดผู้ใช้หลายคน</string>\n    <string name=\"settings_owner_only\">เจ้าของอุปกรณ์เท่านั้น</string>\n    <string name=\"settings_owner_manage\">จัดการโดยเจ้าของอุปกรณ์</string>\n    <string name=\"settings_user_independent\">ตามผู้ใช้</string>\n    <string name=\"owner_only_summary\">เฉพาะเจ้าของที่มีสิทธิ์การเข้าถึง root</string>\n    <string name=\"owner_manage_summary\">เจ้าของสามารถจัดการสิทธิ์การเข้าถึง root</string>\n    <string name=\"user_independent_summary\">ผู้ใช้แต่ละคนสามารถจัดการกฏของตนเอง</string>\n\n    <string name=\"mount_namespace_mode\">Mount Namespace Mode</string>\n    <string name=\"settings_ns_global\">Global Namespace</string>\n    <string name=\"settings_ns_requester\">Inherit Namespace</string>\n    <string name=\"settings_ns_isolate\">Isolated Namespace</string>\n    <string name=\"global_summary\">All root sessions use the global mount namespace.</string>\n    <string name=\"requester_summary\">Root sessions will inherit their requester\\'s namespace.</string>\n    <string name=\"isolate_summary\">Each root session will have its own isolated namespace.</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">การขอเข้าถึง Superuser</string>\n    <string name=\"deny\">ปฏิเสธ</string>\n    <string name=\"prompt\">ถาม</string>\n    <string name=\"grant\">อนุญาต</string>\n    <string name=\"su_warning\">จะให้การเข้าถึงอุปกรณ์ในทุกรูปแบบ กรุณาปฏิเสธหากคุณไม่มั่นใจ!</string>\n    <string name=\"forever\">ตลอดกาล</string>\n    <string name=\"once\">ครั้งเดียว</string>\n    <string name=\"tenmin\">10 นาที</string>\n    <string name=\"twentymin\">20 นาที</string>\n    <string name=\"thirtymin\">30 นาที</string>\n    <string name=\"sixtymin\">60 นาที</string>\n    <string name=\"su_allow_toast\">%1$s ถูกอนุญาตสิทธิ์ Superuser</string>\n    <string name=\"su_deny_toast\">%1$s ถูกปฏิเสธสิทธิ์ Superuser</string>\n    <string name=\"su_snack_grant\">สิทธิ์ Superuser ของ %1$s ถูกอนุญาต</string>\n    <string name=\"su_snack_deny\">สิทธิ์ Superuser ของ %1$s ถูกปฏิเสธ</string>\n    <string name=\"su_snack_notif_on\">การแจ้งเตือนของ %1$s ถูกเปิด</string>\n    <string name=\"su_snack_notif_off\">การแจ้งเตือนของ %1$s ถูกปิด</string>\n    <string name=\"su_snack_log_on\">การบันทึก %1$s ถูกเปิด</string>\n    <string name=\"su_snack_log_off\">การบันทึก %1$s ถูกปิด</string>\n    <string name=\"su_revoke_title\">ลบสิทธิ์?</string>\n    <string name=\"su_revoke_msg\">ยืนยันการลบสิทธิ์ของ %1$s?</string>\n    <string name=\"toast\">การเตือน</string>\n    <string name=\"none\">ไม่มี</string>\n\n    <!--Superuser logs-->\n    <string name=\"target_uid\">UID เป้าหมาย: %1$d</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-tr/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Modüller</string>\n    <string name=\"superuser\">Süper Kullanıcı</string>\n    <string name=\"logs\">Günlükler</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"install\">Kur</string>\n    <string name=\"section_home\">Ana Sayfa</string>\n    <string name=\"section_theme\">Temalar</string>\n    <string name=\"denylist\">Red Listesi</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Bağlantı Yok</string>\n    <string name=\"app_changelog\">Değişiklik Günlüğü</string>\n    <string name=\"loading\">Yükleniyor…</string>\n    <string name=\"update\">Güncelle</string>\n    <string name=\"not_available\">Mevcut Değil</string>\n    <string name=\"hide\">Gizle</string>\n    <string name=\"home_package\">Paket</string>\n    <string name=\"home_app_title\">Uygulama</string>\n    <string name=\"home_notice_content\">Magisk\\'i YALNIZCA resmi GitHub sayfasından indirin. Bilinmeyen kaynaklardan gelen dosyalar kötü amaçlı olabilir!</string>\n    <string name=\"home_support_title\">Bize Destek Olun</string>\n    <string name=\"home_follow_title\">Bizi Takip Edin</string>\n    <string name=\"home_item_source\">Kaynak</string>\n    <string name=\"home_support_content\">Magisk ücretsizdir, açık kaynaklıdır ve her zaman öyle kalacaktır. Ancak, bağış yaparak bize değer verdiğinizi gösterebilirsiniz.</string>\n    <string name=\"home_installed_version\">Yüklü</string>\n    <string name=\"home_latest_version\">En Son</string>\n    <string name=\"invalid_update_channel\">Geçersiz güncelleme kanalı</string>\n    <string name=\"uninstall_magisk_title\">Magisk\\'i Kaldır</string>\n    <string name=\"uninstall_magisk_msg\">Tüm modüller devre dışı bırakılacak/kaldırılacak!\\nRoot kaldırılacak!\\nMagisk kullanılarak şifresi çözülen dahili depolama birimleri yeniden şifrelenecek!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Zorunlu Şifrelemeyi Koru</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity Koru</string>\n    <string name=\"recovery_mode\">Kurtarma Modu</string>\n    <string name=\"install_options_title\">Seçenekler</string>\n    <string name=\"install_method_title\">Yöntem</string>\n    <string name=\"install_next\">Sonraki</string>\n    <string name=\"install_start\">Hadi Başlayalım</string>\n    <string name=\"manager_download_install\">İndirmek ve kurmak için basın</string>\n    <string name=\"direct_install\">Doğrudan kurulum (Önerilen)</string>\n    <string name=\"install_inactive_slot\">Etkin olmayan slota kur (OTA sonrası)</string>\n    <string name=\"install_inactive_slot_msg\">Cihazınız, yeniden başlattıktan sonra mevcut etkin olmayan slota önyükleme yapmaya ZORLANACAKTIR!\\nBu seçeneği yalnızca OTA güncellemesi yapıldıktan sonra kullanın.\\nDevam edilsin mi?</string>\n    <string name=\"setup_title\">Ek Kurulum</string>\n    <string name=\"select_patch_file\">Bir dosya seçin ve yamalayın</string>\n    <string name=\"patch_file_msg\">Ham bir imaj (*.img), bir ODIN tar dosyası (*.tar) veya bir payload.bin (*.bin) seçin</string>\n    <string name=\"reboot_delay_toast\">5 saniye içinde yeniden başlatılıyor…</string>\n    <string name=\"flash_screen_title\">Kuruluyor</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Süper Kullanıcı İsteği</string>\n    <string name=\"touch_filtered_warning\">Bir uygulama Süper Kullanıcı isteğini engellediği için Magisk yanıtınızı doğrulayamıyor.</string>\n    <string name=\"deny\">Reddet</string>\n    <string name=\"prompt\">Sor</string>\n    <string name=\"restrict\">Kısıtla</string>\n    <string name=\"grant\">İzin Ver</string>\n    <string name=\"su_warning\">Cihazınıza tam erişim sağlar.\\nEmin değilseniz reddedin!</string>\n    <string name=\"forever\">Her Zaman</string>\n    <string name=\"once\">Bir Kez</string>\n    <string name=\"tenmin\">10 Dakika</string>\n    <string name=\"twentymin\">20 Dakika</string>\n    <string name=\"thirtymin\">30 Dakika</string>\n    <string name=\"sixtymin\">60 Dakika</string>\n    <string name=\"su_allow_toast\">%1$s uygulamasına Süper Kullanıcı hakları verildi</string>\n    <string name=\"su_deny_toast\">%1$s uygulamasının Süper Kullanıcı hakları reddedildi</string>\n    <string name=\"su_snack_grant\">%1$s uygulamasının Süper Kullanıcı hakları verildi</string>\n    <string name=\"su_snack_deny\">%1$s uygulamasının Süper Kullanıcı hakları reddedildi</string>\n    <string name=\"su_snack_notif_on\">%1$s bildirimleri etkinleştirildi</string>\n    <string name=\"su_snack_notif_off\">%1$s bildirimleri devre dışı bırakıldı</string>\n    <string name=\"su_snack_log_on\">%1$s için günlük kaydı etkinleştirildi</string>\n    <string name=\"su_snack_log_off\">%1$s için günlük kaydı devre dışı bırakıldı</string>\n    <string name=\"su_revoke_title\">İptal Edilsin mi?</string>\n    <string name=\"su_revoke_msg\">%1$s uygulamasının Süper Kullanıcı haklarını iptal etmeyi onaylayın</string>\n    <string name=\"toast\">Bildirim Penceresi</string>\n    <string name=\"none\">Yok</string>\n    <string name=\"superuser_toggle_notification\">Bildirimler</string>\n    <string name=\"superuser_toggle_revoke\">İptal Et</string>\n    <string name=\"superuser_policy_none\">Henüz hiçbir uygulama Süper Kullanıcı izni istemedi.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Günlüğünüz temiz. Root uygulamalarınızı daha fazla kullanmayı deneyin.</string>\n    <string name=\"log_data_magisk_none\">Magisk günlükleri boş, bu garip.</string>\n    <string name=\"menuSaveLog\">Günlüğü Kaydet</string>\n    <string name=\"menuClearLog\">Günlüğü Şimdi Temizle</string>\n    <string name=\"logs_cleared\">Günlük başarıyla temizlendi</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Hedef UID: %1$d</string>\n    <string name=\"target_pid\">Hedef PID: %s</string>\n    <string name=\"selinux_context\">SELinux bağlamı: %s</string>\n    <string name=\"supp_group\">Ek grup: %s</string>\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Sistem Uygulamalarını Göster</string>\n    <string name=\"show_os_app\">İS Uygulamalarını Göster</string>\n    <string name=\"hide_filter_hint\">Ada göre filtrele</string>\n    <string name=\"hide_search\">Ara</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Bilgi sağlanmadı)</string>\n    <string name=\"reboot_userspace\">Hızlı Yeniden Başlat</string>\n    <string name=\"reboot_recovery\">Kurtarma Modunda Yeniden Başlat</string>\n    <string name=\"reboot_bootloader\">Önyükleyici Modunda Yeniden Başlat</string>\n    <string name=\"reboot_download\">Download Modunda Yeniden Başlat</string>\n    <string name=\"reboot_edl\">EDL Modunda Yeniden Başlat</string>\n    <string name=\"reboot_safe_mode\">Güvenli Mod</string>\n    <string name=\"module_version_author\">%1$s, Geliştirici: %2$s</string>\n    <string name=\"module_state_remove\">Kaldır</string>\n    <string name=\"module_action\">Eylem</string>\n    <string name=\"module_state_restore\">Geri Yükle</string>\n    <string name=\"module_action_install_external\">Depolamadan yükle</string>\n    <string name=\"update_available\">Güncelleme mevcut</string>\n    <string name=\"suspend_text_riru\">Modül, %1$s etkin olduğu için askıya alındı</string>\n    <string name=\"suspend_text_zygisk\">Modül, %1$s etkin olmadığı için askıya alındı</string>\n    <string name=\"zygisk_module_unloaded\">Zygisk modülü uyumsuzluk nedeniyle yüklenmedi</string>\n    <string name=\"module_empty\">Yüklü modül yok</string>\n    <string name=\"confirm_install\">%1$s modülü yüklensin mi?</string>\n    <string name=\"confirm_install_title\">Yükleme Onayı</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Tema Modu</string>\n    <string name=\"settings_dark_mode_message\">Tarzınıza en uygun modu seçin!</string>\n    <string name=\"settings_dark_mode_light\">Her Zaman Aydınlık</string>\n    <string name=\"settings_dark_mode_system\">Sistemi Takip Et</string>\n    <string name=\"settings_dark_mode_dark\">Her Zaman Karanlık</string>\n    <string name=\"settings_download_path_title\">İndirme Yolu</string>\n    <string name=\"settings_download_path_message\">Dosyalar %1$s konumuna kaydedilecek</string>\n    <string name=\"settings_hide_app_title\">Magisk Uygulamasını Gizle</string>\n    <string name=\"settings_hide_app_summary\">Rastgele bir paket kimliği ve özel uygulama etiketi ile bir proxy uygulaması yükleyin</string>\n    <string name=\"settings_restore_app_title\">Magisk Uygulamasını Geri Yükle</string>\n    <string name=\"settings_restore_app_summary\">Uygulamanın gizliliğini kaldırın ve orijinal APK\\'yi geri yükleyin</string>\n    <string name=\"language\">Dil</string>\n    <string name=\"system_default\">(Sistem Varsayılanı)</string>\n    <string name=\"settings_check_update_title\">Güncellemeleri Kontrol Et</string>\n    <string name=\"settings_check_update_summary\">Arka planda periyodik olarak güncellemeleri kontrol et</string>\n    <string name=\"settings_update_channel_title\">Güncelleme Kanalı</string>\n    <string name=\"settings_update_stable\">Kararlı</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_debug\">Hata Ayıklama</string>\n    <string name=\"settings_update_custom\">Özel</string>\n    <string name=\"settings_update_custom_msg\">Özel bir kanal URL\\'si girin</string>\n    <string name=\"settings_zygisk_summary\">Magisk\\'in bazı kısımlarını Zygote daemon içinde çalıştırın</string>\n    <string name=\"settings_denylist_title\">Red Listesini Uygula</string>\n    <string name=\"settings_denylist_summary\">Red listesindeki işlemlerin tüm Magisk değişiklikleri geri alınacak</string>\n    <string name=\"settings_denylist_config_title\">Red Listesini Yapılandır</string>\n    <string name=\"settings_denylist_config_summary\">Red listesine dahil edilecek işlemleri seçin</string>\n    <string name=\"settings_hosts_title\">Sistemsiz Hosts</string>\n    <string name=\"settings_hosts_summary\">Reklam engelleme uygulamaları için sistemsiz hosts desteği</string>\n    <string name=\"settings_hosts_toast\">Sistemsiz hosts modülü eklendi</string>\n    <string name=\"settings_app_name_hint\">Yeni ad</string>\n    <string name=\"settings_app_name_helper\">Uygulama bu adla yeniden paketlenecek</string>\n    <string name=\"settings_app_name_error\">Geçersiz format</string>\n    <string name=\"settings_su_app_adb\">Uygulamalar ve ADB</string>\n    <string name=\"settings_su_app\">Sadece Uygulamalar</string>\n    <string name=\"settings_su_adb\">Sadece ADB</string>\n    <string name=\"settings_su_disable\">Devre Dışı</string>\n    <string name=\"settings_su_request_10\">10 Saniye</string>\n    <string name=\"settings_su_request_15\">15 Saniye</string>\n    <string name=\"settings_su_request_20\">20 Saniye</string>\n    <string name=\"settings_su_request_30\">30 Saniye</string>\n    <string name=\"settings_su_request_45\">45 Saniye</string>\n    <string name=\"settings_su_request_60\">60 Saniye</string>\n    <string name=\"superuser_access\">Süper Kullanıcı Erişimi</string>\n    <string name=\"auto_response\">Otomatik Yanıt</string>\n    <string name=\"request_timeout\">İstek Zaman Aşımı</string>\n    <string name=\"superuser_notification\">Süper Kullanıcı Bildirimi</string>\n    <string name=\"settings_su_reauth_title\">Yükseltmeden Sonra Yeniden Kimlik Doğrula</string>\n    <string name=\"settings_su_reauth_summary\">Uygulamaları yükselttikten sonra tekrar Süper Kullanıcı izinlerini iste</string>\n    <string name=\"settings_su_tapjack_title\">Dokunma Saldırısı Koruması</string>\n    <string name=\"settings_su_tapjack_summary\">Süper Kullanıcı istek penceresi, başka bir pencere veya katman tarafından engellendiğinde girdilere yanıt vermeyecektir</string>\n    <string name=\"settings_su_auth_title\">Kullanıcı Kimlik Doğrulaması</string>\n    <string name=\"settings_su_auth_summary\">Süper Kullanıcı istekleri sırasında kullanıcı kimlik doğrulaması iste</string>\n    <string name=\"settings_su_auth_insecure\">Cihazda yapılandırılmış bir kimlik doğrulama yöntemi yok</string>\n    <string name=\"settings_su_restrict_title\">Root Yeteneklerini Kısıtla</string>\n    <string name=\"settings_su_restrict_summary\">Yeni Süper Kullanıcı uygulamalarını varsayılan olarak kısıtlayacaktır. Uyarı: Bu, çoğu uygulamayı bozacaktır. Ne yaptığınızı bilmiyorsanız etkinleştirmeyin.</string>\n    <string name=\"settings_customization\">Özelleştirme</string>\n    <string name=\"setting_add_shortcut_summary\">Uygulamayı gizledikten sonra adı ve simgesi tanınması zor olursa ana ekrana güzel bir kısayol ekleyin</string>\n    <string name=\"settings_doh_title\">HTTPS üzerinden DNS</string>\n    <string name=\"settings_doh_description\">Bazı ülkelerdeki DNS zehirlenmesini aşmak için geçici çözüm</string>\n    <string name=\"settings_random_name_title\">Çıktı Adını Rastgele Yap</string>\n    <string name=\"settings_random_name_description\">Tespit edilmesini önlemek için yamalanmış imajların ve tar dosyalarının çıktı dosya adını rastgele yapın</string>\n    <string name=\"multiuser_mode\">Çoklu Kullanıcı Modu</string>\n    <string name=\"settings_owner_only\">Sadece Cihaz Sahibi</string>\n    <string name=\"settings_owner_manage\">Cihaz Sahibi Tarafından Yönetilen</string>\n    <string name=\"settings_user_independent\">Kullanıcıdan Bağımsız</string>\n    <string name=\"owner_only_summary\">Sadece cihaz sahibi root erişimine sahiptir</string>\n    <string name=\"owner_manage_summary\">Sadece cihaz sahibi root erişimini yönetebilir ve istekleri alabilir</string>\n    <string name=\"user_independent_summary\">Her kullanıcının kendi ayrı root kuralları vardır</string>\n    <string name=\"mount_namespace_mode\">Bağlama Ad Alanı Modu</string>\n    <string name=\"settings_ns_global\">Genel Ad Alanı</string>\n    <string name=\"settings_ns_requester\">Ad Alanını Devral</string>\n    <string name=\"settings_ns_isolate\">Yalıtılmış Ad Alanı</string>\n    <string name=\"global_summary\">Tüm root oturumları genel bağlama ad alanını kullanır</string>\n    <string name=\"requester_summary\">Root oturumları, istekçilerinin ad alanını devralır</string>\n    <string name=\"isolate_summary\">Her root oturumunun kendi yalıtılmış ad alanı olacaktır</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk güncellemeleri</string>\n    <string name=\"progress_channel\">İlerleme bildirimleri</string>\n    <string name=\"updated_channel\">Güncelleme tamamlandı</string>\n    <string name=\"download_complete\">İndirme tamamlandı</string>\n    <string name=\"download_file_error\">Dosya indirilirken hata oluştu</string>\n    <string name=\"magisk_update_title\">Magisk güncellemesi mevcut!</string>\n    <string name=\"updated_title\">Magisk güncellendi</string>\n    <string name=\"updated_text\">Uygulamayı açmak için dokunun</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Evet</string>\n    <string name=\"no\">Hayır</string>\n    <string name=\"repo_install_title\">%1$s %2$s(%3$d) Yüklensin mi?</string>\n    <string name=\"download\">İndir</string>\n    <string name=\"reboot\">Yeniden Başlat</string>\n    <string name=\"close\">Kapat</string>\n    <string name=\"release_notes\">Sürüm Notları</string>\n    <string name=\"flashing\">Flaşlanıyor…</string>\n    <string name=\"running\">Çalışıyor…</string>\n    <string name=\"done\">Bitti!</string>\n    <string name=\"done_action\">%1$s eylemi tamamlandı</string>\n    <string name=\"failure\">Başarısız!</string>\n    <string name=\"hide_app_title\">Magisk uygulaması gizleniyor…</string>\n    <string name=\"open_link_failed_toast\">Bağlantıyı açacak bir uygulama bulunamadı</string>\n    <string name=\"complete_uninstall\">Tamamen Kaldır</string>\n    <string name=\"restore_img\">İmajları Geri Yükle</string>\n    <string name=\"restore_img_msg\">Geri yükleniyor…</string>\n    <string name=\"restore_done\">Geri yükleme tamamlandı!</string>\n    <string name=\"restore_fail\">Stok yedeği mevcut değil!</string>\n    <string name=\"setup_fail\">Kurulum başarısız oldu</string>\n    <string name=\"env_fix_title\">Ek Kurulum Gerektiriyor</string>\n    <string name=\"env_fix_msg\">Cihazınızın Magisk\\'in düzgün çalışması için ek kuruluma ihtiyacı var. Devam edip yeniden başlatmak ister misiniz?</string>\n    <string name=\"env_full_fix_msg\">Cihazınızın düzgün çalışması için Magisk\\'i yeniden flaşlamanız gerekiyor. Lütfen Magisk\\'i uygulama içinden yeniden kurun, Kurtarma modu doğru cihaz bilgisini alamaz.</string>\n    <string name=\"setup_msg\">Çalışma ortamı kuruluyor…</string>\n    <string name=\"unsupport_magisk_title\">Desteklenmeyen Magisk Sürümü</string>\n    <string name=\"unsupport_magisk_msg\">Uygulamanın bu sürümü, %1$s sürümünden daha düşük Magisk sürümlerini desteklemiyor.\\n\\nUygulama, Magisk yüklü değilmiş gibi davranacaktır. Lütfen Magisk\\'i en kısa sürede güncelleyin.</string>\n    <string name=\"unsupport_general_title\">Anormal Durum</string>\n    <string name=\"unsupport_system_app_msg\">Bu uygulamanın bir sistem uygulaması olarak çalıştırılması desteklenmiyor. Lütfen uygulamayı bir kullanıcı uygulamasına geri döndürün.</string>\n    <string name=\"unsupport_other_su_msg\">Magisk\\'e ait olmayan bir \"su\" ikili dosyası tespit edildi. Lütfen rakip root çözümlerini kaldırın ve/veya Magisk\\'i yeniden yükleyin.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk harici depolamaya kurulmuş. Lütfen uygulamayı dahili depolamaya taşıyın.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Gizlenmiş Magisk uygulaması, root kaybolduğu için çalışmaya devam edemiyor. Lütfen orijinal APK\\'yi geri yükleyin.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Bu işlevi etkinleştirmek için depolama izni verin</string>\n    <string name=\"post_notifications_denied\">Bu işlevi etkinleştirmek için bildirim izni verin</string>\n    <string name=\"install_unknown_denied\">Bu işlevi etkinleştirmek için \"Bilinmeyen uygulamaları yükle\" iznini verin</string>\n    <string name=\"add_shortcut_title\">Ana Ekrana Kısayol Ekle</string>\n    <string name=\"add_shortcut_msg\">Bu uygulamayı gizledikten sonra adı ve simgesi tanınması zor olabilir. Ana ekrana güzel bir kısayol eklemek ister misiniz?</string>\n    <string name=\"app_not_found\">Bu eylemi gerçekleştirecek bir uygulama bulunamadı</string>\n    <string name=\"reboot_apply_change\">Değişiklikleri uygulamak için yeniden başlatın</string>\n    <string name=\"restore_app_confirmation\">Bu, gizlenmiş uygulamayı orijinal uygulamaya geri yükleyecektir. Bunu gerçekten yapmak istiyor musunuz?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-uk/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Модулі</string>\n    <string name=\"superuser\">Суперкористувач</string>\n    <string name=\"logs\">Журнали</string>\n    <string name=\"settings\">Налаштування</string>\n    <string name=\"install\">Встановлення</string>\n    <string name=\"section_home\">Домашня</string>\n    <string name=\"section_theme\">Теми</string>\n    <string name=\"denylist\">DenyList</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Немає з\\’єднання</string>\n    <string name=\"app_changelog\">Список змін</string>\n    <string name=\"loading\">Завантаження…</string>\n    <string name=\"update\">Оновити</string>\n    <string name=\"not_available\">Н/Д</string>\n    <string name=\"hide\">Сховати</string>\n    <string name=\"home_package\">Пакунок</string>\n    <string name=\"home_app_title\">Застосунок</string>\n\n    <string name=\"home_notice_content\">Завантажуйте Magisk ЛИШЕ з офіційної сторінки на GitHub. Файли з невідомих джерел можуть містити шкідливий функціонал!</string>\n    <string name=\"home_support_title\">Підтримати нас</string>\n    <string name=\"home_item_source\">Джерело</string>\n    <string name=\"home_support_content\">Magisk є, і завжди буде безкоштовним та з відкритим кодом. Однак, якщо вам не байдуже, можете зробити невеличке пожертвування.</string>\n    <string name=\"home_installed_version\">Встановлено</string>\n    <string name=\"home_latest_version\">Найновіша</string>\n    <string name=\"invalid_update_channel\">Невірний канал оновлення</string>\n    <string name=\"uninstall_magisk_title\">Видалити Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Ця дія призведе до вимкнення/видалення всіх модулів! Рут буде повністю видалено!\\nВаші дані можуть зашифруватися, якщо вони не зашифровані!\\nВпевнені, що бажаєте продовжити?</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Залишити примусове шифрування</string>\n    <string name=\"keep_dm_verity\">Залишити AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Режим Recovery</string>\n    <string name=\"install_options_title\">Параметри</string>\n    <string name=\"install_method_title\">Спосіб</string>\n    <string name=\"install_next\">Далі</string>\n    <string name=\"install_start\">Встановити</string>\n    <string name=\"manager_download_install\">Натисніть, щоб завантажити і встановити</string>\n    <string name=\"direct_install\">Пряме встановлення (рекомендовано)</string>\n    <string name=\"install_inactive_slot\">Встановити в неактивний слот (після OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Ваш пристрій буде примусово перезавантажено в неактивний слот!\\nВикористовуйте цю опцію тільки для встановлення OTA.\\nПродовжити?</string>\n    <string name=\"setup_title\">Додаткове налаштування</string>\n    <string name=\"select_patch_file\">Вибрати і пропатчити файл</string>\n    <string name=\"patch_file_msg\">Виберіть необроблений образ (*.img) або стиснутий файл ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Перезавантаження через 5 секунд…</string>\n    <string name=\"flash_screen_title\">Встановлення</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Запит прав суперкористувача</string>\n    <string name=\"touch_filtered_warning\">Magisk не може перевірити вашу відповідь, тому що застосунок заступає вікно запиту прав суперкористувача</string>\n    <string name=\"deny\">Відмовити</string>\n    <string name=\"prompt\">Запитувати</string>\n    <string name=\"grant\">Надати</string>\n    <string name=\"su_warning\">Надати повний доступ до пристрою.\\nЯкщо не впевнені, відмовте в доступі!</string>\n    <string name=\"forever\">Назавжди</string>\n    <string name=\"once\">Одноразово</string>\n    <string name=\"tenmin\">10 хв.</string>\n    <string name=\"twentymin\">20 хв.</string>\n    <string name=\"thirtymin\">30 хв.</string>\n    <string name=\"sixtymin\">60 хв.</string>\n    <string name=\"su_allow_toast\">%1$s надані права суперкористувача</string>\n    <string name=\"su_deny_toast\">%1$s відмовлено в правах суперкористувача</string>\n    <string name=\"su_snack_grant\">%1$s надані права суперкористувача</string>\n    <string name=\"su_snack_deny\">%1$s відмовлено в правах суперкористувача</string>\n    <string name=\"su_snack_notif_on\">Сповіщення для %1$s увімкнено</string>\n    <string name=\"su_snack_notif_off\">Сповіщення для %1$s вимкнено</string>\n    <string name=\"su_snack_log_on\">Журнал подій для %1$s увімкнено</string>\n    <string name=\"su_snack_log_off\">Журнал подій для %1$s вимкнено</string>\n    <string name=\"su_revoke_title\">Відкликати?</string>\n    <string name=\"su_revoke_msg\">Підтвердити відкликання прав для %1$s?</string>\n    <string name=\"toast\">Спливаюче сповіщення</string>\n    <string name=\"none\">Нічого</string>\n\n    <string name=\"superuser_toggle_notification\">Сповіщення</string>\n    <string name=\"superuser_toggle_revoke\">Відкликати</string>\n    <string name=\"superuser_policy_none\">Досі ніякий застосунок не запитував права суперкористувача</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Покищо немає даних в журналі, спробуйте більше користуватися застосунками, які потребують прав суперкористувача</string>\n    <string name=\"log_data_magisk_none\">Журнал Magisk порожній, це дивно.</string>\n    <string name=\"menuSaveLog\">Зберегти журнал</string>\n    <string name=\"menuClearLog\">Очистити журнал</string>\n    <string name=\"logs_cleared\">Журнал успішно очищено</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">Цільовий UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Показувати системні застосунки</string>\n    <string name=\"show_os_app\">Показувати вбудовані застосунки</string>\n    <string name=\"hide_filter_hint\">Фільтрувати за назвою</string>\n    <string name=\"hide_search\">Пошук</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(немає наданої інформації)</string>\n    <string name=\"reboot_userspace\">Швидке перезавантаження</string>\n    <string name=\"reboot_recovery\">Перезавантажити в Recovery</string>\n    <string name=\"reboot_bootloader\">Перезавантажити в Bootloader</string>\n    <string name=\"reboot_download\">Перезавантажити в Download</string>\n    <string name=\"reboot_edl\">Перезавантажити в EDL</string>\n    <string name=\"module_version_author\">%1$s, розробник %2$s</string>\n    <string name=\"module_state_remove\">Видалити</string>\n    <string name=\"module_state_restore\">Відновити</string>\n    <string name=\"module_action_install_external\">Встановити зі сховища</string>\n    <string name=\"update_available\">Доступне оновлення</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">Режим теми</string>\n    <string name=\"settings_dark_mode_message\">Виберіть режим, який вам найбільш до вподоби!</string>\n    <string name=\"settings_dark_mode_light\">Завжди світлий</string>\n    <string name=\"settings_dark_mode_system\">Слідувати системному</string>\n    <string name=\"settings_dark_mode_dark\">Завжди темний</string>\n    <string name=\"settings_download_path_title\">Шлях завантаження</string>\n    <string name=\"settings_download_path_message\">Файли зберігатимуться в %1$s</string>\n    <string name=\"settings_hide_app_title\">Сховати застосунок Magisk</string>\n    <string name=\"settings_hide_app_summary\">Встановити проксі-застосунок з довільним ID пакунку та назвою</string>\n    <string name=\"settings_restore_app_title\">Відновити застосунок Magisk</string>\n    <string name=\"settings_restore_app_summary\">Відновити застосунок з оригінального APK та показати його</string>\n    <string name=\"language\">Мова</string>\n    <string name=\"system_default\">(системна)</string>\n    <string name=\"settings_check_update_title\">Перевіряти оновлення</string>\n    <string name=\"settings_check_update_summary\">Періодично перевіряти оновлення у фоновому режимі</string>\n    <string name=\"settings_update_channel_title\">Канал оновлення</string>\n    <string name=\"settings_update_stable\">Стабільний реліз</string>\n    <string name=\"settings_update_beta\">Бета реліз</string>\n    <string name=\"settings_update_custom\">Власний</string>\n    <string name=\"settings_update_custom_msg\">Вставте власний URL</string>\n    <string name=\"settings_zygisk_summary\">Запускати частини Magisk в сервісі zygote</string>\n    <string name=\"settings_denylist_title\">Увімкнути DenyList</string>\n    <string name=\"settings_denylist_summary\">Всі зміни, внесені Magisk, будуть приховані від процесів, позначених у DenyList</string>\n    <string name=\"settings_denylist_config_title\">Налаштувати DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Вибрати процеси, які будуть додані до denylist</string>\n    <string name=\"settings_hosts_title\">Позасистемні хости</string>\n    <string name=\"settings_hosts_summary\">Підтримка позасистемних хостів для застосунків блокування реклами</string>\n    <string name=\"settings_hosts_toast\">Додано модуль позасистемних хостів</string>\n    <string name=\"settings_app_name_hint\">Нове ім\\’я</string>\n    <string name=\"settings_app_name_helper\">Застосунок буде перезібрано з цим ім\\’ям</string>\n    <string name=\"settings_app_name_error\">Неправильний формат</string>\n    <string name=\"settings_su_app_adb\">Застосунки і ADB</string>\n    <string name=\"settings_su_app\">Застосунки</string>\n    <string name=\"settings_su_adb\">ADB</string>\n    <string name=\"settings_su_disable\">Вимкнено</string>\n    <string name=\"settings_su_request_10\">10 секунд</string>\n    <string name=\"settings_su_request_15\">15 секунд</string>\n    <string name=\"settings_su_request_20\">20 секунд</string>\n    <string name=\"settings_su_request_30\">30 секунд</string>\n    <string name=\"settings_su_request_45\">45 секунд</string>\n    <string name=\"settings_su_request_60\">60 секунд</string>\n    <string name=\"superuser_access\">Доступ суперкористувача</string>\n    <string name=\"auto_response\">Автоматична відповідь</string>\n    <string name=\"request_timeout\">Час запиту</string>\n    <string name=\"superuser_notification\">Сповіщення суперкористувача</string>\n    <string name=\"settings_su_reauth_title\">Повторна автентифікація</string>\n    <string name=\"settings_su_reauth_summary\">Перевидача прав суперкористувача після оновлення застосунку</string>\n    <string name=\"settings_su_tapjack_title\">Увімкнути захист від підміни натискань</string>\n    <string name=\"settings_su_tapjack_summary\">Діалогове вікно суперкористувача не буде отримувати ввід від користувача, коли вікно перекрито іншим застосунком чи вікном</string>\n    <string name=\"settings_customization\">Оформлення</string>\n    <string name=\"setting_add_shortcut_summary\">Додати ярлик на домашній екран для зручного сприйняття застосунку після його приховування</string>\n    <string name=\"settings_doh_title\">DNS поверх HTTPS</string>\n    <string name=\"settings_doh_description\">Відключити DoH (при нестабільному підключенні)</string>\n\n    <string name=\"multiuser_mode\">Багатокористувацький режим</string>\n    <string name=\"settings_owner_only\">Тільки власник</string>\n    <string name=\"settings_owner_manage\">Регулювання власником</string>\n    <string name=\"settings_user_independent\">Незалежний користувач</string>\n    <string name=\"owner_only_summary\">Тільки власник має root-доступ</string>\n    <string name=\"owner_manage_summary\">Тільки власник може керувати root-доступом і опрацьовувати запити на надання доступу</string>\n    <string name=\"user_independent_summary\">Кожен користувач має власні правила root-доступу</string>\n\n    <string name=\"mount_namespace_mode\">Режим монтування простору імен</string>\n    <string name=\"settings_ns_global\">Глобальний простір імен</string>\n    <string name=\"settings_ns_requester\">Наслідуваний простір імен</string>\n    <string name=\"settings_ns_isolate\">Ізольований простір імен</string>\n    <string name=\"global_summary\">Всі сеанси суперкористувача використовують глобальний простір імен</string>\n    <string name=\"requester_summary\">Сеанси суперкористувача наслідують простір імен запитувача</string>\n    <string name=\"isolate_summary\">Кожнен сеанс суперкористувача має власний ізольований простір імен</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Оновлення Magisk</string>\n    <string name=\"progress_channel\">Сповіщення прогресу</string>\n    <string name=\"download_complete\">Завантаження завершено</string>\n    <string name=\"download_file_error\">Помилка завантаження файлу</string>\n    <string name=\"magisk_update_title\">Доступне оновлення Magisk!</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Так</string>\n    <string name=\"no\">Ні</string>\n    <string name=\"repo_install_title\">Встановити %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Завантажити</string>\n    <string name=\"reboot\">Перезавантажити</string>\n    <string name=\"release_notes\">Особливості версії</string>\n    <string name=\"flashing\">Прошивання…</string>\n    <string name=\"done\">Готово!</string>\n    <string name=\"failure\">Не вдалося!</string>\n    <string name=\"hide_app_title\">Ховання застосунку Magisk…</string>\n    <string name=\"open_link_failed_toast\">Не знайдено застосунку для відкриття посилання</string>\n    <string name=\"complete_uninstall\">Повне видалення</string>\n    <string name=\"restore_img\">Відновити розділи</string>\n    <string name=\"restore_img_msg\">Відновлення…</string>\n    <string name=\"restore_done\">Відновлення завершено!</string>\n    <string name=\"restore_fail\">Немає оригінальної резервної копії!</string>\n    <string name=\"setup_fail\">Не вдалося налаштувати</string>\n    <string name=\"env_fix_title\">Потрібне додаткове налаштування</string>\n    <string name=\"env_fix_msg\">Для вашого пристрою необхідне додаткове налаштування, щоб Magisk працював як слід. Бажаєте виконати налаштування та перезавантажити пристрій?</string>\n    <string name=\"setup_msg\">Налаштування робочого середовища…</string>\n    <string name=\"unsupport_magisk_title\">Версія Magisk не підтримується</string>\n    <string name=\"unsupport_magisk_msg\">Ця версія застосунку не підтримує версію Magisk, нижчу за %1$s.\\n\\nЗастосунок буде працювати так, ніби немає встановленого Magisk. Будь ласка, оновіть Magisk якнайшвидше.</string>\n    <string name=\"unsupport_general_title\">Ненормальний стан</string>\n    <string name=\"unsupport_system_app_msg\">Запуск цього застосунку як системний застосунок не підтримується. Будь ласка, перевстановіть цей застосунок, щоб він був як звичайний застосунок, який встановлений користувачем.</string>\n    <string name=\"unsupport_other_su_msg\">Виявлено команду \\\"su\\\", яка не належить до Magisk. Будь ласка, видаліть інші непідтримувані su.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk встановлено на зовнішнє сховище. Будь ласка, перемістіть застосунок на внутрішнє сховище.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Застосунок не може продовжувати працювати в прихованому режимі, тому що втрачено root. Будь ласка, відновіть оригінальний застосунок з APK.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Надайте доступ до сховища, щоб увімкнути цей функціонал</string>\n    <string name=\"add_shortcut_title\">Ярлик на домашньому екрані</string>\n    <string name=\"add_shortcut_msg\">Після приховування цього застосунку, його значок та ім\\’я будуть іншими, тому застосунок буде важко впізнати. Хочете додати ярлик на домашній екран?</string>\n    <string name=\"app_not_found\">Не знайдено застосунок, який повинен опрацювати цю дію</string>\n    <string name=\"reboot_apply_change\">Перезавантажити, щоб застосувати зміни</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-ur/strings.xml",
    "content": "<resources>\n\n    <string name=\"modules\">ماڈیولز</string>\n    <string name=\"superuser\">سپر یوزر</string>\n    <string name=\"logs\">لاگز</string>\n    <string name=\"settings\">سیٹنگز</string>\n    <string name=\"install\">انسٹال کریں</string>\n    <string name=\"section_home\">ہوم</string>\n    <string name=\"section_theme\">تھیمز</string>\n    <string name=\"denylist\">مسترد فہرست</string>\n\n    <string name=\"no_connection\">کوئی کنکشن دستیاب نہیں</string>\n    <string name=\"app_changelog\">ایپ چینج لاگ</string>\n    <string name=\"loading\">لوڈ ہو رہا ہے…</string>\n    <string name=\"update\">اپ ڈیٹ</string>\n    <string name=\"not_available\">دستیاب نہیں</string>\n    <string name=\"hide\">چھپائیں</string>\n    <string name=\"home_package\">پیکیج</string>\n    <string name=\"home_app_title\">ایپ</string>\n\n    <string name=\"home_notice_content\">صرف آفیشل گٹ ہب پیج سے Magisk ڈاؤن لوڈ کریں۔ نامعلوم ذرائع سے فائلیں نقصان دہ ہو سکتی ہیں!</string>\n    <string name=\"home_support_title\">ہماری مدد کریں</string>\n    <string name=\"home_follow_title\">ہمیں فالو کریں</string>\n    <string name=\"home_item_source\">ذریعہ</string>\n    <string name=\"home_support_content\">Magisk ہمیشہ مفت اور اوپن سورس رہے گا۔ تاہم، آپ عطیہ کر کے ہمیں اپنی دیکھ بھال کا اظہار کر سکتے ہیں۔</string>\n    <string name=\"home_installed_version\">انسٹال شدہ</string>\n    <string name=\"home_latest_version\">تازہ ترین</string>\n    <string name=\"invalid_update_channel\">غلط اپ ڈیٹ چینل</string>\n    <string name=\"uninstall_magisk_title\">Magisk اَن انسٹال کریں</string>\n    <string name=\"uninstall_magisk_msg\">تمام ماڈیولز غیر فعال/ہٹا دیے جائیں گے!\\nروٹ ہٹا دیا جائے گا!\\nMagisk کے استعمال کے ذریعے غیر اینکرپٹ کی گئی کوئی بھی اندرونی اسٹوریج دوبارہ اینکرپٹ ہو جائے گی!</string>\n\n    <string name=\"keep_force_encryption\">جبری انکرپشن کو محفوظ رکھیں</string>\n    <string name=\"keep_dm_verity\">AVB 2.0/dm-verity کو محفوظ رکھیں</string>\n    <string name=\"recovery_mode\">ریکوری موڈ</string>\n    <string name=\"install_options_title\">آپشنز</string>\n    <string name=\"install_method_title\">طریقہ</string>\n    <string name=\"install_next\">اگلا</string>\n    <string name=\"install_start\">چلو شروع کرتے ہیں</string>\n    <string name=\"manager_download_install\">ڈاؤن لوڈ اور انسٹال کرنے کے لیے دبائیں</string>\n    <string name=\"direct_install\">براہ راست انسٹال (تجویز کردہ)</string>\n    <string name=\"install_inactive_slot\">غیر فعال سلاٹ میں انسٹال کریں (OTA کے بعد)</string>\n    <string name=\"install_inactive_slot_msg\">ریبوٹ کے بعد آپ کا آلہ زبردستی موجودہ غیر فعال سلاٹ میں بوٹ ہو جائے گا!\\nیہ آپشن صرف OTA مکمل ہونے کے بعد استعمال کریں۔\\nجاری رکھیں؟</string>\n    <string name=\"setup_title\">اضافی سیٹ اپ</string>\n    <string name=\"select_patch_file\">پیچ فائل منتخب اور پیچ کریں</string>\n    <string name=\"patch_file_msg\">ایک خام امیج (*.img) یا ایک ODIN ٹار فائل (*.tar) یا ایک payload.bin (*.bin) منتخب کریں</string>\n    <string name=\"reboot_delay_toast\">5 سیکنڈ میں ریبوٹ ہو رہا ہے…</string>\n    <string name=\"flash_screen_title\">انسٹالیشن</string>\n\n    <string name=\"su_request_title\">سپر یوزر کی درخواست</string>\n    <string name=\"touch_filtered_warning\">چونکہ ایک ایپ سپر یوزر کی درخواست کو دھندلا کر رہی ہے، اس لیے Magisk آپ کے جواب کی تصدیق نہیں کر سکتا</string>\n    <string name=\"deny\">منع کریں</string>\n    <string name=\"prompt\">پوچھیں</string>\n    <string name=\"grant\">اجازت دیں</string>\n    <string name=\"su_warning\">اپنے آلے تک مکمل رسائی کی اجازت دیں۔\\nاگر آپ کو یقین نہیں ہے تو منع کریں!</string>\n    <string name=\"forever\">ہمیشہ کے لیے</string>\n    <string name=\"once\">ایک بار</string>\n    <string name=\"tenmin\">10 منٹ</string>\n    <string name=\"twentymin\">20 منٹ</string>\n    <string name=\"thirtymin\">30 منٹ</string>\n    <string name=\"sixtymin\">60 منٹ</string>\n    <string name=\"su_allow_toast\">%1$s کو سپر یوزر کے حقوق دیئے گئے</string>\n    <string name=\"su_deny_toast\">%1$s کو سپر یوزر کے حقوق سے انکار کر دیا گیا</string>\n    <string name=\"su_snack_grant\">%1$s کے سپر یوزر حقوق دیئے گئے</string>\n    <string name=\"su_snack_deny\">%1$s کے سپر یوزر حقوق سے انکار کر دیا گیا</string>\n    <string name=\"su_snack_notif_on\">%1$s کی اطلاعات فعال ہیں</string>\n    <string name=\"su_snack_notif_off\">%1$s کی اطلاعات غیر فعال ہیں</string>\n    <string name=\"su_snack_log_on\">%1$s کی لاگنگ فعال ہے</string>\n    <string name=\"su_snack_log_off\">%1$s کی لاگنگ غیر فعال ہے</string>\n    <string name=\"su_revoke_title\">منسوخ کریں؟</string>\n    <string name=\"su_revoke_msg\">%1$s کے سپر یوزر حقوق منسوخ کرنے کی تصدیق کریں</string>\n    <string name=\"toast\">ٹوسٹ</string>\n    <string name=\"none\">کوئی نہیں</string>\n\n    <string name=\"superuser_toggle_notification\">اطلاعات</string>\n    <string name=\"superuser_toggle_revoke\">منسوخ کریں</string>\n    <string name=\"superuser_policy_none\">ابھی تک کسی ایپ نے سپر یوزر کی اجازت نہیں مانگی ہے۔</string>\n\n    <string name=\"log_data_none\">آپ لاگ فری ہیں، اپنی روٹ ایپس کو مزید استعمال کرنے کی کوشش کریں</string>\n    <string name=\"log_data_magisk_none\">Magisk لاگز خالی ہیں، یہ عجیب بات ہے</string>\n    <string name=\"menuSaveLog\">لاگ محفوظ کریں</string>\n    <string name=\"menuClearLog\">ابھی لاگ صاف کریں</string>\n    <string name=\"logs_cleared\">لاگ کامیابی سے صاف ہو گیا</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">ٹارگٹ UID: %1$d</string>\n    <string name=\"target_pid\">ماؤنٹ این ایس ٹارگٹ PID: %s</string>\n    <string name=\"selinux_context\">SELinux سیاق و سباق: %s</string>\n    <string name=\"supp_group\">اضافی گروپ: %s</string>\n\n    <string name=\"show_system_app\">سسٹم ایپس دکھائیں</string>\n    <string name=\"show_os_app\">OS ایپس دکھائیں</string>\n    <string name=\"hide_filter_hint\">نام سے فلٹر کریں</string>\n    <string name=\"hide_search\">تلاش کریں</string>\n\n    <string name=\"no_info_provided\">(کوئی معلومات فراہم نہیں کی گئی)</string>\n    <string name=\"reboot_userspace\">سافٹ ریبوٹ</string>\n    <string name=\"reboot_recovery\">ریکوری میں ریبوٹ کریں</string>\n    <string name=\"reboot_bootloader\">بوٹ لوڈر میں ریبوٹ کریں</string>\n    <string name=\"reboot_download\">ڈاؤن لوڈ میں ریبوٹ کریں</string>\n    <string name=\"reboot_edl\">EDL میں ریبوٹ کریں</string>\n    <string name=\"reboot_safe_mode\">محفوظ موڈ</string>\n    <string name=\"module_version_author\">%2$s کی جانب سے %1$s</string>\n    <string name=\"module_state_remove\">ہٹائیں</string>\n    <string name=\"module_state_restore\">بحال کریں</string>\n    <string name=\"module_action_install_external\">اسٹوریج سے انسٹال کریں</string>\n    <string name=\"update_available\">اپ ڈیٹ دستیاب ہے</string>\n    <string name=\"suspend_text_riru\">ماڈیول معطل کر دیا گیا کیونکہ %1$s فعال ہے</string>\n    <string name=\"suspend_text_zygisk\">ماڈیول معطل کر دیا گیا کیونکہ %1$s فعال نہیں ہے</string>\n    <string name=\"zygisk_module_unloaded\">غیر مطابقت کی وجہ سے Zygisk ماڈیول لوڈ نہیں ہوا</string>\n    <string name=\"module_empty\">کوئی ماڈیول انسٹال نہیں ہے</string>\n    <string name=\"confirm_install\">ماڈیول %1$s انسٹال کریں؟</string>\n    <string name=\"confirm_install_title\">انسٹالیشن کی تصدیق</string>\n\n    <string name=\"settings_dark_mode_title\">تھیم موڈ</string>\n    <string name=\"settings_dark_mode_message\">وہ موڈ منتخب کریں جو آپ کے انداز کے مطابق ہو!</string>\n    <string name=\"settings_dark_mode_light\">ہمیشہ لائٹ</string>\n    <string name=\"settings_dark_mode_system\">سسٹم کی پیروی کریں</string>\n    <string name=\"settings_dark_mode_dark\">ہمیشہ ڈارک</string>\n    <string name=\"settings_download_path_title\">ڈاؤن لوڈ کا راستہ</string>\n    <string name=\"settings_download_path_message\">فائلیں %1$s میں محفوظ کی جائیں گی</string>\n    <string name=\"settings_hide_app_title\">Magisk ایپ کو چھپائیں</string>\n    <string name=\"settings_hide_app_summary\">ایک رینڈم پیکیج ID اور کسٹم ایپ لیبل کے ساتھ ایک پراکسی ایپ انسٹال کریں</string>\n    <string name=\"settings_restore_app_title\">Magisk ایپ کو بحال کریں</string>\n    <string name=\"settings_restore_app_summary\">ایپ کو ان ہائیڈ کریں اور اصل APK کو بحال کریں</string>\n    <string name=\"language\">زبان</string>\n    <string name=\"system_default\">(سسٹم ڈیفالٹ)</string>\n    <string name=\"settings_check_update_title\">اپ ڈیٹس چیک کریں</string>\n    <string name=\"settings_check_update_summary\">وقفے وقفے سے پس منظر میں اپ ڈیٹس چیک کریں</string>\n    <string name=\"settings_update_channel_title\">اپ ڈیٹ چینل</string>\n    <string name=\"settings_update_stable\">مستحکم</string>\n    <string name=\"settings_update_beta\">بیٹا</string>\n    <string name=\"settings_update_custom\">کسٹم</string>\n    <string name=\"settings_update_custom_msg\">ایک کسٹم چینل URL درج کریں</string>\n    <string name=\"settings_zygisk_summary\">zygote ڈیمن میں Magisk کے کچھ حصے چلائیں</string>\n    <string name=\"settings_denylist_title\">مسترد فہرست نافذ کریں</string>\n    <string name=\"settings_denylist_summary\">مسترد فہرست میں موجود عملوں میں Magisk کی تمام ترامیم کو کالعدم قرار دیا جائے گا</string>\n    <string name=\"settings_denylist_config_title\">مسترد فہرست کی تشکیل کریں</string>\n    <string name=\"settings_denylist_config_summary\">مسترد فہرست میں شامل کرنے کے لیے عمل منتخب کریں</string>\n    <string name=\"settings_hosts_title\">سسٹم لیس ہوسٹس</string>\n    <string name=\"settings_hosts_summary\">ایڈ بلاکنگ ایپس کے لیے سسٹم لیس ہوسٹس سپورٹ</string>\n    <string name=\"settings_hosts_toast\">سسٹم لیس ہوسٹس ماڈیول شامل کیا گیا</string>\n    <string name=\"settings_app_name_hint\">نیا نام</string>\n    <string name=\"settings_app_name_helper\">ایپ کو اس نام سے دوبارہ پیک کیا جائے گا</string>\n    <string name=\"settings_app_name_error\">غلط فارمیٹ</string>\n    <string name=\"settings_su_app_adb\">ایپس اور ADB</string>\n    <string name=\"settings_su_app\">صرف ایپس</string>\n    <string name=\"settings_su_adb\">صرف ADB</string>\n    <string name=\"settings_su_disable\">غیر فعال</string>\n    <string name=\"settings_su_request_10\">10 سیکنڈ</string>\n    <string name=\"settings_su_request_15\">15 سیکنڈ</string>\n    <string name=\"settings_su_request_20\">20 سیکنڈ</string>\n    <string name=\"settings_su_request_30\">30 سیکنڈ</string>\n    <string name=\"settings_su_request_45\">45 سیکنڈ</string>\n    <string name=\"settings_su_request_60\">60 سیکنڈ</string>\n    <string name=\"superuser_access\">سپر یوزر تک رسائی</string>\n    <string name=\"auto_response\">خودکار جواب</string>\n    <string name=\"request_timeout\">درخواست کا وقت ختم</string>\n    <string name=\"superuser_notification\">سپر یوزر اطلاع</string>\n    <string name=\"settings_su_reauth_title\">اپ گریڈ کے بعد دوبارہ تصدیق کریں</string>\n    <string name=\"settings_su_reauth_summary\">ایپس کو اپ گریڈ کرنے کے بعد دوبارہ سپر یوزر کی اجازتیں مانگیں</string>\n    <string name=\"settings_su_tapjack_title\">ٹیپ جیکنگ سے تحفظ</string>\n    <string name=\"settings_su_tapjack_summary\">سپر یوزر پرامپٹ ڈائیلاگ کسی دوسری ونڈو یا اوورلے سے دھندلا ہونے کے دوران ان پٹ کا جواب نہیں دے گا</string>\n    <string name=\"settings_su_auth_title\">صارف کی تصدیق</string>\n    <string name=\"settings_su_auth_summary\">سپر یوزر کی درخواستوں کے دوران صارف کی تصدیق طلب کریں</string>\n    <string name=\"settings_su_auth_insecure\">آلے پر کوئی تصدیقی طریقہ کار تشکیل نہیں دیا گیا ہے</string>\n    <string name=\"settings_customization\">حسب ضرورت سازی</string>\n    <string name=\"setting_add_shortcut_summary\">اگر ایپ کو چھپانے کے بعد اس کا نام اور آئیکن پہچاننا مشکل ہو جائے تو ہوم اسکرین پر ایک خوبصورت شارٹ کٹ شامل کریں</string>\n    <string name=\"settings_doh_title\">HTTPS پر DNS</string>\n    <string name=\"settings_doh_description\">بعض ممالک میں DNS زہر آلودگی کا حل</string>\n    <string name=\"settings_random_name_title\">بے ترتیب آؤٹ پٹ نام</string>\n    <string name=\"settings_random_name_description\">پتہ لگانے سے بچنے کے لیے پیچ شدہ امیجز اور ٹار فائلوں کے آؤٹ پٹ فائل کے نام کو بے ترتیب بنائیں</string>\n\n    <string name=\"multiuser_mode\">ملٹی یوزر موڈ</string>\n    <string name=\"settings_owner_only\">صرف آلہ کا مالک</string>\n    <string name=\"settings_owner_manage\">آلہ کے مالک کے زیر انتظام</string>\n    <string name=\"settings_user_independent\">صارف سے آزاد</string>\n    <string name=\"owner_only_summary\">صرف مالک کو روٹ تک رسائی حاصل ہے</string>\n    <string name=\"owner_manage_summary\">صرف مالک روٹ تک رسائی کا انتظام کر سکتا ہے اور درخواست کے اشارے وصول کر سکتا ہے</string>\n    <string name=\"user_independent_summary\">ہر صارف کے اپنے علیحدہ روٹ کے قواعد ہیں</string>\n\n    <string name=\"mount_namespace_mode\">ماؤنٹ نیم اسپیس موڈ</string>\n    <string name=\"settings_ns_global\">عالمی نیم اسپیس</string>\n    <string name=\"settings_ns_requester\">نیم اسپیس وراثت میں حاصل کریں</string>\n    <string name=\"settings_ns_isolate\">آئسولیٹڈ نیم اسپیس</string>\n    <string name=\"isolate_summary\">ہر روٹ سیشن کی اپنی آئسولیٹڈ نیم اسپیس ہوگی</string>\n    <string name=\"update_channel\">Magisk اپ ڈیٹس</string>\n    <string name=\"progress_channel\">پیش رفت کی اطلاعات</string>\n    <string name=\"updated_channel\">اپ ڈیٹ مکمل</string>\n    <string name=\"download_complete\">ڈاؤن لوڈ مکمل</string>\n    <string name=\"download_file_error\">فائل ڈاؤن لوڈ کرنے میں خرابی</string>\n    <string name=\"magisk_update_title\">Magisk اپ ڈیٹ دستیاب ہے!</string>\n    <string name=\"updated_title\">Magisk اپ ڈیٹ ہو گیا</string>\n    <string name=\"updated_text\">ایپ کھولنے کے لیے ٹیپ کریں</string>\n    <string name=\"yes\">ہاں</string>\n    <string name=\"no\">نہیں</string>\n    <string name=\"repo_install_title\">%1$s %2$s (%3$d) انسٹال کریں</string>\n    <string name=\"download\">ڈاؤن لوڈ</string>\n    <string name=\"reboot\">ریبوٹ</string>\n    <string name=\"close\">بند کریں</string>\n    <string name=\"release_notes\">ریلیز نوٹ</string>\n    <string name=\"flashing\">فلیش ہو رہا ہے…</string>\n    <string name=\"done\">ہو گیا!</string>\n    <string name=\"failure\">ناکام!</string>\n    <string name=\"hide_app_title\">Magisk ایپ کو چھپایا جا رہا ہے…</string>\n    <string name=\"open_link_failed_toast\">لنک کھولنے کے لیے کوئی ایپ نہیں ملی</string>\n    <string name=\"complete_uninstall\">مکمل ان انسٹال</string>\n    <string name=\"restore_img\">تصاویر بحال کریں</string>\n    <string name=\"restore_img_msg\">بحال ہو رہا ہے…</string>\n    <string name=\"restore_done\">بحالی مکمل ہو گئی!</string>\n    <string name=\"restore_fail\">اسٹاک بیک اپ موجود نہیں ہے!</string>\n    <string name=\"setup_fail\">سیٹ اپ ناکام ہو گیا</string>\n    <string name=\"env_full_fix_msg\">Magisk کو صحیح طریقے سے کام کرنے کے لیے آپ کے آلے کو دوبارہ فلیش کرنے کی ضرورت ہے۔ براہ کرم ایپ کے اندر Magisk کو دوبارہ انسٹال کریں، ریکوری موڈ درست ڈیوائس کی معلومات حاصل نہیں کر سکتا۔</string>\n    <string name=\"env_fix_title\">اضافی سیٹ اپ درکار ہے</string>\n    <string name=\"env_fix_msg\">Magisk کو صحیح طریقے سے کام کرنے کے لیے آپ کے آلے کو اضافی سیٹ اپ کی ضرورت ہے۔ کیا آپ جاری رکھنا چاہتے ہیں اور ریبوٹ کرنا چاہتے ہیں؟</string>\n    <string name=\"setup_msg\">ماحول کا سیٹ اپ چل رہا ہے…</string>\n    <string name=\"unsupport_magisk_title\">Magisk کا غیر تعاون یافتہ ورژن</string>\n    <string name=\"unsupport_magisk_msg\">ایپ کا یہ ورژن %1$s سے کم Magisk ورژنز کو سپورٹ نہیں کرتا ہے۔\\n\\nایپ ایسے برتاؤ کرے گی جیسے کوئی Magisk انسٹال نہیں ہے، براہ کرم جلد از جلد Magisk کو اپ گریڈ کریں۔</string>\n    <string name=\"unsupport_general_title\">غیر معمولی حالت</string>\n    <string name=\"unsupport_system_app_msg\">سسٹم ایپ کے طور پر اس ایپ کو چلانا تعاون یافتہ نہیں ہے۔ براہ کرم ایپ کو صارف ایپ میں واپس کریں۔</string>\n    <string name=\"unsupport_other_su_msg\">Magisk کے علاوہ کسی اور \"su\" بائنری کا پتہ چلا ہے۔ براہ کرم کسی بھی مسابقتی روٹ حل کو ہٹا دیں اور/یا Magisk کو دوبارہ انسٹال کریں۔</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk بیرونی اسٹوریج پر انسٹال ہے۔ براہ کرم ایپ کو اندرونی اسٹوریج میں منتقل کریں۔</string>\n    <string name=\"unsupport_nonroot_stub_msg\">چھپی ہوئی Magisk ایپ کام جاری نہیں رکھ سکتی کیونکہ روٹ ختم ہو گیا ہے۔ براہ کرم اصل APK کو بحال کریں۔</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">اس فعالیت کو فعال کرنے کے لیے اسٹوریج کی اجازت دیں</string>\n    <string name=\"post_notifications_denied\">اس فعالیت کو فعال کرنے کے لیے اطلاعات کی اجازت دیں</string>\n    <string name=\"install_unknown_denied\">اس فعالیت کو فعال کرنے کے لیے \"نامعلوم ایپس انسٹال کریں\" کی اجازت دیں</string>\n    <string name=\"add_shortcut_title\">ہوم اسکرین پر شارٹ کٹ شامل کریں</string>\n    <string name=\"add_shortcut_msg\">اس ایپ کو چھپانے کے بعد، اس کا نام اور آئیکن پہچاننا مشکل ہو سکتا ہے۔ کیا آپ ہوم اسکرین پر ایک خوبصورت شارٹ کٹ شامل کرنا چاہتے ہیں؟</string>\n    <string name=\"app_not_found\">اس عمل کو سنبھالنے کے لیے کوئی ایپ نہیں ملی</string>\n    <string name=\"reboot_apply_change\">تبدیلیاں لاگو کرنے کے لیے ریبوٹ کریں</string>\n    <string name=\"restore_app_confirmation\">یہ چھپی ہوئی ایپ کو اصل ایپ میں بحال کردے گا۔ کیا آپ واقعی ایسا کرنا چاہتے ہیں؟</string>\n\n</resources>\n\n      \n"
  },
  {
    "path": "app/core/src/main/res/values-v31/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!-- For stub on API 31+, we cannot rely on the platform SplashScreen -->\n    <!-- Force using SplashScreen.Common that was used for older APIs -->\n    <style name=\"StubSplashTheme\" parent=\"Theme.SplashScreen.Common\">\n        <item name=\"android:enforceStatusBarContrast\">false</item>\n        <item name=\"android:enforceNavigationBarContrast\">false</item>\n        <item name=\"windowSplashScreenBackground\">@color/splash_background</item>\n        <item name=\"windowSplashScreenAnimatedIcon\">@drawable/ic_magisk_padded</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-v34/resources.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <bool name=\"enable_fg_service\">false</bool>\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-vi/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">Mô-đun</string>\n    <string name=\"superuser\">Superuser</string>\n    <string name=\"logs\">Nhật ký</string>\n    <string name=\"settings\">Cài đặt</string>\n    <string name=\"install\">Cài đặt</string>\n    <string name=\"section_home\">Trang chủ</string>\n    <string name=\"section_theme\">Chủ đề</string>\n    <string name=\"denylist\">Danh sách loại trừ</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">Không có kết nối</string>\n    <string name=\"app_changelog\">Changelog</string>\n    <string name=\"loading\">Đang tải…</string>\n    <string name=\"update\">Cập nhật</string>\n    <string name=\"not_available\">Không có sẵn</string>\n    <string name=\"hide\">Ẩn</string>\n    <string name=\"home_package\">Gói</string>\n    <string name=\"home_app_title\">Ứng dụng</string>\n\n    <string name=\"home_notice_content\">Chỉ nên tải Magisk từ trang GitHub chính thức. Tải tệp từ các nguồn không rõ có thể gây hại!</string>\n    <string name=\"home_support_title\">Hỗ trợ chúng tôi</string>\n    <string name=\"home_item_source\">Nguồn</string>\n    <string name=\"home_support_content\">Magisk sẽ, và luôn luôn là, miễn phí và mã nguồn mở. Tuy nhiên, bạn có thể cho chúng tôi thấy rằng bạn quan tâm bằng cách gửi một khoản đóng góp nhỏ.</string>\n    <string name=\"home_installed_version\">Cài đặt</string>\n    <string name=\"home_latest_version\">Mới nhất</string>\n    <string name=\"invalid_update_channel\">Kênh cập nhật không hợp lệ</string>\n    <string name=\"uninstall_magisk_title\">Gỡ cài đặt Magisk</string>\n    <string name=\"uninstall_magisk_msg\">Tất cả các mô-đun sẽ bị tắt hoặc bị xóa!\\nRoot sẽ bị gỡ bỏ\\nPhân vùng data đã tắt mã hóa thông qua Magisk sẽ bị mã hóa lại!</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">Giữ bắt buộc mã hóa</string>\n    <string name=\"keep_dm_verity\">Giữ AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">Chế độ Recovery</string>\n    <string name=\"install_options_title\">Tùy chọn</string>\n    <string name=\"install_method_title\">Phương pháp</string>\n    <string name=\"install_next\">Kế tiếp</string>\n    <string name=\"install_start\">Đi nào</string>\n    <string name=\"manager_download_install\">Nhấn để tải xuống và cài đặt</string>\n    <string name=\"direct_install\">Cài đặt trực tiếp (Khuyến nghị)</string>\n    <string name=\"install_inactive_slot\">Cài đặt vào vùng không hoạt động (Sau OTA)</string>\n    <string name=\"install_inactive_slot_msg\">Thiết bị của bạn sẽ bị BUỘC khởi động vào vị trí không hoạt động hiện tại sau khi khởi động lại!\\nChỉ sử dụng tùy chọn này sau khi hoàn tất OTA.\\nTiếp tục chứ?</string>\n    <string name=\"setup_title\">Thiết lập bổ sung</string>\n    <string name=\"select_patch_file\">Chọn và vá tệp</string>\n    <string name=\"patch_file_msg\">Chọn đĩa ảnh (*.img) hoặc tệp tarfile ODIN (*.tar)</string>\n    <string name=\"reboot_delay_toast\">Khởi động lại sau 5 giây…</string>\n    <string name=\"flash_screen_title\">Cài đặt</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">Yêu cầu Superuser</string>\n    <string name=\"touch_filtered_warning\">Vì một ứng dụng đang che phủ yêu cầu Superuser, Magisk không thể xác minh phản hồi của bạn</string>\n    <string name=\"deny\">Từ chối</string>\n    <string name=\"prompt\">Nhắc nhở</string>\n    <string name=\"grant\">Cấp phép</string>\n    <string name=\"su_warning\">Cấp toàn quyền truy cập vào thiết bị của bạn.\\nTừ chối nếu bạn không chắc chắn!</string>\n    <string name=\"forever\">Mãi mãi</string>\n    <string name=\"once\">Một lần</string>\n    <string name=\"tenmin\">10 phút</string>\n    <string name=\"twentymin\">20 phút</string>\n    <string name=\"thirtymin\">30 phút</string>\n    <string name=\"sixtymin\">60 phút</string>\n    <string name=\"su_allow_toast\">%1$s đã được cấp quyền Superuser</string>\n    <string name=\"su_deny_toast\">%1$s đã bị từ chối quyền của Superuser</string>\n    <string name=\"su_snack_grant\">Quyền Superuser của %1$s được cấp</string>\n    <string name=\"su_snack_deny\">Quyền Superuser của %1$s bị từ chối</string>\n    <string name=\"su_snack_notif_on\">Thông báo của %1$s được bật</string>\n    <string name=\"su_snack_notif_off\">Thông báo của %1$s bị tắt</string>\n    <string name=\"su_snack_log_on\">Ghi nhật ký %1$s được bật</string>\n    <string name=\"su_snack_log_off\">Ghi nhật ký %1$s bị vô hiệu hóa</string>\n    <string name=\"su_revoke_title\">Thu hồi?</string>\n    <string name=\"su_revoke_msg\">Xác nhận thu hồi quyền %1$s?</string>\n    <string name=\"toast\">Thông báo nổi</string>\n    <string name=\"none\">Không có</string>\n\n    <string name=\"superuser_toggle_notification\">Thông báo</string>\n    <string name=\"superuser_toggle_revoke\">Thu hồi</string>\n    <string name=\"superuser_policy_none\">Chưa có ứng dụng nào yêu cầu Superuser.</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">Nhật ký của bạn đang trống, hãy thử sử dụng các ứng dụng root của bạn nhiều hơn</string>\n    <string name=\"log_data_magisk_none\">Nhật ký Magisk trống, điều đó thật kỳ lạ</string>\n    <string name=\"menuSaveLog\">Lưu nhật ký</string>\n    <string name=\"menuClearLog\">Xóa nhật ký ngay bây giờ</string>\n    <string name=\"logs_cleared\">Đã xóa nhật ký thành công</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">UID mục tiêu: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">Hiển thị các ứng dụng hệ thống</string>\n    <string name=\"show_os_app\">Hiển thị các ứng dụng hệ điều hành</string>\n    <string name=\"hide_filter_hint\">Lọc theo tên</string>\n    <string name=\"hide_search\">Tìm kiếm</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">(Không cung cấp thông tin)</string>\n    <string name=\"reboot_userspace\">Khởi động lại</string>\n    <string name=\"reboot_recovery\">Khởi động lại vào Recovery Mode</string>\n    <string name=\"reboot_bootloader\">Khởi động lại vào Bootloader</string>\n    <string name=\"reboot_download\">Khởi động lại vào Download</string>\n    <string name=\"reboot_edl\">Khởi động lại vào EDL</string>\n    <string name=\"module_version_author\">%1$s bởi %2$s</string>\n    <string name=\"module_state_remove\">Gỡ bỏ</string>\n    <string name=\"module_state_restore\">Khôi phục</string>\n    <string name=\"module_action_install_external\">Cài đặt từ bộ nhớ</string>\n    <string name=\"update_available\">Cập nhật có sẵn</string>\n    <string name=\"suspend_text_riru\">Mô-đun bị vô hiệu hóa vì %1$s được bật</string>\n    <string name=\"suspend_text_zygisk\">Mô-đun bị vô hiệu hóa vì %1$s không được bật</string>\n    <string name=\"zygisk_module_unloaded\">Không thể tải mô-đun Zygisk vì không tương thích</string>\n    <string name=\"module_empty\">Chưa có mô-đun nào được cài đặt!</string>\n\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">Chế độ chủ đề</string>\n    <string name=\"settings_dark_mode_message\">Chọn chế độ phù hợp nhất với phong cách của bạn!</string>\n    <string name=\"settings_dark_mode_light\">Luôn sáng</string>\n    <string name=\"settings_dark_mode_system\">Theo hệ thống</string>\n    <string name=\"settings_dark_mode_dark\">Luôn tối</string>\n    <string name=\"settings_download_path_title\">Đường dẫn tải xuống</string>\n    <string name=\"settings_download_path_message\">Các tệp sẽ được lưu vào %1$s</string>\n    <string name=\"settings_hide_app_title\">Ẩn ứng dụng Magisk</string>\n    <string name=\"settings_hide_app_summary\">Cài đặt ứng dụng proxy với ID gói ngẫu nhiên và nhãn ứng dụng tùy chỉnh</string>\n    <string name=\"settings_restore_app_title\">Khôi phục ứng dụng Magisk</string>\n    <string name=\"settings_restore_app_summary\">Bỏ ẩn ứng dụng và khôi phục nó về APK ban đầu</string>\n    <string name=\"language\">Ngôn ngữ</string>\n    <string name=\"system_default\">(Mặc định hệ thống)</string>\n    <string name=\"settings_check_update_title\">Kiểm tra cập nhật</string>\n    <string name=\"settings_check_update_summary\">Kiểm tra định kỳ các bản cập nhật trong nền</string>\n    <string name=\"settings_update_channel_title\">Kênh cập nhật</string>\n    <string name=\"settings_update_stable\">Ổn định</string>\n    <string name=\"settings_update_beta\">Beta</string>\n    <string name=\"settings_update_custom\">Kênh tùy chỉnh</string>\n    <string name=\"settings_update_custom_msg\">Chèn một URL tùy chỉnh</string>\n    <string name=\"settings_zygisk_summary\">Khởi chạy các thành phần của Magisk trong tiến trình nền zygote</string>\n    <string name=\"settings_denylist_title\">Thực thi danh sách loại trừ</string>\n    <string name=\"settings_denylist_summary\">Mọi sửa đổi do Magisk thực hiện sẽ bị loại khỏi các quy trình trong danh sách loại trừ</string>\n    <string name=\"settings_denylist_config_title\">Cấu hình DenyList</string>\n    <string name=\"settings_denylist_config_summary\">Chọn các quy trình được đưa vào DenyList</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">Hỗ trợ systemless hosts cho các ứng dụng chặn quảng cáo</string>\n    <string name=\"settings_hosts_toast\">Đã thêm mô-đun systemless hosts</string>\n    <string name=\"settings_app_name_hint\">Tên mới</string>\n    <string name=\"settings_app_name_helper\">Ứng dụng sẽ được đóng gói lại với tên này</string>\n    <string name=\"settings_app_name_error\">Định dạng không hợp lệ</string>\n    <string name=\"settings_su_app_adb\">Ứng dụng và ADB</string>\n    <string name=\"settings_su_app\">Chỉ ứng dụng</string>\n    <string name=\"settings_su_adb\">Chỉ ADB</string>\n    <string name=\"settings_su_disable\">Vô hiệu hóa</string>\n    <string name=\"settings_su_request_10\">10 giây</string>\n    <string name=\"settings_su_request_15\">15 giây</string>\n    <string name=\"settings_su_request_20\">20 giây</string>\n    <string name=\"settings_su_request_30\">30 giây</string>\n    <string name=\"settings_su_request_45\">45 giây</string>\n    <string name=\"settings_su_request_60\">60 giây</string>\n    <string name=\"superuser_access\">Quyền truy cập Superuser</string>\n    <string name=\"auto_response\">Đáp ứng tự động</string>\n    <string name=\"request_timeout\">Hết thời gian yêu cầu</string>\n    <string name=\"superuser_notification\">Thông báo của Superuser</string>\n    <string name=\"settings_su_reauth_title\">Xác thực lại sau khi nâng cấp</string>\n    <string name=\"settings_su_reauth_summary\">Xác thực lại quyền Superuser sau khi nâng cấp ứng dụng</string>\n    <string name=\"settings_su_tapjack_title\">Bảo vệ khỏi Tapjacking</string>\n    <string name=\"settings_su_tapjack_summary\">Hộp thoại nhắc Superuser sẽ không trả lời đầu vào khi bị che khuất bởi bất kỳ cửa sổ hoặc lớp phủ nào khác</string>\n    <string name=\"settings_customization\">Tùy biến</string>\n    <string name=\"setting_add_shortcut_summary\">Thêm một phím tắt đẹp vào màn hình trong trường hợp khó tìm ra tên và biểu tượng sau khi ẩn ứng dụng</string>\n    <string name=\"settings_doh_title\">DNS over HTTPS</string>\n    <string name=\"settings_doh_description\">Workaround DNS poisoning in some nations</string>\n\n    <string name=\"multiuser_mode\">Chế độ đa người dùng</string>\n    <string name=\"settings_owner_only\">Chỉ chủ sở hữu thiết bị</string>\n    <string name=\"settings_owner_manage\">Chủ sở hữu thiết bị được quản lý</string>\n    <string name=\"settings_user_independent\">Người dùng độc lập</string>\n    <string name=\"owner_only_summary\">Chỉ chủ sở hữu mới có quyền truy cập root</string>\n    <string name=\"owner_manage_summary\">Chỉ chủ sở hữu mới có thể quản lý quyền truy cập root và nhận lời nhắc yêu cầu</string>\n    <string name=\"user_independent_summary\">Mỗi người dùng có các quy tắc root riêng biệt của riêng mình</string>\n\n    <string name=\"mount_namespace_mode\">Chế độ cho Mount namespace</string>\n    <string name=\"settings_ns_global\">Không gian tên chung</string>\n    <string name=\"settings_ns_requester\">Không gian tên kế thừa</string>\n    <string name=\"settings_ns_isolate\">Không gian tên độc lập</string>\n    <string name=\"global_summary\">Tất cả các phiên root sử dụng không gian tên gắn kết chung</string>\n    <string name=\"requester_summary\">Phiên root sẽ kế thừa không gian tên của người yêu cầu của họ</string>\n    <string name=\"isolate_summary\">Mỗi phiên root sẽ có không gian tên riêng biệt</string>\n\n    <!--Thông báo-->\n    <string name=\"update_channel\">Cập nhật Magisk</string>\n    <string name=\"progress_channel\">Thông báo tiến độ</string>\n    <string name=\"updated_channel\">Cập nhật hoàn tất</string>\n    <string name=\"download_complete\">Tải về hoàn tất</string>\n    <string name=\"download_file_error\">Lỗi khi tải xuống tệp</string>\n    <string name=\"magisk_update_title\">Cập nhật Magisk có sẵn!</string>\n    <string name=\"updated_title\">Đã cập nhật Magisk</string>\n    <string name=\"updated_text\">Chạm để mở ứng dụng</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">Có</string>\n    <string name=\"no\">Không</string>\n    <string name=\"repo_install_title\">Cài đặt %1$s %2$s(%3$d)</string>\n    <string name=\"download\">Tải xuống</string>\n    <string name=\"reboot\">Khởi động lại</string>\n    <string name=\"release_notes\">Ghi chú bản phát hành</string>\n    <string name=\"flashing\">Đang cài…</string>\n    <string name=\"done\">Xong!</string>\n    <string name=\"failure\">Thất bại!</string>\n    <string name=\"hide_app_title\">Đang ẩn ứng dụng Magisk…</string>\n    <string name=\"open_link_failed_toast\">Không tìm thấy ứng dụng nào để mở liên kết</string>\n    <string name=\"complete_uninstall\">Hoàn thành Gỡ cài đặt</string>\n    <string name=\"restore_img\">Khôi phục đĩa ảnh boot (boot image)</string>\n    <string name=\"restore_img_msg\">Đang khôi phục…</string>\n    <string name=\"restore_done\">Đã khôi phục xong!</string>\n    <string name=\"restore_fail\">Bản sao lưu gốc không tồn tại!</string>\n    <string name=\"setup_fail\">Thiết lập không thành công</string>\n    <string name=\"env_fix_title\">Yêu cầu thiết lập bổ sung</string>\n    <string name=\"env_fix_msg\">Thiết bị của bạn cần thiết lập bổ sung để Magisk hoạt động bình thường. Bạn có muốn tiếp tục và khởi động lại không?</string>\n    <string name=\"setup_msg\">Đang chạy thiết lập môi trường…</string>\n    <string name=\"unsupport_magisk_title\">Phiên bản Magisk không được hỗ trợ</string>\n    <string name=\"unsupport_magisk_msg\">Phiên bản hiện tại của ứng dụng không hỗ trợ phiên bản Magisk thấp hơn %1$s.\\n\\nỨng dụng sẽ hoạt động như thể Magisk chưa được cài đặt. Vui lòng nâng cấp lên phiên bản mới nhất.</string>\n    <string name=\"unsupport_general_title\">Trạng thái bất thường</string>\n    <string name=\"unsupport_system_app_msg\">Không hỗ trợ chạy ứng dụng này dưới dạng ứng dụng hệ thống. Vui lòng hoàn nguyên ứng dụng về ứng dụng người dùng.</string>\n    <string name=\"unsupport_other_su_msg\">Một lệnh \\\"su\\\" không thuộc về Magisk được phát hiện. Vui lòng gỡ bỏ bất kì phương pháp root khác và/hoặc cài đặt lại Magisk.</string>\n    <string name=\"unsupport_external_storage_msg\">Magisk được cài đặt vào bộ nhớ ngoài. Vui lòng chuyển ứng dụng vào bộ nhớ trong.</string>\n    <string name=\"unsupport_nonroot_stub_msg\">Ứng dụng không thể tiếp tục hoạt động ở trạng thái ẩn vì mất quyền root. Vui lòng khôi phục nó trở lại APK ban đầu.</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">Cấp quyền lưu trữ để bật chức năng này</string>\n    <string name=\"install_unknown_denied\">Cho phép \"cài đặt ứng dụng không rõ nguồn gốc\" để bật chức năng này</string>\n    <string name=\"add_shortcut_title\">Thêm lối tắt vào màn hình chính</string>\n    <string name=\"add_shortcut_msg\">Sau khi ẩn ứng dụng này, tên và biểu tượng của nó có thể trở nên khó nhận ra. Bạn có muốn thêm một phím tắt đẹp vào màn hình chính không?</string>\n    <string name=\"app_not_found\">Không tìm thấy ứng dụng nào để xử lý hành động này</string>\n    <string name=\"reboot_apply_change\">Khởi động lại để áp dụng các thay đổi</string>\n    <string name=\"restore_app_confirmation\">Điều này sẽ khôi phục ứng dụng ẩn về nguyên bản. Bạn chắc chắn muốn làm điều này chứ?</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-zh-rCN/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">模块</string>\n    <string name=\"superuser\">超级用户</string>\n    <string name=\"logs\">日志</string>\n    <string name=\"settings\">设置</string>\n    <string name=\"install\">安装</string>\n    <string name=\"section_home\">主页</string>\n    <string name=\"section_theme\">主题</string>\n    <string name=\"denylist\">排除列表</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">无法连接</string>\n    <string name=\"app_changelog\">更新日志</string>\n    <string name=\"loading\">正在加载</string>\n    <string name=\"update\">更新</string>\n    <string name=\"not_available\">无法获取</string>\n    <string name=\"hide\">不再显示</string>\n    <string name=\"home_package\">包名</string>\n    <string name=\"home_app_title\">App</string>\n\n    <string name=\"home_notice_content\">仅从官方 GitHub 页面下载 Magisk。未知来源的文件可能具有恶意行为！</string>\n    <string name=\"home_support_title\">支持开发</string>\n    <string name=\"home_follow_title\">关注我们</string>\n    <string name=\"home_item_source\">源代码</string>\n    <string name=\"home_support_content\">Magisk 将一直保持免费且开源，向开发者捐赠以表示支持。</string>\n    <string name=\"home_installed_version\">当前</string>\n    <string name=\"home_latest_version\">最新</string>\n    <string name=\"invalid_update_channel\">无效的更新通道</string>\n    <string name=\"uninstall_magisk_title\">卸载 Magisk</string>\n    <string name=\"uninstall_magisk_msg\">所有模块将被停用或删除！超级用户权限丢失！\\n如果设备尚未加密，用户数据可能被自动加密。</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">保持强制加密</string>\n    <string name=\"keep_dm_verity\">保留 AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">安装到 Recovery</string>\n    <string name=\"install_options_title\">选项</string>\n    <string name=\"install_method_title\">方式</string>\n    <string name=\"install_next\">下一步</string>\n    <string name=\"install_start\">开始</string>\n    <string name=\"manager_download_install\">立即安装</string>\n    <string name=\"direct_install\">直接安装（推荐）</string>\n    <string name=\"install_inactive_slot\">安装到未使用的槽位（OTA 后）</string>\n    <string name=\"install_inactive_slot_msg\">将在重启后强制切换到另一个槽位！注意只能在 OTA 更新完成后的重启之前使用。</string>\n    <string name=\"setup_title\">修复安装</string>\n    <string name=\"select_patch_file\">选择并修补一个文件</string>\n    <string name=\"patch_file_msg\">选择一个原始映像文件（*.img）、Odin 包（*.tar）或 payload.bin（*.bin）</string>\n    <string name=\"reboot_delay_toast\">设备将在 5 秒后重启</string>\n    <string name=\"flash_screen_title\">安装</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">超级用户请求</string>\n    <string name=\"touch_filtered_warning\">由于某个应用遮挡了超级用户请求界面，因此 Magisk 无法验证您的回应</string>\n    <string name=\"deny\">拒绝</string>\n    <string name=\"prompt\">提示</string>\n    <string name=\"restrict\">受限</string>\n    <string name=\"grant\">允许</string>\n    <string name=\"su_warning\">将授予对该设备的最高权限。\\n如果不确定，请拒绝！</string>\n    <string name=\"forever\">永久</string>\n    <string name=\"once\">仅此一次</string>\n    <string name=\"tenmin\">10 分钟</string>\n    <string name=\"twentymin\">20 分钟</string>\n    <string name=\"thirtymin\">30 分钟</string>\n    <string name=\"sixtymin\">60 分钟</string>\n    <string name=\"su_allow_toast\">%1$s 已被授予超级用户权限</string>\n    <string name=\"su_deny_toast\">%1$s 已被拒绝超级用户权限</string>\n    <string name=\"su_snack_grant\">已授予 %1$s 超级用户权限</string>\n    <string name=\"su_snack_deny\">已拒绝 %1$s 超级用户权限</string>\n    <string name=\"su_snack_notif_on\">已启用 %1$s 的通知</string>\n    <string name=\"su_snack_notif_off\">已禁用 %1$s 的通知</string>\n    <string name=\"su_snack_log_on\">已启用对 %1$s 的日志记录</string>\n    <string name=\"su_snack_log_off\">已禁用对 %1$s 的日志记录</string>\n    <string name=\"su_revoke_title\">撤销</string>\n    <string name=\"su_revoke_msg\">确认撤销授予 %1$s 的权限？</string>\n    <string name=\"toast\">消息提示</string>\n    <string name=\"none\">无</string>\n\n    <string name=\"superuser_toggle_notification\">通知</string>\n    <string name=\"superuser_toggle_revoke\">撤销</string>\n    <string name=\"superuser_policy_none\">尚无应用请求超级用户权限</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">没有超级用户权限使用日志</string>\n    <string name=\"log_data_magisk_none\">没有 Magisk 日志</string>\n    <string name=\"menuSaveLog\">保存日志</string>\n    <string name=\"menuClearLog\">清空日志</string>\n    <string name=\"logs_cleared\">日志已清空</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">目标 UID: %1$d</string>\n    <string name=\"target_pid\">挂载命名空间目标 PID: %s</string>\n    <string name=\"selinux_context\">SELinux 上下文: %s</string>\n    <string name=\"supp_group\">补充组: %s</string>\n\n    <!--SafetyNet-->\n\n    <!--MagiskHide-->\n    <string name=\"show_system_app\">显示系统应用</string>\n    <string name=\"show_os_app\">显示操作系统</string>\n    <string name=\"hide_filter_hint\">按名称过滤</string>\n    <string name=\"hide_search\">搜索</string>\n\n    <!--Module-->\n    <string name=\"no_info_provided\">（未提供信息）</string>\n    <string name=\"reboot_userspace\">软重启</string>\n    <string name=\"reboot_recovery\">重启到 Recovery</string>\n    <string name=\"reboot_bootloader\">重启到 Bootloader</string>\n    <string name=\"reboot_download\">重启到 Download</string>\n    <string name=\"reboot_edl\">重启到 EDL</string>\n    <string name=\"reboot_safe_mode\">安全模式</string>\n    <string name=\"module_version_author\">%1$s，作者 %2$s</string>\n    <string name=\"module_state_remove\">移除</string>\n    <string name=\"module_action\">操作</string>\n    <string name=\"module_state_restore\">还原</string>\n    <string name=\"module_action_install_external\">从本地安装</string>\n    <string name=\"update_available\">可更新</string>\n    <string name=\"suspend_text_riru\">%1$s 已启用，此模块暂停加载</string>\n    <string name=\"suspend_text_zygisk\">%1$s 未启用，此模块暂停加载</string>\n    <string name=\"zygisk_module_unloaded\">存在兼容性问题，此模块未加载</string>\n    <string name=\"module_empty\">未安装任何模块</string>\n    <string name=\"confirm_install\">安装模块 %1$s？</string>\n    <string name=\"confirm_install_title\">安装确认</string>\n\n    <!--Settings-->\n    <string name=\"settings_dark_mode_title\">主题模式</string>\n    <string name=\"settings_dark_mode_message\">选择一个模式</string>\n    <string name=\"settings_dark_mode_light\">明亮模式</string>\n    <string name=\"settings_dark_mode_system\">跟随系统</string>\n    <string name=\"settings_dark_mode_dark\">深色模式</string>\n    <string name=\"settings_download_path_title\">下载路径</string>\n    <string name=\"settings_download_path_message\">文件将保存到 %1$s</string>\n    <string name=\"settings_hide_app_title\">隐藏 Magisk 应用</string>\n    <string name=\"settings_hide_app_summary\">安装具有随机包名和自定义应用名称的代理应用</string>\n    <string name=\"settings_restore_app_title\">还原 Magisk 应用</string>\n    <string name=\"settings_restore_app_summary\">取消隐藏，恢复到原始应用</string>\n    <string name=\"language\">语言</string>\n    <string name=\"system_default\">系统默认</string>\n    <string name=\"settings_check_update_title\">检查更新</string>\n    <string name=\"settings_check_update_summary\">定期在后台检查更新</string>\n    <string name=\"settings_update_channel_title\">更新通道</string>\n    <string name=\"settings_update_stable\">稳定版</string>\n    <string name=\"settings_update_beta\">测试版</string>\n    <string name=\"settings_update_custom\">自定义通道</string>\n    <string name=\"settings_update_custom_msg\">自定义通道网址</string>\n    <string name=\"settings_zygisk_summary\">在 Zygote 中运行 Magisk</string>\n    <string name=\"settings_denylist_title\">遵守排除列表</string>\n    <string name=\"settings_denylist_summary\">Magisk 不会修改列表中的进程</string>\n    <string name=\"settings_denylist_config_title\">配置排除列表</string>\n    <string name=\"settings_denylist_config_summary\">选择加入排除列表的进程</string>\n    <string name=\"settings_hosts_title\">Systemless hosts</string>\n    <string name=\"settings_hosts_summary\">为广告屏蔽应用提供 Systemless hosts 支持</string>\n    <string name=\"settings_hosts_toast\">已添加 systemless hosts 模块</string>\n    <string name=\"settings_app_name_hint\">新的应用名称</string>\n    <string name=\"settings_app_name_helper\">将使用新名称重新安装本应用</string>\n    <string name=\"settings_app_name_error\">无效输入</string>\n    <string name=\"settings_su_app_adb\">应用和 ADB</string>\n    <string name=\"settings_su_app\">仅应用</string>\n    <string name=\"settings_su_adb\">仅 ADB</string>\n    <string name=\"settings_su_disable\">已禁用</string>\n    <string name=\"settings_su_request_10\">10 秒</string>\n    <string name=\"settings_su_request_15\">15 秒</string>\n    <string name=\"settings_su_request_20\">20 秒</string>\n    <string name=\"settings_su_request_30\">30 秒</string>\n    <string name=\"settings_su_request_45\">45 秒</string>\n    <string name=\"settings_su_request_60\">60 秒</string>\n    <string name=\"superuser_access\">超级用户访问权限</string>\n    <string name=\"auto_response\">自动响应</string>\n    <string name=\"request_timeout\">请求超时</string>\n    <string name=\"superuser_notification\">超级用户通知</string>\n    <string name=\"settings_su_reauth_title\">更新后重新认证</string>\n    <string name=\"settings_su_reauth_summary\">应用更新后重新认证超级用户权限</string>\n    <string name=\"settings_su_tapjack_title\">点按劫持保护</string>\n    <string name=\"settings_su_tapjack_summary\">存在屏幕叠加层时，超级用户请求弹窗不响应允许操作</string>\n    <string name=\"settings_su_auth_title\">身份验证</string>\n    <string name=\"settings_su_auth_summary\">对超级用户请求验证身份</string>\n    <string name=\"settings_su_auth_insecure\">设备未配置验证方式</string>\n    <string name=\"settings_su_restrict_title\">限制超级用户权能</string>\n    <string name=\"settings_su_restrict_summary\">默认限制新的超级用户应用。警告，这会破坏大多数应用，不建议启用。</string>\n    <string name=\"settings_customization\">个性化</string>\n    <string name=\"setting_add_shortcut_summary\">在隐藏后难以识别名称和图标的情况下，添加快捷方式到桌面</string>\n    <string name=\"settings_doh_title\">安全 DNS（DoH）</string>\n    <string name=\"settings_doh_description\">解决某些地区的 DNS 污染问题</string>\n    <string name=\"settings_random_name_title\">随机文件名</string>\n    <string name=\"settings_random_name_description\">随机修补镜像和 tar 文件的文件名以防止检测</string>\n\n    <string name=\"multiuser_mode\">多用户模式</string>\n    <string name=\"settings_owner_only\">仅设备所有者</string>\n    <string name=\"settings_owner_manage\">由设备所有者管理</string>\n    <string name=\"settings_user_independent\">各用户独立</string>\n    <string name=\"owner_only_summary\">仅设备所有者有超级用户权限</string>\n    <string name=\"owner_manage_summary\">仅设备所有者能管理超级用户并接收权限请求提示</string>\n    <string name=\"user_independent_summary\">每个用户有独立的超级用户规则</string>\n\n    <string name=\"mount_namespace_mode\">挂载命名空间模式</string>\n    <string name=\"settings_ns_global\">全局命名空间</string>\n    <string name=\"settings_ns_requester\">继承命名空间</string>\n    <string name=\"settings_ns_isolate\">独立命名空间</string>\n    <string name=\"global_summary\">所有 ROOT 会话使用全局挂载命名空间</string>\n    <string name=\"requester_summary\">ROOT 会话继承原进程的命名空间</string>\n    <string name=\"isolate_summary\">每个 ROOT 会话使用独立的命名空间</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">更新提示</string>\n    <string name=\"progress_channel\">下载进度</string>\n    <string name=\"updated_channel\">更新完成</string>\n    <string name=\"download_complete\">下载完成</string>\n    <string name=\"download_file_error\">下载失败</string>\n    <string name=\"magisk_update_title\">Magisk 已发布新版本！</string>\n    <string name=\"updated_title\">Magisk 已完成更新</string>\n    <string name=\"updated_text\">点按即可打开应用</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"repo_install_title\">安装 %1$s %2$s(%3$d)</string>\n    <string name=\"download\">下载</string>\n    <string name=\"reboot\">重启</string>\n    <string name=\"close\">关闭</string>\n    <string name=\"release_notes\">发布说明</string>\n    <string name=\"flashing\">正在刷入</string>\n    <string name=\"running\">运行中……</string>\n    <string name=\"done\">完成！</string>\n    <string name=\"done_action\">%1$s 操作运行完成</string>\n    <string name=\"failure\">失败</string>\n    <string name=\"hide_app_title\">正在隐藏 Magisk 应用</string>\n    <string name=\"open_link_failed_toast\">找不到能打开此链接的应用</string>\n    <string name=\"complete_uninstall\">完全卸载</string>\n    <string name=\"restore_img\">还原原厂映像</string>\n    <string name=\"restore_img_msg\">正在还原</string>\n    <string name=\"restore_done\">已还原</string>\n    <string name=\"restore_fail\">原厂 Boot 映像的备份不存在</string>\n    <string name=\"setup_fail\">安装失败</string>\n    <string name=\"env_fix_title\">需要修复运行环境</string>\n    <string name=\"env_fix_msg\">需要一些额外的安装才能使 Magisk 正常工作。完成后自动重启，是否继续？</string>\n    <string name=\"env_full_fix_msg\">需要重新安装才能使 Magisk 正常工作。请在应用内重新安装，recovery 模式无法正确获取设备信息。</string>\n    <string name=\"setup_msg\">正在修复运行环境</string>\n    <string name=\"unsupport_magisk_title\">不支持的 Magisk 版本</string>\n    <string name=\"unsupport_magisk_msg\">应用不支持低于 %1$s 版本的 Magisk，表现为未安装状态。但升级功能可用，请尽快在应用内升级 Magisk。</string>\n    <string name=\"unsupport_general_title\">异常状态</string>\n    <string name=\"unsupport_system_app_msg\">Magisk 不支持安装为系统应用，请还原为用户应用。</string>\n    <string name=\"unsupport_other_su_msg\">检测到不属于 Magisk 的 su 文件，请删除其他超级用户程序。</string>\n    <string name=\"unsupport_external_storage_msg\">不支持将 Magisk 安装到外置存储卡，请将应用移动回内部存储空间。</string>\n    <string name=\"unsupport_nonroot_stub_msg\">超级用户权限丢失，应用无法在隐藏状态下继续工作，请恢复到原始 Magisk 应用。</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">允许访问存储空间以使用此功能</string>\n    <string name=\"post_notifications_denied\">允许发送通知以使用此功能</string>\n    <string name=\"install_unknown_denied\">允许安装未知应用以使用此功能</string>\n    <string name=\"add_shortcut_title\">添加快捷方式到桌面</string>\n    <string name=\"add_shortcut_msg\">隐藏后应用的名字和图标可能难以识别。需要在桌面上添加具有原始名称和图标的快捷方式吗？</string>\n    <string name=\"app_not_found\">找不到可处理此操作的应用</string>\n    <string name=\"reboot_apply_change\">重启后生效</string>\n    <string name=\"restore_app_confirmation\">即将把隐藏的 Magisk 应用恢复回原始应用，是否继续？</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources>\n\n    <!--Sections-->\n    <string name=\"modules\">模組</string>\n    <string name=\"superuser\">超級使用者</string>\n    <string name=\"logs\">紀錄</string>\n    <string name=\"settings\">設定</string>\n    <string name=\"install\">安裝</string>\n    <string name=\"section_home\">首頁</string>\n    <string name=\"section_theme\">主題</string>\n    <string name=\"denylist\">黑名單</string>\n\n    <!--Home-->\n    <string name=\"no_connection\">無法連線</string>\n    <string name=\"app_changelog\">變更紀錄</string>\n    <string name=\"loading\">載入中……</string>\n    <string name=\"update\">更新</string>\n    <string name=\"not_available\">無</string>\n    <string name=\"hide\">隱藏</string>\n    <string name=\"home_package\">套件</string>\n    <string name=\"home_app_title\">應用程式</string>\n\n    <string name=\"home_notice_content\">請從官方的 Github 網頁下載 Magisk。從未知來源下載的檔案可能懷有惡意！</string>\n    <string name=\"home_support_title\">支持開發</string>\n    <string name=\"home_follow_title\">追蹤我們</string>\n    <string name=\"home_item_source\">原始碼</string>\n    <string name=\"home_support_content\">Magisk 無論現在和未來永遠是免費且開源。但您可以透過發送小額的抖內來彰顯您對我們的支持。</string>\n    <string name=\"home_installed_version\">已安裝</string>\n    <string name=\"home_latest_version\">最新</string>\n    <string name=\"invalid_update_channel\">無效的更新頻道</string>\n    <string name=\"uninstall_magisk_title\">解除安裝 Magisk</string>\n    <string name=\"uninstall_magisk_msg\">所有模組將會被停用並移除！\\nRoot 將會被移除且未被加密的資料將可能被加密！</string>\n\n    <!--Install-->\n    <string name=\"keep_force_encryption\">保持強制加密</string>\n    <string name=\"keep_dm_verity\">保持 AVB 2.0/dm-verity</string>\n    <string name=\"recovery_mode\">安裝至 Recovery</string>\n    <string name=\"install_options_title\">選項</string>\n    <string name=\"install_method_title\">安裝方式</string>\n    <string name=\"install_next\">下一步</string>\n    <string name=\"install_start\">開始執行</string>\n    <string name=\"manager_download_install\">點選以下載並安裝</string>\n    <string name=\"direct_install\">直接安裝（建議）</string>\n    <string name=\"install_inactive_slot\">安裝到非使用中的槽位（在 OTA 更新後）</string>\n    <string name=\"install_inactive_slot_msg\">您的裝置將在下次重新啟動後強制切換到非使用中的槽位！\\n這個選項僅在 OTA 更新完畢後使用。\\n請問是否繼續？</string>\n    <string name=\"setup_title\">修復安裝</string>\n    <string name=\"select_patch_file\">選擇並修補檔案</string>\n    <string name=\"patch_file_msg\">請選取未修改過的映像檔 (*.img) 或 Odin 的 TAR 檔案 (*.tar) 或 payload.bin (*.bin)</string>\n    <string name=\"reboot_delay_toast\">將在 5 秒後重新啟動……</string>\n    <string name=\"flash_screen_title\">安裝</string>\n\n    <!--Superuser-->\n    <string name=\"su_request_title\">超級使用者要求</string>\n    <string name=\"touch_filtered_warning\">由於某個應用程式遮蔽了超級使用者要求的視窗，因此 Magisk 無法驗證您的回應</string>\n    <string name=\"deny\">拒絕</string>\n    <string name=\"prompt\">提示</string>\n    <string name=\"grant\">授予</string>\n    <string name=\"su_warning\">將授予本裝置使用 Root 權限。\\n如果不確定，請拒絕！</string>\n    <string name=\"forever\">永久</string>\n    <string name=\"once\">僅此一次</string>\n    <string name=\"tenmin\">10 分鐘</string>\n    <string name=\"twentymin\">20 分鐘</string>\n    <string name=\"thirtymin\">30 分鐘</string>\n    <string name=\"sixtymin\">60 分鐘</string>\n    <string name=\"su_allow_toast\">已授予 %1$s 使用超級使用者的權限</string>\n    <string name=\"su_deny_toast\">已拒絕 %1$s 使用超級使用者的權限</string>\n    <string name=\"su_snack_grant\">已授予 %1$s 使用超級使用者的權限</string>\n    <string name=\"su_snack_deny\">已拒絕 %1$s 使用超級使用者的權限</string>\n    <string name=\"su_snack_notif_on\">已啟用 %1$s 通知</string>\n    <string name=\"su_snack_notif_off\">已停用 %1$s 通知</string>\n    <string name=\"su_snack_log_on\">已啟用 %1$s 寫入紀錄</string>\n    <string name=\"su_snack_log_off\">已停用 %1$s 寫入紀錄</string>\n    <string name=\"su_revoke_title\">撤銷權限？</string>\n    <string name=\"su_revoke_msg\">確定撤銷 %1$s 的權限？</string>\n    <string name=\"toast\">快顯通知</string>\n    <string name=\"none\">無</string>\n\n    <string name=\"superuser_toggle_notification\">通知</string>\n    <string name=\"superuser_toggle_revoke\">撤銷</string>\n    <string name=\"superuser_policy_none\">目前沒有任何應用程式要求超級使用者的權限。</string>\n\n    <!--Logs-->\n    <string name=\"log_data_none\">您的紀錄是空的，請嘗試使用具備需要超級使用者權限的應用程式。</string>\n    <string name=\"log_data_magisk_none\">Magisk 的紀錄是空的，很奇怪……</string>\n    <string name=\"menuSaveLog\">儲存紀錄</string>\n    <string name=\"menuClearLog\">清除紀錄</string>\n    <string name=\"logs_cleared\">已成功清除紀錄</string>\n    <string name=\"pid\">PID: %1$d</string>\n    <string name=\"target_uid\">目標 UID: %1$d</string>\n\n    <!--SafetyNet-->\n\n    <!-- MagiskHide -->\n    <string name=\"show_system_app\">顯示系統應用程式</string>\n    <string name=\"show_os_app\">顯示作業系统</string>\n    <string name=\"hide_filter_hint\">過濾名稱</string>\n    <string name=\"hide_search\">搜尋</string>\n\n    <!--Module -->\n    <string name=\"no_info_provided\">（未提供資訊）</string>\n    <string name=\"reboot_userspace\">快速重新啟動</string>\n    <string name=\"reboot_recovery\">重新啟動至 Recovery</string>\n    <string name=\"reboot_bootloader\">重新啟動至 Bootloader</string>\n    <string name=\"reboot_download\">重新啟動至 Download</string>\n    <string name=\"reboot_edl\">重新啟動至 EDL</string>\n    <string name=\"module_version_author\">%1$s 來自 %2$s</string>\n    <string name=\"module_state_remove\">移除</string>\n    <string name=\"module_state_restore\">復原</string>\n    <string name=\"module_action_install_external\">從本機安裝</string>\n    <string name=\"update_available\">有可用的更新</string>\n    <string name=\"suspend_text_riru\">此模組因 %1$s 已啟用而暫停運作</string>\n    <string name=\"suspend_text_zygisk\">此模組因 %1$s 未啟用而暫停運作</string>\n    <string name=\"zygisk_module_unloaded\">此模組因不相容 Zygisk 而未載入</string>\n    <string name=\"module_empty\">未安裝任何模組</string>\n    <string name=\"confirm_install\">安裝模組 %1$s？</string>\n    <string name=\"confirm_install_title\">安裝確認</string>\n\n    <!--Settings -->\n    <string name=\"settings_dark_mode_title\">主題模式</string>\n    <string name=\"settings_dark_mode_message\">選擇最符合您風格的模式！</string>\n    <string name=\"settings_dark_mode_light\">淺色模式</string>\n    <string name=\"settings_dark_mode_system\">跟隨系統</string>\n    <string name=\"settings_dark_mode_dark\">深色模式</string>\n    <string name=\"settings_download_path_title\">下載路徑</string>\n    <string name=\"settings_download_path_message\">檔案將被儲存在：%1$s</string>\n    <string name=\"settings_hide_app_title\">隱藏 Magisk</string>\n    <string name=\"settings_hide_app_summary\">安裝一個隨機套件名稱和客製化應用程式名稱的代理應用程式</string>\n    <string name=\"settings_restore_app_title\">還原 Magisk</string>\n    <string name=\"settings_restore_app_summary\">取消隱藏並還原為原始套件</string>\n    <string name=\"language\">語言</string>\n    <string name=\"system_default\">（系統預設值）</string>\n    <string name=\"settings_check_update_title\">檢查更新</string>\n    <string name=\"settings_check_update_summary\">定期於背景檢查更新</string>\n    <string name=\"settings_update_channel_title\">更新頻道</string>\n    <string name=\"settings_update_stable\">穩定版</string>\n    <string name=\"settings_update_beta\">測試版</string>\n    <string name=\"settings_update_custom\">自訂頻道</string>\n    <string name=\"settings_update_custom_msg\">輸入一個自訂的網址</string>\n    <string name=\"settings_zygisk_summary\">在 Zygote 中執行 Magisk</string>\n    <string name=\"settings_denylist_title\">強制黑名單</string>\n    <string name=\"settings_denylist_summary\">Magisk 黑名單上的處理程序將復原變更</string>\n    <string name=\"settings_denylist_config_title\">設定黑名單</string>\n    <string name=\"settings_denylist_config_summary\">選擇要包含在黑名單的處理程序</string>\n    <string name=\"settings_hosts_title\">主機（hosts）模組化</string>\n    <string name=\"settings_hosts_summary\">為廣告阻擋程式提供主機模組</string>\n    <string name=\"settings_hosts_toast\">已安裝主機模組</string>\n    <string name=\"settings_app_name_hint\">新的名稱</string>\n    <string name=\"settings_app_name_helper\">應用程式將以此名稱重新封裝</string>\n    <string name=\"settings_app_name_error\">無效的格式</string>\n    <string name=\"settings_su_app_adb\">應用程式及 ADB</string>\n    <string name=\"settings_su_app\">僅限應用程式</string>\n    <string name=\"settings_su_adb\">僅限 ADB</string>\n    <string name=\"settings_su_disable\">停用</string>\n    <string name=\"settings_su_request_10\">10 秒</string>\n    <string name=\"settings_su_request_15\">15 秒</string>\n    <string name=\"settings_su_request_20\">20 秒</string>\n    <string name=\"settings_su_request_30\">30 秒</string>\n    <string name=\"settings_su_request_45\">45 秒</string>\n    <string name=\"settings_su_request_60\">60 秒</string>\n    <string name=\"superuser_access\">超級使用者存取權限</string>\n    <string name=\"auto_response\">自動回應</string>\n    <string name=\"request_timeout\">要求逾時</string>\n    <string name=\"superuser_notification\">超級使用者通知</string>\n    <string name=\"settings_su_reauth_title\">更新後重新驗證</string>\n    <string name=\"settings_su_reauth_summary\">應用程式更新後，重新驗證超級使用者的要求</string>\n    <string name=\"settings_su_tapjack_title\">啟用點選攔截保護</string>\n    <string name=\"settings_su_tapjack_summary\">發現有其他應用程式重疊在超級使用者視窗上面時，不回應允許操作</string>\n    <string name=\"settings_customization\">客製化</string>\n    <string name=\"setting_add_shortcut_summary\">在主螢幕中新增一個精緻的捷徑。防止隱藏 Magisk 以後，其名稱與圖示將難以辨識</string>\n    <string name=\"settings_doh_title\">安全化的網域解析(DoH)</string>\n    <string name=\"settings_doh_description\">解決某些地區的 DNS 中毒問題</string>\n\n    <string name=\"multiuser_mode\">多重使用者模式</string>\n    <string name=\"settings_owner_only\">僅限裝置擁有者</string>\n    <string name=\"settings_owner_manage\">由裝置擁有者管理</string>\n    <string name=\"settings_user_independent\">使用者獨立管理</string>\n    <string name=\"owner_only_summary\">僅限裝置擁有者可使用 Root 權限</string>\n    <string name=\"owner_manage_summary\">僅限裝置擁有者可管理及接收 Root 權限的要求提示</string>\n    <string name=\"user_independent_summary\">每個使用者皆擁有獨立的 Root 權限規則</string>\n\n    <string name=\"mount_namespace_mode\">掛接命名空間模式</string>\n    <string name=\"settings_ns_global\">全域命名空間</string>\n    <string name=\"settings_ns_requester\">繼承命名空間</string>\n    <string name=\"settings_ns_isolate\">獨立命名空間</string>\n    <string name=\"global_summary\">所有 Root 工作階段皆使用全域的命名空間</string>\n    <string name=\"requester_summary\">所有 Root 工作階段皆繼承原程式的命名空間</string>\n    <string name=\"isolate_summary\">所有 Root 工作階段皆擁有獨立的命名空間</string>\n\n    <!--Notifications-->\n    <string name=\"update_channel\">Magisk 更新</string>\n    <string name=\"progress_channel\">進度通知</string>\n    <string name=\"updated_channel\">更新完成</string>\n    <string name=\"download_complete\">下載完成</string>\n    <string name=\"download_file_error\">下載錯誤</string>\n    <string name=\"magisk_update_title\">Magisk 有可用的更新！</string>\n    <string name=\"updated_title\">Magisk 已完成更新</string>\n    <string name=\"updated_text\">點選即可開啟應用程式</string>\n\n    <!--Toasts, Dialogs-->\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"repo_install_title\">安裝 %1$s %2$s(%3$d)</string>\n    <string name=\"download\">下載</string>\n    <string name=\"reboot\">重新啟動</string>\n    <string name=\"release_notes\">版本資訊</string>\n    <string name=\"flashing\">正在刷入……</string>\n    <string name=\"done\">完成！</string>\n    <string name=\"failure\">失敗！</string>\n    <string name=\"hide_app_title\">正在隱藏 Magisk……</string>\n    <string name=\"open_link_failed_toast\">沒有可以開啟此連結的應用程式</string>\n    <string name=\"complete_uninstall\">完全解除安裝</string>\n    <string name=\"restore_img\">還原原始映像檔</string>\n    <string name=\"restore_img_msg\">正在還原……</string>\n    <string name=\"restore_done\">還原完成！</string>\n    <string name=\"restore_fail\">不存在原始備份的映像檔！</string>\n    <string name=\"setup_fail\">安裝失敗</string>\n    <string name=\"env_fix_title\">需要修復執行環境</string>\n    <string name=\"env_fix_msg\">缺少讓 Magisk 正常執行所需的檔案。請您同意讓 Magisk 額外下載安裝包進行修復安裝，修復完成後將自動重新啟動。請問您是否繼續？</string>\n    <string name=\"env_full_fix_msg\">需要重新安裝才能使 Magisk 正常工作。請在應用程式內重新安裝，recovery 模式無法正確獲取設備訊息。</string>\n    <string name=\"setup_msg\">正在修復執行環境……</string>\n    <string name=\"unsupport_magisk_title\">不支援的 Magisk 版本</string>\n    <string name=\"unsupport_magisk_msg\">此應用程式的版本不支援 Magisk %1$s 版或更低的版本。\\n\\nMagisk 將顯示為未安裝的狀態。不過您仍然可以升級功能，請盡快升級。</string>\n    <string name=\"unsupport_general_title\">異常狀態</string>\n    <string name=\"unsupport_system_app_msg\">本應用程式不支援以系統應用程式的方式執行。請恢復為使用者應用程式。</string>\n    <string name=\"unsupport_other_su_msg\">偵測到一個不是來自 Magisk 的「su」二進位檔案。請移除其他 Root 方案。</string>\n    <string name=\"unsupport_external_storage_msg\">應用程式已被安裝到外部儲存空間。請移動應用程式至內部儲存空間。</string>\n    <string name=\"unsupport_nonroot_stub_msg\">應用程式無法在 Root 權限遺失的情況下以隱藏模式執行。請還原為原始套件。</string>\n    <string name=\"unsupport_nonroot_stub_title\">@string/settings_restore_app_title</string>\n    <string name=\"external_rw_permission_denied\">授予儲存空間存取權以啟用此功能</string>\n    <string name=\"install_unknown_denied\">允許「安裝未知應用程式」以啟用此功能</string>\n    <string name=\"add_shortcut_title\">在主螢幕中新增捷徑</string>\n    <string name=\"add_shortcut_msg\">在隱藏應用程式以後，其名稱與圖示將難以辨識。請問您想要在主螢幕中新增一個精緻的捷徑嗎？</string>\n    <string name=\"app_not_found\">沒有可以處理這個動作的應用程式</string>\n    <string name=\"reboot_apply_change\">重新啟動裝置以套用設定變更</string>\n    <string name=\"restore_app_confirmation\">這將會還原隱藏的應用程式至原始。請問您確定要執行？</string>\n\n</resources>\n"
  },
  {
    "path": "app/core/src/main/res/xml/locale_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<locale-config xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <locale android:name=\"ar\" />\n    <locale android:name=\"ast\" />\n    <locale android:name=\"az\" />\n    <locale android:name=\"be\" />\n    <locale android:name=\"bg\" />\n    <locale android:name=\"bn\" />\n    <locale android:name=\"ca\" />\n    <locale android:name=\"cs\" />\n    <locale android:name=\"de\" />\n    <locale android:name=\"el\" />\n    <locale android:name=\"en\" />\n    <locale android:name=\"es\" />\n    <locale android:name=\"et\" />\n    <locale android:name=\"fa\" />\n    <locale android:name=\"fr\" />\n    <locale android:name=\"hi\" />\n    <locale android:name=\"hr\" />\n    <locale android:name=\"hu\" />\n    <locale android:name=\"in\" />\n    <locale android:name=\"it\" />\n    <locale android:name=\"iw\" />\n    <locale android:name=\"ja\" />\n    <locale android:name=\"ka\" />\n    <locale android:name=\"kk\" />\n    <locale android:name=\"ko\" />\n    <locale android:name=\"lt\" />\n    <locale android:name=\"mk\" />\n    <locale android:name=\"ml\" />\n    <locale android:name=\"nb\" />\n    <locale android:name=\"nl\" />\n    <locale android:name=\"pa\" />\n    <locale android:name=\"pl\" />\n    <locale android:name=\"pt-BR\" />\n    <locale android:name=\"pt-PT\" />\n    <locale android:name=\"ro\" />\n    <locale android:name=\"ru\" />\n    <locale android:name=\"sk\" />\n    <locale android:name=\"sq\" />\n    <locale android:name=\"sr\" />\n    <locale android:name=\"sv\" />\n    <locale android:name=\"sw\" />\n    <locale android:name=\"ta\" />\n    <locale android:name=\"th\" />\n    <locale android:name=\"tr\" />\n    <locale android:name=\"uk\" />\n    <locale android:name=\"vi\" />\n    <locale android:name=\"zh-CN\" />\n    <locale android:name=\"zh-TW\" />\n</locale-config>\n"
  },
  {
    "path": "app/gradle/libs.versions.toml",
    "content": "[versions]\nkotlin = \"2.3.10\"\nandroid = \"9.0.1\"\nksp = \"2.3.4\"\nrikka = \"1.3.0\"\nnavigation = \"2.9.7\"\nlibsu = \"6.0.0\"\nokhttp = \"5.3.2\"\nretrofit = \"3.0.0\"\nroom = \"2.8.4\"\ncompose-bom = \"2026.02.01\"\nlifecycle = \"2.10.0\"\nactivity-compose = \"1.12.4\"\nmiuix = \"0.8.6\"\nnavigation3 = \"1.1.0-alpha05\"\nnavigationevent = \"1.0.2\"\n\n[libraries]\nbcpkix = { module = \"org.bouncycastle:bcpkix-jdk18on\", version = \"1.83\" }\ncommons-compress = { module = \"org.apache.commons:commons-compress\", version = \"1.28.0\" }\nretrofit = { module = \"com.squareup.retrofit2:retrofit\", version.ref = \"retrofit\" }\nretrofit-moshi = { module = \"com.squareup.retrofit2:converter-moshi\", version.ref = \"retrofit\" }\nretrofit-scalars = { module = \"com.squareup.retrofit2:converter-scalars\", version.ref = \"retrofit\" }\nmarkwon-core = { module = \"io.noties.markwon:core\", version = \"4.6.2\" }\nokhttp = { module = \"com.squareup.okhttp3:okhttp\", version.ref = \"okhttp\" }\nokhttp-dnsoverhttps = { module = \"com.squareup.okhttp3:okhttp-dnsoverhttps\", version.ref = \"okhttp\" }\nokhttp-logging = { module = \"com.squareup.okhttp3:logging-interceptor\", version.ref = \"okhttp\" }\ntimber = { module = \"com.jakewharton.timber:timber\", version = \"5.0.1\" }\n\n# AndroidX\nactivity = { module = \"androidx.activity:activity\", version = \"1.12.4\" }\nappcompat = { module = \"androidx.appcompat:appcompat\", version = \"1.7.1\" }\ncore-ktx = { module = \"androidx.core:core-ktx\", version = \"1.17.0\" }\ncore-splashscreen = { module = \"androidx.core:core-splashscreen\", version = \"1.2.0\" }\nconstraintlayout = { module = \"androidx.constraintlayout:constraintlayout\", version = \"2.2.1\" }\nfragment-ktx = { module = \"androidx.fragment:fragment-ktx\", version = \"1.8.9\" }\nnavigation-fragment-ktx = { module = \"androidx.navigation:navigation-fragment-ktx\", version.ref = \"navigation\" }\nnavigation-ui-ktx = { module = \"androidx.navigation:navigation-ui-ktx\", version.ref = \"navigation\" }\nprofileinstaller = { module = \"androidx.profileinstaller:profileinstaller\", version = \"1.4.1\" }\nrecyclerview = { module = \"androidx.recyclerview:recyclerview\", version = \"1.4.0\" }\nroom-ktx = { module = \"androidx.room:room-ktx\", version.ref = \"room\" }\nroom-runtime = { module = \"androidx.room:room-runtime\", version.ref = \"room\" }\nroom-compiler = { module = \"androidx.room:room-compiler\", version.ref = \"room\" }\nswiperefreshlayout = { module = \"androidx.swiperefreshlayout:swiperefreshlayout\", version = \"1.2.0\" }\ntransition = { module = \"androidx.transition:transition\", version = \"1.7.0\" }\ncollection-ktx = { module = \"androidx.collection:collection-ktx\", version = \"1.5.0\" }\nmaterial = { module = \"com.google.android.material:material\", version = \"1.13.0\" }\njdk-libs = { module = \"com.android.tools:desugar_jdk_libs_nio\", version = \"2.1.5\" }\ntest-runner = { module = \"androidx.test:runner\", version = \"1.7.0\" }\ntest-rules = { module = \"androidx.test:rules\", version = \"1.7.0\" }\ntest-junit = { module = \"androidx.test.ext:junit\", version = \"1.3.0\" }\ntest-uiautomator = { module = \"androidx.test.uiautomator:uiautomator\", version = \"2.3.0\" }\n\n# topjohnwu\nindeterminate-checkbox = { module = \"com.github.topjohnwu:indeterminate-checkbox\", version = \"1.0.7\" }\nlibsu-core = { module = \"com.github.topjohnwu.libsu:core\", version.ref = \"libsu\" }\nlibsu-service = { module = \"com.github.topjohnwu.libsu:service\", version.ref = \"libsu\" }\nlibsu-nio = { module = \"com.github.topjohnwu.libsu:nio\", version.ref = \"libsu\" }\n\n# Rikka\nrikka-recyclerview = { module = \"dev.rikka.rikkax.recyclerview:recyclerview-ktx\", version = \"1.3.2\" }\nrikka-layoutinflater = { module = \"dev.rikka.rikkax.layoutinflater:layoutinflater\", version.ref = \"rikka\" }\nrikka-insets = { module = \"dev.rikka.rikkax.insets:insets\", version.ref = \"rikka\" }\n\n# Compose\nactivity-compose = { module = \"androidx.activity:activity-compose\", version.ref = \"activity-compose\" }\ncompose-bom = { module = \"androidx.compose:compose-bom\", version.ref = \"compose-bom\" }\ncompose-ui = { module = \"androidx.compose.ui:ui\" }\ncompose-ui-tooling = { module = \"androidx.compose.ui:ui-tooling\" }\ncompose-ui-tooling-preview = { module = \"androidx.compose.ui:ui-tooling-preview\" }\nlifecycle-runtime-compose = { module = \"androidx.lifecycle:lifecycle-runtime-compose\", version.ref = \"lifecycle\" }\nlifecycle-viewmodel-compose = { module = \"androidx.lifecycle:lifecycle-viewmodel-compose\", version.ref = \"lifecycle\" }\nmiuix = { module = \"top.yukonga.miuix.kmp:miuix-android\", version.ref = \"miuix\" }\nmiuix-icons = { module = \"top.yukonga.miuix.kmp:miuix-icons-android\", version.ref = \"miuix\" }\nmiuix-navigation3-ui = { module = \"top.yukonga.miuix.kmp:miuix-navigation3-ui-android\", version.ref = \"miuix\" }\nnavigation3-runtime = { module = \"androidx.navigation3:navigation3-runtime\", version.ref = \"navigation3\" }\nnavigationevent-compose = { module = \"androidx.navigationevent:navigationevent-compose\", version.ref = \"navigationevent\" }\nlifecycle-viewmodel-navigation3 = { module = \"androidx.lifecycle:lifecycle-viewmodel-navigation3\", version.ref = \"lifecycle\" }\n\n# Build plugins\nandroid-gradle-plugin = { module = \"com.android.tools.build:gradle\", version.ref = \"android\" }\n\n[plugins]\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nlsparanoid = { id = \"org.lsposed.lsparanoid\", version = \"0.6.0\" }\nmoshix = { id = \"dev.zacsweers.moshix\", version = \"0.34.4\" }\nlegacy-kapt = { id = \"com.android.legacy-kapt\", version.ref = \"android\" }\nnavigation-safeargs = { id = \"androidx.navigation.safeargs.kotlin\", version.ref = \"navigation\" }"
  },
  {
    "path": "app/gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.3.0-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "app/gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx10248m -XX:MaxPermSize=256m\norg.gradle.jvmargs=-Xmx2560m -Dfile.encoding=UTF-8\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\norg.gradle.parallel=true\n\n# Enable build cache\norg.gradle.caching=true\n\n# Use K2 in kapt\nkapt.use.k2=true\n\n# Android\nandroid.injected.testOnly=false\n\n# Magisk\nmagisk.stubVersion=40\nmagisk.versionCode=30700\n"
  },
  {
    "path": "app/gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n# SPDX-License-Identifier: Apache-2.0\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd -P \"${APP_HOME:-./}\" > /dev/null && printf '%s\\n' \"$PWD\" ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=\"\\\\\\\"\\\\\\\"\"\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        -jar \"$APP_HOME/gradle/wrapper/gradle-wrapper.jar\" \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "app/gradlew.bat",
    "content": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\r\n@rem you may not use this file except in compliance with the License.\r\n@rem You may obtain a copy of the License at\r\n@rem\r\n@rem      https://www.apache.org/licenses/LICENSE-2.0\r\n@rem\r\n@rem Unless required by applicable law or agreed to in writing, software\r\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\r\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n@rem See the License for the specific language governing permissions and\r\n@rem limitations under the License.\r\n@rem\r\n@rem SPDX-License-Identifier: Apache-2.0\r\n@rem\r\n\r\n@if \"%DEBUG%\"==\"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\r\n@rem This is normally unused\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\r\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif %ERRORLEVEL% equ 0 goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto execute\r\n\r\necho. 1>&2\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\r\necho. 1>&2\r\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\r\necho location of your Java installation. 1>&2\r\n\r\ngoto fail\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=\r\n\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" -jar \"%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\" %*\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif %ERRORLEVEL% equ 0 goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nset EXIT_CODE=%ERRORLEVEL%\r\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\r\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\r\nexit /b %EXIT_CODE%\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "app/settings.gradle.kts",
    "content": "@Suppress(\"UnstableApiUsage\")\ndependencyResolutionManagement {\n    repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS\n    repositories {\n        google()\n        mavenCentral()\n        maven(\"https://jitpack.io\")\n    }\n}\n\npluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n    }\n}\n\nrootProject.name = \"Magisk\"\ninclude(\":apk\", \":apk-ng\", \":core\", \":shared\", \":stub\", \":test\")\n"
  },
  {
    "path": "app/shared/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.library\")\n}\n\nsetupCommon()\n\nandroid {\n    namespace = \"com.topjohnwu.shared\"\n    enableKotlin = false\n}\n"
  },
  {
    "path": "app/shared/src/debug/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application\n        android:usesCleartextTraffic=\"true\"\n        tools:ignore=\"UnusedAttribute\" />\n\n</manifest>\n"
  },
  {
    "path": "app/shared/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:installLocation=\"internalOnly\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n    <uses-permission android:name=\"android.permission.HIDE_OVERLAY_WINDOWS\" />\n    <uses-permission android:name=\"android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.RUN_USER_INITIATED_JOBS\" />\n    <uses-permission\n        android:name=\"android.permission.FOREGROUND_SERVICE\"\n        android:maxSdkVersion=\"33\" />\n    <uses-permission\n        android:name=\"android.permission.READ_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"29\" />\n    <uses-permission\n        android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"\n        android:maxSdkVersion=\"29\"\n        tools:ignore=\"ScopedStorage\" />\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n\n    <application\n        android:allowBackup=\"false\"\n        android:enableOnBackInvokedCallback=\"false\"\n        android:label=\"Magisk\"\n        android:requestLegacyExternalStorage=\"true\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@android:style/Theme.Translucent.NoTitleBar\" />\n\n</manifest>\n"
  },
  {
    "path": "app/shared/src/main/java/com/topjohnwu/magisk/ProviderInstaller.java",
    "content": "package com.topjohnwu.magisk;\n\nimport android.content.Context;\nimport android.content.pm.ApplicationInfo;\n\npublic class ProviderInstaller {\n\n    private static final String GMS_PACKAGE_NAME = \"com.google.android.gms\";\n\n    public static void install(Context context) {\n        try {\n            // Check if gms is a system app\n            ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(GMS_PACKAGE_NAME, 0);\n            if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {\n                return;\n            }\n\n            // Try installing new SSL provider from Google Play Service\n            Context gms = context.createPackageContext(GMS_PACKAGE_NAME,\n                    Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);\n            gms.getClassLoader()\n                    .loadClass(\"com.google.android.gms.common.security.ProviderInstallerImpl\")\n                    .getMethod(\"insertProvider\", Context.class)\n                    .invoke(null, gms);\n        } catch (Exception ignored) {\n        }\n    }\n}\n"
  },
  {
    "path": "app/shared/src/main/java/com/topjohnwu/magisk/StubApk.java",
    "content": "package com.topjohnwu.magisk;\n\nimport static android.os.Build.VERSION.SDK_INT;\nimport static android.os.ParcelFileDescriptor.MODE_READ_ONLY;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\nimport android.content.res.AssetManager;\nimport android.content.res.Resources;\nimport android.content.res.loader.ResourcesLoader;\nimport android.content.res.loader.ResourcesProvider;\nimport android.os.Build;\nimport android.os.ParcelFileDescriptor;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.lang.reflect.Method;\nimport java.util.Map;\n\npublic class StubApk {\n    private static File dynDir;\n    private static Method addAssetPath;\n\n    private static File getDynDir(ApplicationInfo info) {\n        if (dynDir == null) {\n            final String dataDir;\n            if (SDK_INT >= Build.VERSION_CODES.N) {\n                // Use device protected path to allow directBootAware\n                dataDir = info.deviceProtectedDataDir;\n            } else {\n                dataDir = info.dataDir;\n            }\n            dynDir = new File(dataDir, \"dyn\");\n            dynDir.mkdirs();\n        }\n        return dynDir;\n    }\n\n    public static File current(Context c) {\n        return new File(getDynDir(c.getApplicationInfo()), \"current.apk\");\n    }\n\n    public static File current(ApplicationInfo info) {\n        return new File(getDynDir(info), \"current.apk\");\n    }\n\n    public static File update(Context c) {\n        return new File(getDynDir(c.getApplicationInfo()), \"update.apk\");\n    }\n\n    public static File update(ApplicationInfo info) {\n        return new File(getDynDir(info), \"update.apk\");\n    }\n\n    @TargetApi(Build.VERSION_CODES.R)\n    private static ResourcesLoader getResourcesLoader(File path) throws IOException {\n        var loader = new ResourcesLoader();\n        ResourcesProvider provider;\n        if (path.isDirectory()) {\n            provider = ResourcesProvider.loadFromDirectory(path.getPath(), null);\n        } else {\n            var fd = ParcelFileDescriptor.open(path, MODE_READ_ONLY);\n            provider = ResourcesProvider.loadFromApk(fd);\n        }\n        loader.addProvider(provider);\n        return loader;\n    }\n\n    public static void addAssetPath(Resources res, String path) {\n        if (SDK_INT >= Build.VERSION_CODES.R) {\n            try {\n                res.addLoaders(getResourcesLoader(new File(path)));\n            } catch (IOException ignored) {}\n        } else {\n            AssetManager asset = res.getAssets();\n            try {\n                if (addAssetPath == null)\n                    addAssetPath = AssetManager.class.getMethod(\"addAssetPath\", String.class);\n                addAssetPath.invoke(asset, path);\n            } catch (Exception ignored) {}\n        }\n    }\n\n    public static void restartProcess(Activity activity) {\n        Intent intent = activity.getPackageManager()\n                .getLaunchIntentForPackage(activity.getPackageName());\n        activity.finishAffinity();\n        activity.startActivity(intent);\n        Runtime.getRuntime().exit(0);\n    }\n\n    public static class Data {\n        // Indices of the object array\n        private static final int STUB_VERSION = 0;\n        private static final int CLASS_COMPONENT_MAP = 1;\n        private static final int ROOT_SERVICE = 2;\n        private static final int ARR_SIZE = 3;\n\n        private final Object[] arr;\n\n        public Data() { arr = new Object[ARR_SIZE]; }\n        public Data(Object o) { arr = (Object[]) o; }\n        public Object getObject() { return arr; }\n\n        public int getVersion() { return (int) arr[STUB_VERSION]; }\n        public void setVersion(int version) { arr[STUB_VERSION] = version; }\n        public Map<String, String> getClassToComponent() {\n            // noinspection unchecked\n            return (Map<String, String>) arr[CLASS_COMPONENT_MAP];\n        }\n        public void setClassToComponent(Map<String, String> map) {\n            arr[CLASS_COMPONENT_MAP] = map;\n        }\n        public Class<?> getRootService() { return (Class<?>) arr[ROOT_SERVICE]; }\n        public void setRootService(Class<?> service) { arr[ROOT_SERVICE] = service; }\n    }\n}\n"
  },
  {
    "path": "app/shared/src/main/java/com/topjohnwu/magisk/utils/APKInstall.java",
    "content": "package com.topjohnwu.magisk.utils;\n\nimport static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;\nimport static android.content.pm.PackageInstaller.EXTRA_STATUS;\nimport static android.content.pm.PackageInstaller.STATUS_FAILURE_INVALID;\nimport static android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION;\nimport static android.content.pm.PackageInstaller.STATUS_SUCCESS;\n\nimport android.app.PendingIntent;\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.IntentFilter;\nimport android.content.pm.PackageInstaller.SessionParams;\nimport android.net.Uri;\nimport android.os.Build;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.UUID;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.TimeUnit;\n\npublic final class APKInstall {\n\n    public static void transfer(InputStream in, OutputStream out) throws IOException {\n        int size = 8192;\n        var buffer = new byte[size];\n        int read;\n        while ((read = in.read(buffer, 0, size)) >= 0) {\n            out.write(buffer, 0, read);\n        }\n    }\n\n    public static void registerReceiver(\n            Context context, BroadcastReceiver receiver, IntentFilter filter) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            // noinspection InlinedApi\n            context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);\n        } else {\n            context.registerReceiver(receiver, filter);\n        }\n    }\n\n    public static Session startSession(Context context) {\n        return startSession(context, null, null, null);\n    }\n\n    public static Session startSession(Context context, String pkg,\n                                       Runnable onFailure, Runnable onSuccess) {\n        var receiver = new InstallReceiver(pkg, onSuccess, onFailure);\n        context = context.getApplicationContext();\n        if (pkg != null) {\n            // If pkg is not null, look for package added event\n            var filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);\n            filter.addDataScheme(\"package\");\n            registerReceiver(context, receiver, filter);\n        }\n        registerReceiver(context, receiver, new IntentFilter(receiver.sessionId));\n        return receiver;\n    }\n\n    public interface Session {\n        // @WorkerThread\n        OutputStream openStream(Context context) throws IOException;\n        // @WorkerThread @Nullable\n        Intent waitIntent();\n    }\n\n    private static class InstallReceiver extends BroadcastReceiver implements Session {\n        private final String packageName;\n        private final Runnable onSuccess;\n        private final Runnable onFailure;\n        private final CountDownLatch latch = new CountDownLatch(1);\n        private Intent userAction = null;\n\n        final String sessionId = UUID.randomUUID().toString();\n\n        private InstallReceiver(String packageName, Runnable onSuccess, Runnable onFailure) {\n            this.packageName = packageName;\n            this.onSuccess = onSuccess;\n            this.onFailure = onFailure;\n        }\n\n        @Override\n        public void onReceive(Context context, Intent intent) {\n            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {\n                Uri data = intent.getData();\n                if (data == null)\n                    return;\n                String pkg = data.getSchemeSpecificPart();\n                if (pkg.equals(packageName)) {\n                    onSuccess(context);\n                }\n            } else if (sessionId.equals(intent.getAction())) {\n                int status = intent.getIntExtra(EXTRA_STATUS, STATUS_FAILURE_INVALID);\n                switch (status) {\n                    case STATUS_PENDING_USER_ACTION ->\n                            userAction = intent.getParcelableExtra(Intent.EXTRA_INTENT);\n                    case STATUS_SUCCESS -> {\n                        if (packageName == null) {\n                            onSuccess(context);\n                        }\n                    }\n                    default -> {\n                        int id = intent.getIntExtra(EXTRA_SESSION_ID, 0);\n                        var installer = context.getPackageManager().getPackageInstaller();\n                        try {\n                            installer.abandonSession(id);\n                        } catch (SecurityException ignored) {\n                        }\n                        if (onFailure != null) {\n                            onFailure.run();\n                        }\n                        try {\n                            context.getApplicationContext().unregisterReceiver(this);\n                        } catch (IllegalArgumentException ignored) {\n                        }\n                    }\n                }\n                latch.countDown();\n            }\n        }\n\n        private void onSuccess(Context context) {\n            if (onSuccess != null)\n                onSuccess.run();\n            try {\n                context.getApplicationContext().unregisterReceiver(this);\n            } catch (IllegalArgumentException ignored) {\n            }\n        }\n\n        @Override\n        public Intent waitIntent() {\n            try {\n                // noinspection ResultOfMethodCallIgnored\n                latch.await(5, TimeUnit.SECONDS);\n            } catch (Exception ignored) {}\n            return userAction;\n        }\n\n        @Override\n        public OutputStream openStream(Context context) throws IOException {\n            // noinspection InlinedApi\n            var flag = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE;\n            var intent = new Intent(sessionId).setPackage(context.getPackageName());\n            var pending = PendingIntent.getBroadcast(context, 0, intent, flag);\n\n            var installer = context.getPackageManager().getPackageInstaller();\n            var params = new SessionParams(SessionParams.MODE_FULL_INSTALL);\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n                params.setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED);\n            }\n            var session = installer.openSession(installer.createSession(params));\n            var out = session.openWrite(sessionId, 0, -1);\n            return new FilterOutputStream(out) {\n                @Override\n                public void write(byte[] b, int off, int len) throws IOException {\n                    out.write(b, off, len);\n                }\n                @Override\n                public void close() throws IOException {\n                    super.close();\n                    session.commit(pending.getIntentSender());\n                    session.close();\n                }\n            };\n        }\n    }\n}\n"
  },
  {
    "path": "app/shared/src/main/java/com/topjohnwu/magisk/utils/CompoundEnumeration.java",
    "content": "package com.topjohnwu.magisk.utils;\n\nimport java.util.Enumeration;\nimport java.util.NoSuchElementException;\n\npublic class CompoundEnumeration<E> implements Enumeration<E> {\n    private Enumeration<E>[] enums;\n    private int index = 0;\n\n    @SafeVarargs\n    public CompoundEnumeration(Enumeration<E> ...enums) {\n        this.enums = enums;\n    }\n\n    private boolean next() {\n        while (index < enums.length) {\n            if (enums[index] != null && enums[index].hasMoreElements()) {\n                return true;\n            }\n            index++;\n        }\n        return false;\n    }\n\n    public boolean hasMoreElements() {\n        return next();\n    }\n\n    public E nextElement() {\n        if (!next()) {\n            throw new NoSuchElementException();\n        }\n        return enums[index].nextElement();\n    }\n}\n"
  },
  {
    "path": "app/shared/src/main/java/com/topjohnwu/magisk/utils/DynamicClassLoader.java",
    "content": "package com.topjohnwu.magisk.utils;\n\nimport android.os.Process;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.net.URL;\nimport java.util.Enumeration;\n\nimport dalvik.system.BaseDexClassLoader;\n\npublic class DynamicClassLoader extends BaseDexClassLoader {\n\n    public DynamicClassLoader(File apk) {\n        this(apk, DynamicClassLoader.class.getClassLoader());\n    }\n\n    public DynamicClassLoader(File apk, ClassLoader parent) {\n        // Set optimizedDirectory to null for RootService to bypass DexFile's security checks\n        super(apk.getPath(), Process.myUid() == 0 ? null : apk.getParentFile(), null, parent);\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        // First check if already loaded\n        Class<?> cls = findLoadedClass(name);\n        if (cls != null)\n            return cls;\n\n        try {\n            // Then check boot classpath\n            return getSystemClassLoader().loadClass(name);\n        } catch (ClassNotFoundException ignored) {\n            try {\n                // Next try current dex\n                return findClass(name);\n            } catch (ClassNotFoundException fromSuper) {\n                try {\n                    // Finally try parent\n                    return getParent().loadClass(name);\n                } catch (ClassNotFoundException e) {\n                    throw fromSuper;\n                }\n            }\n        }\n    }\n\n    @Override\n    public URL getResource(String name) {\n        URL resource = getSystemClassLoader().getResource(name);\n        if (resource != null)\n            return resource;\n        resource = findResource(name);\n        if (resource != null)\n            return resource;\n        resource = getParent().getResource(name);\n        return resource;\n    }\n\n    @Override\n    public Enumeration<URL> getResources(String name) throws IOException {\n        return new CompoundEnumeration<>(getSystemClassLoader().getResources(name),\n                findResources(name), getParent().getResources(name));\n    }\n}\n"
  },
  {
    "path": "app/stub/.gitignore",
    "content": "/build\n/src/release/AndroidManifest.xml\n/src/debug/AndroidManifest.xml\n"
  },
  {
    "path": "app/stub/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n    alias(libs.plugins.lsparanoid)\n}\n\nlsparanoid {\n    seed = if (RAND_SEED != 0) RAND_SEED else null\n    includeDependencies = true\n    classFilter = { true }\n}\n\nandroid {\n    namespace = \"com.topjohnwu.magisk\"\n\n    val canary = !Config.version.contains(\".\")\n    val base = \"https://github.com/topjohnwu/Magisk/releases/download/\"\n    val url = base + \"v${Config.version}/Magisk-v${Config.version}.apk\"\n    val canaryUrl = base + \"canary-${Config.versionCode}/\"\n\n    defaultConfig {\n        applicationId = \"com.topjohnwu.magisk\"\n        versionCode = 1\n        versionName = \"1.0\"\n        buildConfigField(\"String\", \"APK_URL\", \"\\\"$url\\\"\")\n        buildConfigField(\"int\", \"STUB_VERSION\", Config.stubVersion)\n    }\n\n    buildTypes {\n        release {\n            if (canary) buildConfigField(\"String\", \"APK_URL\", \"\\\"${canaryUrl}app-release.apk\\\"\")\n            proguardFiles(\"proguard-rules.pro\")\n            isMinifyEnabled = true\n            isShrinkResources = false\n        }\n        debug {\n            if (canary) buildConfigField(\"String\", \"APK_URL\", \"\\\"${canaryUrl}app-debug.apk\\\"\")\n        }\n    }\n\n    buildFeatures {\n        buildConfig = true\n    }\n}\n\nsetupStubApk()\n\ndependencies {\n    implementation(project(\":shared\"))\n}\n"
  },
  {
    "path": "app/stub/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n-obfuscationdictionary ../dict.txt\n-classobfuscationdictionary ../dict.txt\n-packageobfuscationdictionary ../dict.txt\n\n# Excessive obfuscation\n-repackageclasses\n-allowaccessmodification\n-keepclassmembers class com.topjohnwu.magisk.dummy.* { <init>(); }\n-keepclassmembers class com.topjohnwu.magisk.DownloadActivity { <init>(); }\n-keepclassmembers class com.topjohnwu.magisk.StubRootService { <init>(); }\n"
  },
  {
    "path": "app/stub/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission\n        android:name=\"com.android.launcher.permission.INSTALL_SHORTCUT\"\n        android:maxSdkVersion=\"25\" />\n\n    <application tools:ignore=\"MissingApplicationIcon\">\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java",
    "content": "package com.topjohnwu.magisk;\n\nimport android.content.pm.PackageInfo;\n\nimport com.topjohnwu.magisk.dummy.DummyProvider;\nimport com.topjohnwu.magisk.dummy.DummyReceiver;\nimport com.topjohnwu.magisk.dummy.DummyService;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n// Wrap the actual classloader as we only want to resolve classname\n// mapping when loading from platform (via LoadedApk.mClassLoader)\nclass MappingClassLoader extends ClassLoader {\n\n    private final Map<String, String> mapping;\n\n    MappingClassLoader(ClassLoader parent, Map<String, String> m) {\n        super(parent);\n        mapping = m;\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        String clz = mapping.get(name);\n        name = clz != null ? clz : name;\n        return super.loadClass(name, resolve);\n    }\n}\n\nclass StubClassLoader extends ClassLoader {\n\n    private final Map<String, Class<?>> mapping = new HashMap<>();\n\n    StubClassLoader(PackageInfo info) {\n        super(StubClassLoader.class.getClassLoader());\n        for (var c : info.activities) {\n            mapping.put(c.name, DownloadActivity.class);\n        }\n        for (var c : info.services) {\n            mapping.put(c.name, DummyService.class);\n        }\n        for (var c : info.providers) {\n            mapping.put(c.name, DummyProvider.class);\n        }\n        for (var c : info.receivers) {\n            mapping.put(c.name, DummyReceiver.class);\n        }\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        Class<?> clz = mapping.get(name);\n        return clz == null ? super.loadClass(name, resolve) : clz;\n    }\n}\n\nclass DelegateClassLoader extends ClassLoader {\n\n    DelegateClassLoader() {\n        super();\n    }\n\n    @Override\n    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {\n        return DynLoad.activeClassLoader.loadClass(name);\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java",
    "content": "package com.topjohnwu.magisk;\n\nimport android.annotation.SuppressLint;\nimport android.app.Activity;\nimport android.app.AppComponentFactory;\nimport android.app.Application;\nimport android.app.Service;\nimport android.content.BroadcastReceiver;\nimport android.content.ContentProvider;\nimport android.content.Intent;\nimport android.content.pm.ApplicationInfo;\n\nimport com.topjohnwu.magisk.dummy.DummyProvider;\nimport com.topjohnwu.magisk.dummy.DummyReceiver;\nimport com.topjohnwu.magisk.dummy.DummyService;\n\n@SuppressLint(\"NewApi\")\npublic class DelegateComponentFactory extends AppComponentFactory {\n\n    AppComponentFactory receiver;\n\n    public DelegateComponentFactory() {\n        DynLoad.componentFactory = this;\n    }\n\n    @Override\n    public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo info) {\n        return new DelegateClassLoader();\n    }\n\n    @Override\n    public Application instantiateApplication(ClassLoader cl, String className) {\n        return new StubApplication();\n    }\n\n    @Override\n    public Activity instantiateActivity(ClassLoader cl, String className, Intent intent)\n            throws ClassNotFoundException, IllegalAccessException, InstantiationException {\n        if (receiver != null)\n            return receiver.instantiateActivity(DynLoad.activeClassLoader, className, intent);\n        return create(className, DownloadActivity.class);\n    }\n\n    @Override\n    public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent)\n            throws ClassNotFoundException, IllegalAccessException, InstantiationException {\n        if (receiver != null)\n            return receiver.instantiateReceiver(DynLoad.activeClassLoader, className, intent);\n        return create(className, DummyReceiver.class);\n    }\n\n    @Override\n    public Service instantiateService(ClassLoader cl, String className, Intent intent)\n            throws ClassNotFoundException, IllegalAccessException, InstantiationException {\n        if (receiver != null)\n            return receiver.instantiateService(DynLoad.activeClassLoader, className, intent);\n        return create(className, DummyService.class);\n    }\n\n    @Override\n    public ContentProvider instantiateProvider(ClassLoader cl, String className)\n            throws ClassNotFoundException, IllegalAccessException, InstantiationException {\n        if (receiver != null)\n            return receiver.instantiateProvider(DynLoad.activeClassLoader, className);\n        return create(className, DummyProvider.class);\n    }\n\n    private <T> T create(String name, Class<T> fallback)\n            throws IllegalAccessException, InstantiationException {\n        try {\n            // noinspection unchecked\n            return (T) DynLoad.activeClassLoader.loadClass(name).newInstance();\n        } catch (ClassNotFoundException e) {\n            return fallback.newInstance();\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java",
    "content": "package com.topjohnwu.magisk;\n\nimport static android.R.string.no;\nimport static android.R.string.ok;\nimport static android.R.string.yes;\nimport static com.topjohnwu.magisk.R.string.dling;\nimport static com.topjohnwu.magisk.R.string.no_internet_msg;\nimport static com.topjohnwu.magisk.R.string.upgrade_msg;\n\nimport android.app.Activity;\nimport android.app.AlertDialog;\nimport android.app.ProgressDialog;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.content.res.loader.ResourcesLoader;\nimport android.content.res.loader.ResourcesProvider;\nimport android.os.AsyncTask;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.ParcelFileDescriptor;\nimport android.system.Os;\nimport android.system.OsConstants;\nimport android.util.Log;\nimport android.view.ContextThemeWrapper;\n\nimport com.topjohnwu.magisk.net.Networking;\nimport com.topjohnwu.magisk.net.Request;\nimport com.topjohnwu.magisk.utils.APKInstall;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.InflaterInputStream;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipOutputStream;\n\nimport javax.crypto.Cipher;\nimport javax.crypto.CipherInputStream;\nimport javax.crypto.SecretKey;\nimport javax.crypto.spec.IvParameterSpec;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic class DownloadActivity extends Activity {\n\n    private static final String APP_NAME = \"Magisk\";\n\n    private Context themed;\n    private boolean dynLoad;\n\n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault);\n\n        // Only download and dynamic load full APK if hidden\n        dynLoad = !getPackageName().equals(BuildConfig.APPLICATION_ID);\n\n        // Inject resources\n        try {\n            loadResources();\n        } catch (Exception e) {\n            error(e);\n        }\n\n        ProviderInstaller.install(this);\n\n        if (Networking.checkNetworkStatus(this)) {\n            showDialog();\n        } else {\n            new AlertDialog.Builder(themed)\n                    .setCancelable(false)\n                    .setTitle(APP_NAME)\n                    .setMessage(getString(no_internet_msg))\n                    .setNegativeButton(ok, (d, w) -> finish())\n                    .show();\n        }\n    }\n\n    @Override\n    public void finish() {\n        super.finish();\n        Runtime.getRuntime().exit(0);\n    }\n\n    private void error(Throwable e) {\n        Log.e(getClass().getSimpleName(), Log.getStackTraceString(e));\n        finish();\n    }\n\n    private Request request(String url) {\n        return Networking.get(url).setErrorHandler((conn, e) -> error(e));\n    }\n\n    private void showDialog() {\n        new AlertDialog.Builder(themed)\n                .setCancelable(false)\n                .setTitle(APP_NAME)\n                .setMessage(getString(upgrade_msg))\n                .setPositiveButton(yes, (d, w) -> dlAPK())\n                .setNegativeButton(no, (d, w) -> finish())\n                .show();\n    }\n\n    private void dlAPK() {\n        ProgressDialog.show(themed, getString(dling), getString(dling) + \" \" + APP_NAME, true);\n        // Download and upgrade the app\n        var request = request(BuildConfig.APK_URL).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR);\n        if (dynLoad) {\n            request.getAsFile(StubApk.current(this), file -> StubApk.restartProcess(this));\n        } else {\n            request.getAsInputStream(input -> {\n                var session = APKInstall.startSession(this);\n                try (input; var out = session.openStream(this)) {\n                    if (out != null)\n                        APKInstall.transfer(input, out);\n                } catch (IOException e) {\n                    error(e);\n                }\n                Intent intent = session.waitIntent();\n                if (intent != null)\n                    startActivity(intent);\n            });\n        }\n    }\n\n    private void decryptResources(OutputStream out) throws Exception {\n        Cipher cipher = Cipher.getInstance(\"AES/CBC/PKCS5Padding\");\n        SecretKey key = new SecretKeySpec(Bytes.key(), \"AES\");\n        IvParameterSpec iv = new IvParameterSpec(Bytes.iv());\n        cipher.init(Cipher.DECRYPT_MODE, key, iv);\n        var is = new InflaterInputStream(new CipherInputStream(\n                new ByteArrayInputStream(Bytes.res()), cipher));\n        try (is; out) {\n            APKInstall.transfer(is, out);\n        }\n    }\n\n    private void loadResources() throws Exception {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            var fd = Os.memfd_create(\"res\", 0);\n            try {\n                decryptResources(new FileOutputStream(fd));\n                Os.lseek(fd, 0, OsConstants.SEEK_SET);\n                var loader = new ResourcesLoader();\n                try (var pfd = ParcelFileDescriptor.dup(fd)) {\n                    loader.addProvider(ResourcesProvider.loadFromTable(pfd, null));\n                    getResources().addLoaders(loader);\n                }\n            } finally {\n                Os.close(fd);\n            }\n        } else {\n            File res = new File(getCodeCacheDir(), \"res.apk\");\n            try (var out = new ZipOutputStream(new FileOutputStream(res))) {\n                // AndroidManifest.xml is required on Android 6-, and directory support is broken on Android 9-10\n                out.putNextEntry(new ZipEntry(\"AndroidManifest.xml\"));\n                try (var stubApk = new ZipFile(getPackageCodePath())) {\n                    APKInstall.transfer(stubApk.getInputStream(stubApk.getEntry(\"AndroidManifest.xml\")), out);\n                }\n                out.putNextEntry(new ZipEntry(\"resources.arsc\"));\n                decryptResources(out);\n            }\n            StubApk.addAssetPath(getResources(), res.getPath());\n        }\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java",
    "content": "package com.topjohnwu.magisk;\n\nimport static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID;\n\nimport android.app.AppComponentFactory;\nimport android.app.Application;\nimport android.app.job.JobService;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.pm.ActivityInfo;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.content.pm.ServiceInfo;\nimport android.os.Build;\nimport android.util.Log;\n\nimport com.topjohnwu.magisk.utils.APKInstall;\nimport com.topjohnwu.magisk.utils.DynamicClassLoader;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Method;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@SuppressWarnings(\"ResultOfMethodCallIgnored\")\npublic class DynLoad {\n\n    static Object componentFactory;\n    static ClassLoader activeClassLoader = DynLoad.class.getClassLoader();\n\n    static StubApk.Data createApkData() {\n        var data = new StubApk.Data();\n        data.setVersion(BuildConfig.STUB_VERSION);\n        data.setClassToComponent(new HashMap<>());\n        data.setRootService(StubRootService.class);\n        return data;\n    }\n\n    static void attachContext(Object o, Context context) {\n        if (!(o instanceof ContextWrapper))\n            return;\n        try {\n            Method m = ContextWrapper.class.getDeclaredMethod(\"attachBaseContext\", Context.class);\n            m.setAccessible(true);\n            m.invoke(o, context);\n        } catch (Exception ignored) { /* Impossible */ }\n    }\n\n    // Dynamically load APK from internal, external storage, or previous app\n    static DynamicClassLoader loadApk(Context context) {\n        File apk = StubApk.current(context);\n        File update = StubApk.update(context);\n\n        if (update.exists()) {\n            // Rename from update\n            update.renameTo(apk);\n        }\n\n        // Copy from external for easier development\n        if (BuildConfig.DEBUG) {\n            try {\n                File external = new File(context.getExternalFilesDir(null), \"magisk.apk\");\n                if (external.exists()) {\n                    apk.delete();\n                    try {\n                        var in = new FileInputStream(external);\n                        var out = new FileOutputStream(apk);\n                        apk.setReadOnly();\n                        try (in; out) {\n                            APKInstall.transfer(in, out);\n                        }\n                    } catch (IOException e) {\n                        Log.e(DynLoad.class.getSimpleName(), \"\", e);\n                        apk.delete();\n                    } finally {\n                        external.delete();\n                    }\n                }\n            } catch (SecurityException e) {\n                // Do not crash in root service\n            }\n        }\n\n        if (apk.exists()) {\n            apk.setReadOnly();\n            return new DynamicClassLoader(apk);\n        }\n\n        // If no APK is loaded, attempt to copy from previous app\n        if (!context.getPackageName().equals(APPLICATION_ID)) {\n            try {\n                var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0);\n                apk.delete();\n                var src = new FileInputStream(info.sourceDir);\n                var out = new FileOutputStream(apk);\n                apk.setReadOnly();\n                try (src; out) {\n                    APKInstall.transfer(src, out);\n                }\n                return new DynamicClassLoader(apk);\n            } catch (PackageManager.NameNotFoundException ignored) {\n            } catch (IOException e) {\n                Log.e(DynLoad.class.getSimpleName(), \"\", e);\n                apk.delete();\n            }\n        }\n\n        return null;\n    }\n\n    // Dynamically load APK and initialize the application\n    static void loadAndInitializeApp(Application context) {\n        // On API >= 29, AppComponentFactory will replace the ClassLoader for us\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)\n            replaceClassLoader(context);\n\n        // noinspection InlinedApi\n        int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES\n                | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS\n                | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DISABLED_COMPONENTS\n                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;\n        var pm = context.getPackageManager();\n\n        final PackageInfo stubInfo;\n        try {\n            // noinspection WrongConstant\n            stubInfo = pm.getPackageInfo(context.getPackageName(), flags);\n        } catch (PackageManager.NameNotFoundException e) {\n            // Impossible\n            throw new RuntimeException(e);\n        }\n\n        File apk = StubApk.current(context);\n\n        final var cl = loadApk(context);\n        if (cl != null) try {\n            // noinspection WrongConstant\n            var apkInfo = pm.getPackageArchiveInfo(apk.getPath(), flags);\n            var mapping = generateMapping(stubInfo, apkInfo);\n\n            var data = createApkData();\n            var map = data.getClassToComponent();\n            // Create the inverse mapping (class to component name)\n            for (var e : mapping.entrySet()) {\n                map.put(e.getValue(), e.getKey());\n            }\n\n            var appInfo = apkInfo.applicationInfo;\n            // Create the receiver Application with proper constructor\n            var app = cl.loadClass(appInfo.className)\n                    .getConstructor(Object.class)\n                    .newInstance(data.getObject());\n\n            // Create the receiver component factory\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && componentFactory != null) {\n                var delegate = (DelegateComponentFactory) componentFactory;\n                if (appInfo.appComponentFactory == null) {\n                    delegate.receiver = new AppComponentFactory();\n                } else {\n                    Object factory = cl.loadClass(appInfo.appComponentFactory).newInstance();\n                    delegate.receiver = (AppComponentFactory) factory;\n                }\n            }\n\n            activeClassLoader = new MappingClassLoader(cl, mapping);\n\n            // Call Application.attachBaseContext\n            attachContext(app, context);\n        } catch (Exception e) {\n            Log.e(DynLoad.class.getSimpleName(), \"\", e);\n            apk.delete();\n        } else {\n            // Dynamic loading failed, use normal stub classloader\n            activeClassLoader = new StubClassLoader(stubInfo);\n        }\n    }\n\n    // Replace LoadedApk mClassLoader\n    private static void replaceClassLoader(Context context) {\n        // Get ContextImpl\n        while (context instanceof ContextWrapper) {\n            context = ((ContextWrapper) context).getBaseContext();\n        }\n\n        try {\n            Field mInfo = context.getClass().getDeclaredField(\"mPackageInfo\");\n            mInfo.setAccessible(true);\n            Object loadedApk = mInfo.get(context);\n            assert loadedApk != null;\n            Field mcl = loadedApk.getClass().getDeclaredField(\"mClassLoader\");\n            mcl.setAccessible(true);\n            mcl.set(loadedApk, new DelegateClassLoader());\n        } catch (Exception e) {\n            // Actually impossible as this method is only called on API < 29,\n            // and API 23 - 28 do not restrict access to these fields.\n            Log.e(DynLoad.class.getSimpleName(), \"\", e);\n        }\n    }\n\n    private static Map<String, String> generateMapping(PackageInfo stub, PackageInfo app) {\n        var mapping = new HashMap<String, String>();\n        {\n            var src = stub.activities;\n            var dest = app.activities;\n\n            final ActivityInfo sa;\n            final ActivityInfo da;\n            final ActivityInfo sb;\n            final ActivityInfo db;\n            if (src[0].exported) {\n                sa = src[0];\n                sb = src[1];\n            } else {\n                sa = src[1];\n                sb = src[0];\n            }\n            if (dest[0].exported) {\n                da = dest[0];\n                db = dest[1];\n            } else {\n                da = dest[1];\n                db = dest[0];\n            }\n            mapping.put(sa.name, da.name);\n            mapping.put(sb.name, db.name);\n        }\n\n        {\n            var src = stub.services;\n            var dest = app.services;\n\n            final ServiceInfo sa;\n            final ServiceInfo da;\n            final ServiceInfo sb;\n            final ServiceInfo db;\n            if (JobService.PERMISSION_BIND.equals(src[0].permission)) {\n                sa = src[0];\n                sb = src[1];\n            } else {\n                sa = src[1];\n                sb = src[0];\n            }\n            if (JobService.PERMISSION_BIND.equals(dest[0].permission)) {\n                da = dest[0];\n                db = dest[1];\n            } else {\n                da = dest[1];\n                db = dest[0];\n            }\n            mapping.put(sa.name, da.name);\n            mapping.put(sb.name, db.name);\n        }\n\n        {\n            var src = stub.receivers;\n            var dest = app.receivers;\n            mapping.put(src[0].name, dest[0].name);\n        }\n\n        {\n            var src = stub.providers;\n            var dest = app.providers;\n            mapping.put(src[0].name, dest[0].name);\n        }\n        return mapping;\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/StubApplication.java",
    "content": "package com.topjohnwu.magisk;\n\nimport android.app.Application;\nimport android.content.Context;\n\npublic class StubApplication extends Application {\n    @Override\n    protected void attachBaseContext(Context base) {\n        super.attachBaseContext(base);\n        DynLoad.loadAndInitializeApp(this);\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/StubRootService.java",
    "content": "package com.topjohnwu.magisk;\n\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.util.Log;\n\nimport java.io.File;\nimport java.lang.reflect.Constructor;\n\npublic class StubRootService extends ContextWrapper {\n\n    public StubRootService() {\n        super(null);\n    }\n\n    @Override\n    protected void attachBaseContext(Context base) {\n        ClassLoader loader = DynLoad.loadApk(base);\n        if (loader == null)\n            return;\n\n        try {\n            // Create application to get the real root service class\n            var data = DynLoad.createApkData();\n            File apk = StubApk.current(base);\n            PackageManager pm = base.getPackageManager();\n            PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0);\n            loader.loadClass(pkgInfo.applicationInfo.className)\n                    .getConstructor(Object.class)\n                    .newInstance(data.getObject());\n\n            // Create the actual RootService and call its attachBaseContext\n            Constructor<?> ctor = data.getRootService().getConstructor(Object.class);\n            ctor.setAccessible(true);\n            Object service = ctor.newInstance(this);\n            DynLoad.attachContext(service, base);\n        } catch (Exception e) {\n            Log.e(StubRootService.class.getSimpleName(), \"\", e);\n        }\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/dummy/DummyProvider.java",
    "content": "package com.topjohnwu.magisk.dummy;\n\nimport android.content.ContentProvider;\nimport android.content.ContentValues;\nimport android.database.Cursor;\nimport android.net.Uri;\n\npublic class DummyProvider extends ContentProvider {\n    @Override\n    public boolean onCreate() {\n        return false;\n    }\n\n    @Override\n    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {\n        return null;\n    }\n\n    @Override\n    public String getType(Uri uri) {\n        return null;\n    }\n\n    @Override\n    public Uri insert(Uri uri, ContentValues values) {\n        return null;\n    }\n\n    @Override\n    public int delete(Uri uri, String selection, String[] selectionArgs) {\n        return 0;\n    }\n\n    @Override\n    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n        return 0;\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/dummy/DummyReceiver.java",
    "content": "package com.topjohnwu.magisk.dummy;\n\nimport android.content.BroadcastReceiver;\nimport android.content.Context;\nimport android.content.Intent;\n\npublic class DummyReceiver extends BroadcastReceiver {\n    @Override\n    public void onReceive(Context context, Intent intent) {}\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/dummy/DummyService.java",
    "content": "package com.topjohnwu.magisk.dummy;\n\nimport android.app.Service;\nimport android.content.Intent;\nimport android.os.IBinder;\n\npublic class DummyService extends Service {\n    @Override\n    public IBinder onBind(Intent intent) {\n        return null;\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/net/BadRequest.java",
    "content": "package com.topjohnwu.magisk.net;\n\nimport org.json.JSONArray;\nimport org.json.JSONObject;\n\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nclass BadRequest extends Request {\n\n    private final IOException ex;\n\n    BadRequest(IOException e) { super(null); ex = e; }\n\n    @Override\n    public Request addHeaders(String key, String value) { return this; }\n\n    @Override\n    public Result<InputStream> execForInputStream() { fail(); return new Result<>(); }\n\n    @Override\n    public void getAsFile(File out, ResponseListener<File> rs) { fail(); }\n\n    @Override\n    public void execForFile(File out) { fail(); }\n\n    @Override\n    public void getAsString(ResponseListener<String> rs) { fail(); }\n\n    @Override\n    public Result<String> execForString() { fail(); return new Result<>(); }\n\n    @Override\n    public void getAsJSONObject(ResponseListener<JSONObject> rs) { fail(); }\n\n    @Override\n    public Result<JSONObject> execForJSONObject() { fail(); return new Result<>(); }\n\n    @Override\n    public void getAsJSONArray(ResponseListener<JSONArray> rs) { fail(); }\n\n    @Override\n    public Result<JSONArray> execForJSONArray() { fail(); return new Result<>(); }\n\n    private void fail() {\n        if (err != null)\n            err.onError(null, ex);\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/net/ErrorHandler.java",
    "content": "package com.topjohnwu.magisk.net;\n\nimport java.net.HttpURLConnection;\n\npublic interface ErrorHandler {\n    void onError(HttpURLConnection conn, Exception e);\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/net/Networking.java",
    "content": "package com.topjohnwu.magisk.net;\n\nimport android.content.Context;\nimport android.net.ConnectivityManager;\nimport android.net.NetworkInfo;\nimport android.os.Handler;\nimport android.os.Looper;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\n\npublic class Networking {\n\n    private static final int READ_TIMEOUT = 15000;\n    private static final int CONNECT_TIMEOUT = 15000;\n    static Handler mainHandler = new Handler(Looper.getMainLooper());\n\n    private static Request request(String url, String method) {\n        try {\n            HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();\n            conn.setRequestMethod(method);\n            conn.setReadTimeout(READ_TIMEOUT);\n            conn.setConnectTimeout(CONNECT_TIMEOUT);\n            return new Request(conn);\n        } catch (IOException e) {\n            return new BadRequest(e);\n        }\n    }\n\n    public static Request get(String url) {\n        return request(url, \"GET\");\n    }\n\n    public static boolean checkNetworkStatus(Context context) {\n        ConnectivityManager manager = (ConnectivityManager)\n                context.getSystemService(Context.CONNECTIVITY_SERVICE);\n        NetworkInfo networkInfo = manager.getActiveNetworkInfo();\n        return networkInfo != null && networkInfo.isConnected();\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/net/Request.java",
    "content": "package com.topjohnwu.magisk.net;\n\nimport android.os.AsyncTask;\n\nimport com.topjohnwu.magisk.utils.APKInstall;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.File;\nimport java.io.FileOutputStream;\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.util.Scanner;\nimport java.util.concurrent.Executor;\n\npublic class Request {\n    private final HttpURLConnection conn;\n    private Executor executor = null;\n    private int code = -1;\n\n    ErrorHandler err = null;\n\n    private interface Requestor<T> {\n        T request() throws Exception;\n    }\n\n    public class Result<T> {\n        T result;\n\n        public T getResult() {\n            return result;\n        }\n\n        public int getCode() {\n            return code;\n        }\n\n        public boolean isSuccess() {\n            return code >= 200 && code <= 299;\n        }\n\n        public HttpURLConnection getConnection() {\n            return conn;\n        }\n    }\n\n    Request(HttpURLConnection c) {\n        conn = c;\n    }\n\n    public Request addHeaders(String key, String value) {\n        conn.setRequestProperty(key, value);\n        return this;\n    }\n\n    public Request setErrorHandler(ErrorHandler handler) {\n        err = handler;\n        return this;\n    }\n\n    public Request setExecutor(Executor e) {\n        executor = e;\n        return this;\n    }\n\n    public Result<Void> connect() {\n        try {\n            connect0();\n        } catch (IOException e) {\n            if (err != null)\n                err.onError(conn, e);\n        }\n        return new Result<>();\n    }\n\n    public Result<InputStream> execForInputStream() {\n        return exec(this::getInputStream);\n    }\n\n    public void getAsInputStream(ResponseListener<InputStream> rs) {\n        submit(this::getInputStream, rs);\n    }\n\n    public void getAsFile(File out, ResponseListener<File> rs) {\n        submit(() -> dlFile(out), rs);\n    }\n\n    public void execForFile(File out) {\n        exec(() -> dlFile(out));\n    }\n\n    public void getAsBytes(ResponseListener<byte[]> rs) {\n        submit(this::dlBytes, rs);\n    }\n\n    public Result<byte[]> execForBytes() {\n        return exec(this::dlBytes);\n    }\n\n    public void getAsString(ResponseListener<String> rs) {\n        submit(this::dlString, rs);\n    }\n\n    public Result<String> execForString() {\n        return exec(this::dlString);\n    }\n\n    public void getAsJSONObject(ResponseListener<JSONObject> rs) {\n        submit(this::dlJSONObject, rs);\n    }\n\n    public Result<JSONObject> execForJSONObject() {\n        return exec(this::dlJSONObject);\n    }\n\n    public void getAsJSONArray(ResponseListener<JSONArray> rs) {\n        submit(this::dlJSONArray, rs);\n    }\n\n    public Result<JSONArray> execForJSONArray() {\n        return exec(this::dlJSONArray);\n    }\n\n    private void connect0() throws IOException {\n        conn.connect();\n        code = conn.getResponseCode();\n    }\n\n    private <T> Result<T> exec(Requestor<T> req) {\n        Result<T> res = new Result<>();\n        try {\n            res.result = req.request();\n        } catch (Exception e) {\n            if (err != null)\n                err.onError(conn, e);\n        }\n        return res;\n    }\n\n    private <T> void submit(Requestor<T> req, ResponseListener<T> rs) {\n        AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {\n            try {\n                T t = req.request();\n                Runnable cb = () -> rs.onResponse(t);\n                if (executor == null)\n                    Networking.mainHandler.post(cb);\n                else\n                    executor.execute(cb);\n            } catch (Exception e) {\n                if (err != null)\n                    err.onError(conn, e);\n            }\n        });\n    }\n\n    private BufferedInputStream getInputStream() throws IOException {\n        connect0();\n        InputStream in = new FilterInputStream(conn.getInputStream()) {\n            @Override\n            public void close() throws IOException {\n                super.close();\n                conn.disconnect();\n            }\n        };\n        return new BufferedInputStream(in);\n    }\n\n    private String dlString() throws IOException {\n        try (Scanner s = new Scanner(getInputStream(), \"UTF-8\")) {\n            s.useDelimiter(\"\\\\A\");\n            return s.next();\n        }\n    }\n\n    private JSONObject dlJSONObject() throws IOException, JSONException {\n        return new JSONObject(dlString());\n    }\n\n    private JSONArray dlJSONArray() throws IOException, JSONException {\n        return new JSONArray(dlString());\n    }\n\n    private File dlFile(File f) throws IOException {\n        try (InputStream in = getInputStream();\n             OutputStream out = new BufferedOutputStream(new FileOutputStream(f))) {\n            APKInstall.transfer(in, out);\n        }\n        return f;\n    }\n\n    private byte[] dlBytes() throws IOException {\n        int len = conn.getContentLength();\n        len = len > 0 ? len : 32;\n        ByteArrayOutputStream out = new ByteArrayOutputStream(len);\n        try (InputStream in = getInputStream()) {\n            APKInstall.transfer(in, out);\n        }\n        return out.toByteArray();\n    }\n}\n"
  },
  {
    "path": "app/stub/src/main/java/com/topjohnwu/magisk/net/ResponseListener.java",
    "content": "package com.topjohnwu.magisk.net;\n\npublic interface ResponseListener<T> {\n    void onResponse(T response);\n}\n"
  },
  {
    "path": "app/stub/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Upgrade to full Magisk to finish the setup. Download and install?</string>\n    <string name=\"no_internet_msg\">Please connect to the Internet! Upgrading to full Magisk is required.</string>\n    <string name=\"dling\">Downloading</string>\n    <string name=\"relaunch_app\">Please manually re-launch the app</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">عليك بتحديث ماجيسك لإكمال تهيئة التطبيق. هل تريد التنزيل والتثبيت؟</string>\n    <string name=\"no_internet_msg\">يرجى الإتصال بالإنترنت! تحديث ماجيسك مطلوب.</string>\n    <string name=\"dling\">جارٍ التنزيل</string>\n    <string name=\"relaunch_app\">يرجى إعادة تشغيل التطبيق يدوياً</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ast/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Instala la versión completa de Magisk pa finar la configuración. ¿Quies facelo agora?</string>\n    <string name=\"no_internet_msg\">¡Conéctate a internet! Tienes d\\'instalar la versión completa de Magisk.</string>\n    <string name=\"dling\">Baxando</string>\n    <string name=\"relaunch_app\">Volvi llanzar l\\'aplicación manualmente</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-az/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Qurmanı sonlandırmaq üçün full Magisk Manager`ə yüksəldin. Yüklənib qurulsun?</string>\n    <string name=\"no_internet_msg\">Lütfən internetə qoşulun! Full Magisk Manager\\'ə yüksəltmə lazımidir.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-b+sr+Latn/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--Author: Radoš Milićev (https://github.com/rammba)-->\n<resources>\n    <string name=\"upgrade_msg\">Ažurirajte Magisk da biste završili postavljanje. Preuzmi i instaliraj?</string>\n    <string name=\"no_internet_msg\">Molimo povežite se na internet! Neophodno je ažuriranje Magisk-a.</string>\n    <string name=\"dling\">Preuzimanje</string>\n    <string name=\"relaunch_app\">Molimo pokrenite aplikaciju ponovo</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-be/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Абнавіце Magisk Manager для завяршэння ўсталёўкі. Спампаваць і ўсталяваць?</string>\n    <string name=\"no_internet_msg\">Калі ласка, падлучыцеся да інтэрнэту! Патрабуецца абнаўленне Magisk Manager.</string>\n    <string name=\"dling\">Спампоўванне</string>\n    <string name=\"relaunch_app\">Калі ласка, уласнаручна перазапусціце праграму</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-bg/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Надградете до пълната версия на Magisk Manager, за да довършите първоначалната настройка. Изтегляне и инсталиране сега?</string>\n    <string name=\"no_internet_msg\">Моля да се свържете към работеща интернет мрежа, защото надграждането до пълната версия на Magisk Manager е задължително.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ca/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Fes una actualització total de Magisk Manager per finalitzar l\\'instal·lació. Descarregar i instal·lar?</string>\n    <string name=\"no_internet_msg\">Si us plau, connecta\\'t a internet! Es necessari fer una actualització total de Magisk Manager.</string>\n    <string name=\"dling\">Baixant</string>\n    <string name=\"relaunch_app\">Torni a obrir l\\'aplicació manualment, si us plau</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-cs/strings.xml",
    "content": "<resources></resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-de/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Upgrade zum vollständigen Magisk Manager, um das Setup abzuschließen. Herunterladen und installieren?</string>\n    <string name=\"no_internet_msg\">Bitte Internetverbindung herstellen! Das Upgrade zum vollständigen Magisk Manager ist erforderlich.</string>\n    <string name=\"dling\">Herunterladen...</string>\n    <string name=\"relaunch_app\">Bitte starte die App manuell neu!</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-el/strings.xml",
    "content": "<resources></resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-es/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Actualiza a la versión completa de Magisk para finalizar la instalación. ¿Descargar e instalar?</string>\n    <string name=\"no_internet_msg\">Sin conexión disponible</string>\n    <string name=\"dling\">Descargando...</string>\n    <string name=\"relaunch_app\">Por favor, relanza la app</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-et/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Täienda seadistuse lõpetamiseks Magiski täisversioonile. Kas laadid alla ja installid?</string>\n    <string name=\"no_internet_msg\">Palun ühendu Internetti! Nõutud on Magiski täisversioonile täiendamine.</string>\n    <string name=\"dling\">Allalaadimine</string>\n    <string name=\"relaunch_app\">Palun käivita rakendus käsitsi uuesti</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-fa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">برای به پایان رساندن تنظیمات ، به نسخه کامل Magisk Manager ارتقا دهید. دانلود و نصب بشه؟</string>\n    <string name=\"no_internet_msg\">لطفاً به اینترنت متصل شوید! برای ارتقا به نسخه کامل Magisk Manager لازم است.</string>\n    <string name=\"dling\">درحال دانلود</string>\n    <string name=\"relaunch_app\">لطفاً به صورت دستی برنامه را دوباره راه اندازی کنید</string>\n</resources>"
  },
  {
    "path": "app/stub/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Une mise à niveau de Magisk Manager en version complète est nécessaire afin de terminer l’installation. Souhaitez‑vous procéder à son téléchargement et son installation ?</string>\n    <string name=\"no_internet_msg\">Veuillez vous connecter à Internet ! Une mise à niveau complète de Magisk Manager est requise.</string>\n    <string name=\"dling\">Téléchargement en cours</string>\n    <string name=\"relaunch_app\">Veuillez relancer manuellement l’application</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-hi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">सेटअप को पूरा करने के लिए पूर्ण मैजिस्क मैनेजर में अपग्रेड करें. डाउनलोड करके इंस्टॉल करें?</string>\n    <string name=\"no_internet_msg\">कृपया इन्टरनेट से जुड़िये! पूर्ण मैजिस्क मैनेजर में अपग्रेड की आवश्यकता है।</string>\n    <string name=\"dling\">डाउनलोड हो रहा है</string>\n    <string name=\"relaunch_app\">कृपया ऐप को फिर से शुरू करें</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-hn/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Setup complete karne ke liye full Magisk install karna hoga. Abhi download aur install karein?</string>\n    <string name=\"no_internet_msg\">Full Magisk upgrade ke liye Internet se connect hona zaroori hai!</string>\n    <string name=\"dling\">Download ho raha hai…</string>\n    <string name=\"relaunch_app\">App ko reopen karo</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-hr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Nadogradite na full Magisk Manager da biste dovršili postavljanje. Preuzeti i instalirati?</string>\n    <string name=\"no_internet_msg\">Povežite se na Internet! Potrebna je nadogradnja na full Magisk Manager.</string>\n    <string name=\"dling\">Preuzimanje</string>\n    <string name=\"relaunch_app\">Ručno ponovno pokrenite aplikaciju</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-hu/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">A telepítés befejezéséhez frissíts a teljes Magiskre. Letöltés és telepítés?</string>\n    <string name=\"no_internet_msg\">Csatlakozz az internethez! Frissíteni kell a teljes Magiskre.</string>\n    <string name=\"dling\">Letöltés</string>\n    <string name=\"relaunch_app\">Kérjük manuálisan indítsd újra az alkalmazást</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Tingkatkan ke Magisk penuh untuk menyelesaikan penyiapan. Unduh dan pasang?</string>\n    <string name=\"no_internet_msg\">Harap sambungkan ke Internet! Peningkatan ke Magisk penuh diperlukan.</string>\n    <string name=\"dling\">Mengunduh</string>\n    <string name=\"relaunch_app\">Silakan jalankan ulang aplikasi secara manual</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-it/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Aggiorna alla versione completa di Magisk per completare l\\'installazione. Vuoi procedere con il download e l\\'installazione?</string>\n    <string name=\"no_internet_msg\">Controlla la connessione a Internet! È necessaria per l\\'aggiornamento alla versione completa di Magisk.</string>\n    <string name=\"dling\">Download in corso</string>\n    <string name=\"relaunch_app\">Riavvia manualmente l\\'app</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-iw/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">יש לעדכן לגירסה מלאה של מנהל Magisk בכדי לסיים את העדכון. להוריד ולהתקין?</string>\n    <string name=\"no_internet_msg\">נא להתחבר לאינטרנט! עדכון לגירסה מלאה של מנהל Magisk נדרש.</string>\n    <string name=\"dling\">מוריד</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ja/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Magisk Manager のフルバージョンにアップグレードしてセットアップを完了します。ダウンロードしてインストールしますか？</string>\n    <string name=\"dling\">ダウンロード中</string>\n    <string name=\"no_internet_msg\">インターネットに接続してください！フルバージョンの Magisk Manager が必要です。</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ka/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">განაახლეთ სრულ Magisk მენჯერის ვერსიამდე ინსტალაციის დასასრულებლად. გსურთ გადმოწერა და ინსტალირება?</string>\n    <string name=\"no_internet_msg\">გთხოვთ დაუკავშირდით ინტერნეტს! საჭიროა Magisk მენეჯერის სრულ ვერსიამდე განახლება.</string>\n    <string name=\"dling\">მიმდინარეობს გადმოწერა</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-kk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Орнатуды аяқтау үшін Magisk қолданбасының толық нұсқасын жүктеп алу керек. Жалғастырасыз ба?</string>\n    <string name=\"no_internet_msg\">Интернетке қосылыңызшы! Magisk қолданбасын жаңарту керек.</string>\n    <string name=\"dling\">Жүктеп алуда</string>\n    <string name=\"relaunch_app\">Қолданбаны қайта қосыңыз</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ko/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">완전한 Magisk Manager로 업데이트하여 설치를 마치십시오. 다운로드하고 설치하시겠습니까?</string>\n    <string name=\"no_internet_msg\">인터넷에 연결해 주시기 바랍니다! 완전한 Magisk Manager로 업데이트 해야 합니다.</string>\n    <string name=\"dling\">다운로드중</string>\n    <string name=\"relaunch_app\">앱을 수동으로 재시작 하세요</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ku/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">ماجیسکەکەت بەرزبکەوە بۆ وەشانی تەواوەتی، دەتەوێت دایبگریت و ڕێکیبخەیت؟</string>\n    <string name=\"no_internet_msg\">تکایە پەیوەست ببە بە ئینتەرنێتەوە، پێویستە ماجیسکەکەت ڕێک بخەیت.</string>\n    <string name=\"dling\">داگرتن</string>\n    <string name=\"relaunch_app\">تکایە دووبارە ئەپەکە بکەوە</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-lt/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Atsinaujinkite į pilną Magisk Manager versiją, kad baigtumėte pasiruošimą. Atsisiųsti ir instaliuoti?</string>\n    <string name=\"no_internet_msg\">Prašome prisijungti prie interneto! Atsinaujinimas į pilną Magisk Manager versiją yra privalomas.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-mk/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Надградете до целосната верзија на Magisk Manager за да го завршите поставувањето. Преземете и инсталирајте?</string>\n    <string name=\"no_internet_msg\">Ве молиме поврзете се на интернет бидејќи е потребна надградба на целосната верзија на Magisk Manager.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ml/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">സജ്ജീകരണം പൂർത്തിയാക്കാൻ പൂർണ്ണ മജിസ്കിലേക്ക് അപ്ഗ്രേഡ് ചെയ്യുക. ഡൗൺലോഡ് ചെയ്ത് ഇൻസ്റ്റാൾ ചെയ്യണോ?</string>\n    <string name=\"no_internet_msg\">ദയവായി ഇന്റർനെറ്റിലേക്ക് കണക്റ്റുചെയ്യുക! പൂർണ്ണ മജിസ്കിലേക്ക് അപ്‌ഗ്രേഡ് ചെയ്യേണ്ടതുണ്ട്.</string>\n    <string name=\"dling\">ഡൗൺലോഡ് ചെയ്യുന്നു</string>\n    <string name=\"relaunch_app\">ആപ്പ് സ്വമേധയാ വീണ്ടും തുറക്കുക</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-nb/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Oppgrader til den komplette versjonen av Magisk Manager for å fullføre oppsettet. Vil du laste ned og installere?</string>\n    <string name=\"no_internet_msg\">Vennligst koble deg på internettet! Å oppgradere til den komplette versjonen av Magisk Manager er påkrevd.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-nl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Installeer de volledige Magisk Manager om de installatie af te ronden. Wil je dit nu doen?</string>\n    <string name=\"no_internet_msg\">Maak verbinding met het internet! Het installeren van de volledige Magisk Manager is vereist.</string>\n    <string name=\"dling\">Bezig met downloaden...</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-pa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">ਸੈਟਅਪ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਪੂਰੇ ਮੈਜਿਕਸ ਮੈਨੇਜਰ ਵਿਚ ਅਪਗ੍ਰੇਡ ਕਰੋ. ਡਾਉਨਲੋਡ ਅਤੇ ਇੰਸਟੌਲ ਕਰੋ?</string>\n    <string name=\"no_internet_msg\">ਕਿਰਪਾ ਕਰਕੇ ਇੰਟਰਨੈਟ ਨਾਲ ਜੁੜੋ! ਪੂਰਾ ਮੈਜਿਕਸ ਮੈਨੇਜਰ ਅਪਗ੍ਰੇਡ ਕਰਨ ਦੀ ਲੋੜ ਹੈ।</string>\n    <string name=\"dling\">ਡਾਊਨਲੋਡ ਹੋ ਰਿਹਾ ਹੈ</string>\n    <string name=\"relaunch_app\">ਕਿਰਪਾ ਕਰਕੇ ਐਪ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰੋ</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-pl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Zaktualizuj Magisk do pełnej wersji aby ukończyć instalację. Pobrać i zainstalować?</string>\n    <string name=\"no_internet_msg\">Połącz się z Internetem! Wymagane jest uaktualnienie Magisk do pełnej wersji.</string>\n    <string name=\"dling\">Pobieranie</string>\n    <string name=\"relaunch_app\">Proszę ręcznie uruchomić aplikację ponownie.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Atualize para o Magisk completo para finalizar a configuração. Deseja baixar e instalar?</string>\n    <string name=\"no_internet_msg\">Por favor, conecte-se à internet! É necessário atualizar para o Magisk completo.</string>\n    <string name=\"dling\">Baixando</string>\n    <string name=\"relaunch_app\">Por favor, reinicie o app manualmente.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-pt-rPT/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Atualize para o Magisk completo para finalizar a configuração. Deseja descarregar e instalar?</string>\n    <string name=\"no_internet_msg\">Por favor, ligue-se à internet! É necessário atualizar para o Magisk completo.</string>\n    <string name=\"dling\">A descarregar</string>\n    <string name=\"relaunch_app\">Por favor, reinicie o app manualmente.</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ro/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Treci la versiunea completă Magisk pentru a finaliza configurarea. Descarci și instalezi?</string>\n    <string name=\"no_internet_msg\">Te rugăm să te conectezi la internet! Este necesară actualizarea la versiunea completă Magisk.</string>\n    <string name=\"dling\">Se descarcă</string>\n    <string name=\"relaunch_app\">Te rugăm să relansezi manual aplicația</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ru/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">Обновите Magisk для завершения установки. Продолжить?</string>\n    <string name=\"no_internet_msg\">Пожалуйста, подключитесь к Интернету! Требуется обновление Magisk.</string>\n    <string name=\"dling\">Загрузка</string>\n    <string name=\"relaunch_app\">Пожалуйста, перезапустите приложение</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-sk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Pre dokončenie inštalácie sa vyžaduje upgrade Magisk Managera. Stiahnuť a nainštalovať?</string>\n    <string name=\"no_internet_msg\">Pripojte sa na internet! Upgrade Magisk Managera je potrebný.</string>\n    <string name=\"dling\">Sťahuje sa</string>\n    <string name=\"relaunch_app\">Zavrite a spustite apku manuálne</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-sq/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Përditësoni Magisk në versionin e plotë për të përfunduar konfigurimin. Shkarkoni dhe instaloni?</string>\n    <string name=\"no_internet_msg\">Ju lutemi lidhuni me internetin! Kërkohet internet për të shkarkuar Magisk në versionin e plotë.</string>\n    <string name=\"dling\">Duke shkarkuar</string>\n    <string name=\"relaunch_app\">Ju lutemi, ri-hapni aplikacionin manualisht</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-sr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--Author: Radoš Milićev (https://github.com/rammba)-->\n<resources>\n    <string name=\"upgrade_msg\">Ажурирајте Magisk да бисте завршили постављање. Преузми и инсталирај?</string>\n    <string name=\"no_internet_msg\">Молимо повежите се на интернет! Неопходно је ажурирање Magisk-а.</string>\n    <string name=\"dling\">Преузимање</string>\n    <string name=\"relaunch_app\">Молимо покрените апликацију поново</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-sv/strings.xml",
    "content": "<resources></resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-sw/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Pata toleo jipya la Magisk kamili ili kumaliza usanidi. Pakua na usakinishe?</string>\n    <string name=\"no_internet_msg\">Tafadhali unganisha kwenye Mtandao! Kusasisha hadi Magisk kamili inahitajika.</string>\n    <string name=\"dling\">Inapakua</string>\n    <string name=\"relaunch_app\">Tafadhali zindua upya programu wewe mwenyewe</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-ta/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">அமைப்பை முடிக்க முழு மேகிஸ்க்கு மேம்படுத்தவும். பதிவிறக்கி நிறுவவா?</string>\n    <string name=\"no_internet_msg\">இணையத்துடன் இணைக்கவும்! முழு மேகிஸ்க்கு மேம்படுத்தல் தேவை.</string>\n    <string name=\"dling\">பதிவிறக்குகிறது</string>\n    <string name=\"relaunch_app\">பயன்பாட்டை கைமுறையாக மீண்டும் தொடங்கவும்</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-th/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Kurulumu tamamlamak için tam Magisk Manager\\'a yükseltin. İndirip yüklensin mi?</string>\n    <string name=\"no_internet_msg\">Lütfen internete bağlanın! Magisk Manager\\'ın tam sürümüne yükseltmek gerekiyor.</string>\n    <string name=\"dling\">İndiriliyor</string>\n    <string name=\"relaunch_app\">Lütfen uygulamayı el ile yeniden açınız.</string>\n</resources>\n\n"
  },
  {
    "path": "app/stub/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">Оновіть додаток Magisk для завершення встановлення. Завантажити і встановити?</string>\n    <string name=\"no_internet_msg\">Будь ласка, підключіться до Інтернету! Потрібно оновити додаток Magisk.</string>\n    <string name=\"dling\">Завантаження</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-vi/strings.xml",
    "content": "<resources></resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"upgrade_msg\">需要下载完整版 Magisk 才能正常运行。开始下载？</string>\n    <string name=\"no_internet_msg\">下载需要网络，请检查网络连接。</string>\n    <string name=\"dling\">正在下载</string>\n    <string name=\"relaunch_app\">请重新打开本应用</string>\n</resources>\n"
  },
  {
    "path": "app/stub/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources>\n    <string name=\"upgrade_msg\">需要升級到完整版 Magisk Manager。是否下載並安裝？</string>\n    <string name=\"no_internet_msg\">請連上網路！升級到完整版 Magisk Manager 是必須的。</string>\n    <string name=\"dling\">正在下載</string>\n</resources>\n"
  },
  {
    "path": "app/test/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/test/build.gradle.kts",
    "content": "plugins {\n    id(\"com.android.application\")\n}\n\nandroid {\n    namespace = \"com.topjohnwu.magisk.test\"\n\n    defaultConfig {\n        applicationId = \"com.topjohnwu.magisk.test\"\n        versionCode = 1\n        versionName = \"1.0\"\n        proguardFile(\"proguard-rules.pro\")\n    }\n\n    buildTypes {\n        release {\n            isMinifyEnabled = true\n        }\n    }\n}\n\nsetupTestApk()\n\ndependencies {\n    implementation(libs.test.runner)\n    implementation(libs.test.rules)\n    implementation(libs.test.junit)\n    implementation(libs.test.uiautomator)\n}\n"
  },
  {
    "path": "app/test/proguard-rules.pro",
    "content": "# Keep all test dependencies\n-keep class org.junit.** { *; }\n-keep class androidx.test.** { *; }\n\n# Make sure the classloader constructor is kept\n-keepclassmembers class com.topjohnwu.magisk.test.TestClassLoader { <init>(); }\n\n# Repackage dependencies\n-repackageclasses 'deps'\n-allowaccessmodification\n\n# Keep attributes for stacktrace\n-keepattributes *\n"
  },
  {
    "path": "app/test/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n\n    <queries tools:node=\"removeAll\" />\n\n    <application tools:node=\"replace\">\n        <uses-library android:name=\"android.test.runner\" />\n    </application>\n\n    <instrumentation\n        android:name=\"com.topjohnwu.magisk.test.AppTestRunner\"\n        android:targetPackage=\"com.topjohnwu.magisk\" />\n\n    <instrumentation\n        android:name=\"com.topjohnwu.magisk.test.TestRunner\"\n        android:targetPackage=\"com.topjohnwu.magisk.test\" />\n\n</manifest>\n"
  },
  {
    "path": "app/test/src/main/java/com/topjohnwu/magisk/test/AppMigrationTest.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.ParcelFileDescriptor.AutoCloseInputStream\nimport androidx.annotation.Keep\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.After\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport java.util.concurrent.CountDownLatch\nimport java.util.concurrent.TimeUnit\n\n@Keep\n@RunWith(AndroidJUnit4::class)\nclass AppMigrationTest {\n\n    companion object {\n        private const val APP_PKG = \"com.topjohnwu.magisk\"\n        private const val STUB_PKG = \"repackaged.$APP_PKG\"\n        private const val RECEIVER_TIMEOUT = 20L\n    }\n\n    private val instrumentation get() = InstrumentationRegistry.getInstrumentation()\n    private val context get() = instrumentation.context\n    private val uiAutomation get() = instrumentation.uiAutomation\n    private val registeredReceivers = mutableListOf<BroadcastReceiver>()\n\n    class PackageRemoveMonitor(\n        context: Context,\n        private val packageName: String\n    ) : BroadcastReceiver() {\n\n        val latch = CountDownLatch(1)\n\n        init {\n            val filter = IntentFilter(Intent.ACTION_PACKAGE_REMOVED)\n            filter.addDataScheme(\"package\")\n            context.registerReceiver(this, filter)\n        }\n\n        override fun onReceive(context: Context, intent: Intent) {\n            if (intent.action != Intent.ACTION_PACKAGE_REMOVED)\n                return\n            val data = intent.data ?: return\n            val pkg = data.schemeSpecificPart\n            if (pkg == packageName) latch.countDown()\n        }\n    }\n\n    @After\n    fun tearDown() {\n        registeredReceivers.forEach(context::unregisterReceiver)\n    }\n\n    private fun testAppMigration(pkg: String, method: String) {\n        val receiver = PackageRemoveMonitor(context, pkg)\n        registeredReceivers.add(receiver)\n\n        // Trigger the test to run migration\n        val pfd = uiAutomation.executeShellCommand(\n            \"am instrument -w --user 0 -e class .Environment#$method \" +\n                \"$pkg.test/${AppTestRunner::class.java.name}\"\n        )\n        val output = AutoCloseInputStream(pfd).reader().use { it.readText() }\n        assertTrue(\"$method failed, inst out: $output\", output.contains(\"OK (\"))\n\n        // Wait for migration to complete\n        assertTrue(\n            \"$pkg uninstallation failed\",\n            receiver.latch.await(RECEIVER_TIMEOUT, TimeUnit.SECONDS)\n        )\n    }\n\n    @Test\n    fun testAppHide() {\n        testAppMigration(APP_PKG, \"setupAppHide\")\n    }\n\n    @Test\n    fun testAppRestore() {\n        testAppMigration(STUB_PKG, \"setupAppRestore\")\n    }\n}\n"
  },
  {
    "path": "app/test/src/main/java/com/topjohnwu/magisk/test/Runners.kt",
    "content": "package com.topjohnwu.magisk.test\n\nimport android.os.Bundle\nimport androidx.test.platform.app.InstrumentationRegistry\nimport androidx.test.runner.AndroidJUnitRunner\n\nopen class TestRunner : AndroidJUnitRunner() {\n    override fun onCreate(arguments: Bundle) {\n        // Support short-hand \".ClassName\"\n        arguments.getString(\"class\")?.let {\n            val classArg = it.split(\",\").joinToString(separator = \",\") { clz ->\n                if (clz.startsWith(\".\")) {\n                    \"com.topjohnwu.magisk.test$clz\"\n                } else {\n                    clz\n                }\n            }\n            arguments.putString(\"class\", classArg)\n        }\n        super.onCreate(arguments)\n    }\n}\n\nclass AppTestRunner : TestRunner() {\n    override fun onCreate(arguments: Bundle) {\n        // Force using the target context's classloader to run tests\n        arguments.putString(\"classLoader\", TestClassLoader::class.java.name)\n        super.onCreate(arguments)\n    }\n}\n\nprivate val targetClassLoader inline get() =\n    InstrumentationRegistry.getInstrumentation().targetContext.classLoader\n\nclass TestClassLoader : ClassLoader(targetClassLoader)\n"
  },
  {
    "path": "build.py",
    "content": "#!/usr/bin/env python3\nimport argparse\nimport glob\nimport multiprocessing\nimport os\nimport platform\nimport re\nimport shutil\nimport stat\nimport subprocess\nimport sys\nimport tarfile\nimport urllib.request\nfrom pathlib import Path\nfrom zipfile import ZipFile\n\n\ndef color_print(code, str):\n    if no_color:\n        print(str)\n    else:\n        str = str.replace(\"\\n\", f\"\\033[0m\\n{code}\")\n        print(f\"{code}{str}\\033[0m\")\n\n\ndef error(str):\n    color_print(\"\\033[41;39m\", f\"\\n! {str}\\n\")\n    sys.exit(1)\n\n\ndef header(str):\n    color_print(\"\\033[44;39m\", f\"\\n{str}\\n\")\n\n\ndef vprint(str):\n    if args.verbose > 0:\n        print(str)\n\n\n# OS detection\nos_name = platform.system().lower()\nis_windows = False\nif os_name != \"linux\" and os_name != \"darwin\":\n    # It's possible we're using MSYS/Cygwin/MinGW, treat them all as Windows\n    is_windows = True\n    os_name = \"windows\"\nEXE_EXT = \".exe\" if is_windows else \"\"\n\nno_color = False\nif is_windows:\n    try:\n        import colorama\n\n        colorama.init()\n    except ImportError:\n        # We can't do ANSI color codes in terminal on Windows without colorama\n        no_color = True\n\nif not sys.version_info >= (3, 8):\n    error(\"Requires Python 3.8+\")\n\ncpu_count = multiprocessing.cpu_count()\n\n# Common constants\nsupport_abis = {\n    \"arm64-v8a\": \"aarch64-linux-android\",\n    \"armeabi-v7a\": \"thumbv7neon-linux-androideabi\",\n    \"x86_64\": \"x86_64-linux-android\",\n    \"x86\": \"i686-linux-android\",\n    \"riscv64\": \"riscv64-linux-android\",\n}\nabi_alias = {\n    \"arm\": \"armeabi-v7a\",\n    \"arm32\": \"armeabi-v7a\",\n    \"arm64\": \"arm64-v8a\",\n    \"x64\": \"x86_64\",\n}\ndefault_abis = support_abis.keys() - {\"riscv64\"}\nsupport_targets = {\"magisk\", \"magiskinit\", \"magiskboot\", \"magiskpolicy\", \"resetprop\"}\ndefault_targets = support_targets - {\"resetprop\"}\nrust_targets = default_targets.copy()\nclean_targets = {\"native\", \"cpp\", \"rust\", \"app\"}\nondk_version = \"r29.5\"\n\n# Global vars\nconfig = {}\nargs: argparse.Namespace\nbuild_abis: dict[str, str]\nforce_out = False\n\n###################\n# Helper functions\n###################\n\n\ndef mv(source: Path, target: Path):\n    try:\n        shutil.move(source, target)\n        vprint(f\"mv {source} -> {target}\")\n    except:\n        pass\n\n\ndef cp(source: Path, target: Path):\n    try:\n        shutil.copyfile(source, target)\n        vprint(f\"cp {source} -> {target}\")\n    except:\n        pass\n\n\ndef rm(file: Path):\n    try:\n        os.remove(file)\n        vprint(f\"rm {file}\")\n    except FileNotFoundError as e:\n        pass\n\n\ndef rm_on_error(func, path, _):\n    # Removing a read-only file on Windows will get \"WindowsError: [Error 5] Access is denied\"\n    # Clear the \"read-only\" bit and retry\n    try:\n        os.chmod(path, stat.S_IWRITE)\n        os.unlink(path)\n    except FileNotFoundError as e:\n        pass\n\n\ndef rm_rf(path: Path):\n    vprint(f\"rm -rf {path}\")\n    if sys.version_info >= (3, 12):\n        shutil.rmtree(path, ignore_errors=False, onexc=rm_on_error)\n    else:\n        shutil.rmtree(path, ignore_errors=False, onerror=rm_on_error)\n\n\ndef execv(cmds: list, env=None):\n    out = None if force_out or args.verbose > 0 else subprocess.DEVNULL\n    # Use shell on Windows to support PATHEXT\n    return subprocess.run(cmds, stdout=out, env=env, shell=is_windows)\n\n\ndef cmd_out(cmds: list):\n    return (\n        subprocess.run(\n            cmds,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.DEVNULL,\n            shell=is_windows,\n        )\n        .stdout.strip()\n        .decode(\"utf-8\")\n    )\n\n\n###############\n# Build Native\n###############\n\n\ndef clean_elf():\n    cargo_toml = Path(\"tools\", \"elf-cleaner\", \"Cargo.toml\")\n    cmds = [\"run\", \"--release\", \"--manifest-path\", cargo_toml]\n    if args.verbose == 0:\n        cmds.append(\"-q\")\n    elif args.verbose > 1:\n        cmds.append(\"--verbose\")\n    cmds.append(\"--\")\n    cmds.extend(glob.glob(\"native/out/*/magisk\"))\n    cmds.extend(glob.glob(\"native/out/*/magiskpolicy\"))\n    run_cargo(cmds)\n\n\ndef collect_ndk_build():\n    for arch in build_abis.keys():\n        arch_dir = Path(\"native\", \"libs\", arch)\n        out_dir = Path(\"native\", \"out\", arch)\n        for source in arch_dir.iterdir():\n            target = out_dir / source.name\n            mv(source, target)\n\n\ndef run_ndk_build(cmds: list[str]):\n    os.chdir(\"native\")\n    cmds.append(\"NDK_PROJECT_PATH=.\")\n    cmds.append(\"NDK_APPLICATION_MK=src/Application.mk\")\n    cmds.append(f\"APP_ABI={' '.join(build_abis.keys())}\")\n    cmds.append(f\"-j{cpu_count}\")\n    if args.verbose > 1:\n        cmds.append(\"V=1\")\n    if not args.release:\n        cmds.append(\"MAGISK_DEBUG=1\")\n    proc = execv([ndk_build, *cmds])\n    if proc.returncode != 0:\n        error(\"Build binary failed!\")\n    os.chdir(\"..\")\n\n\ndef build_cpp_src(targets: set[str]):\n    cmds = []\n    clean = False\n\n    if \"magisk\" in targets:\n        cmds.append(\"B_MAGISK=1\")\n        clean = True\n\n    if \"magiskpolicy\" in targets:\n        cmds.append(\"B_POLICY=1\")\n        clean = True\n\n    if \"magiskinit\" in targets:\n        cmds.append(\"B_PRELOAD=1\")\n\n    if \"resetprop\" in targets:\n        cmds.append(\"B_PROP=1\")\n\n    if cmds:\n        run_ndk_build(cmds)\n        collect_ndk_build()\n\n    cmds.clear()\n\n    if \"magiskinit\" in targets:\n        cmds.append(\"B_INIT=1\")\n\n    if \"magiskboot\" in targets:\n        cmds.append(\"B_BOOT=1\")\n\n    if cmds:\n        cmds.append(\"B_CRT0=1\")\n        run_ndk_build(cmds)\n        collect_ndk_build()\n\n    if clean:\n        clean_elf()\n\n\ndef run_cargo(cmds: list[str]):\n    ensure_paths()\n    env = os.environ.copy()\n    env[\"CARGO_BUILD_RUSTFLAGS\"] = f\"-Z threads={min(8, cpu_count)}\"\n    if shutil.which(\"rustup\"):\n        # Go through rustup proxies by default if available\n        env[\"RUSTUP_TOOLCHAIN\"] = str(rust_sysroot)\n    else:\n        env[\"PATH\"] = f\"{rust_sysroot / \"bin\"}{os.pathsep}{env[\"PATH\"]}\"\n        # Cargo calls executables in $RUSTROOT/lib/rustlib/$TRIPLE/bin, we need\n        # to make sure the runtime linker also search $RUSTROOT/lib for libraries.\n        # This is only required on Unix, as Windows search dlls from PATH.\n        if os_name == \"darwin\":\n            env[\"DYLD_FALLBACK_LIBRARY_PATH\"] = str(rust_sysroot / \"lib\")\n        elif os_name == \"linux\":\n            env[\"LD_LIBRARY_PATH\"] = str(rust_sysroot / \"lib\")\n    return execv([\"cargo\", *cmds], env)\n\n\ndef build_rust_src(targets: set[str]):\n    targets = targets.copy()\n    if \"resetprop\" in targets:\n        targets.add(\"magisk\")\n    targets = targets & rust_targets\n    if not targets:\n        return\n\n    os.chdir(Path(\"native\", \"src\"))\n\n    # Start building the build commands\n    cmds = [\"build\", \"-p\", \"\"]\n    if args.release:\n        cmds.append(\"-r\")\n        profile = \"release\"\n    else:\n        profile = \"debug\"\n    if args.verbose == 0:\n        cmds.append(\"-q\")\n    elif args.verbose > 1:\n        cmds.append(\"--verbose\")\n\n    for triple in build_abis.values():\n        cmds.append(\"--target\")\n        cmds.append(triple)\n\n    for tgt in targets:\n        cmds[2] = tgt\n        proc = run_cargo(cmds)\n        if proc.returncode != 0:\n            error(\"Build binary failed!\")\n\n    os.chdir(Path(\"..\", \"..\"))\n\n    native_out = Path(\"native\", \"out\")\n    rust_out = native_out / \"rust\"\n    for arch, triple in build_abis.items():\n        arch_out = native_out / arch\n        arch_out.mkdir(mode=0o755, exist_ok=True)\n        for tgt in targets:\n            source = rust_out / triple / profile / f\"lib{tgt}.a\"\n            target = arch_out / f\"lib{tgt}-rs.a\"\n            mv(source, target)\n\n\ndef write_if_diff(file_name: Path, text: str):\n    do_write = True\n    if file_name.exists():\n        with open(file_name, \"r\") as f:\n            orig = f.read()\n        do_write = orig != text\n    if do_write:\n        with open(file_name, \"w\") as f:\n            f.write(text)\n\n\ndef dump_flags_native():\n    flag_txt = \"#pragma once\\n\"\n    flag_txt += f'#define MAGISK_VERSION      \"{config[\"version\"]}\"\\n'\n    flag_txt += f'#define MAGISK_VER_CODE     {config[\"versionCode\"]}\\n'\n    flag_txt += f\"#define MAGISK_DEBUG        {0 if args.release else 1}\\n\"\n\n    native_gen_path = Path(\"native\", \"out\", \"generated\")\n    native_gen_path.mkdir(mode=0o755, parents=True, exist_ok=True)\n    write_if_diff(native_gen_path / \"flags.h\", flag_txt)\n\n    rust_flag_txt = f'pub const MAGISK_VERSION: &str = \"{config[\"version\"]}\";\\n'\n    rust_flag_txt += f'pub const MAGISK_VER_CODE: i32 = {config[\"versionCode\"]};\\n'\n    write_if_diff(native_gen_path / \"flags.rs\", rust_flag_txt)\n\n\ndef ensure_toolchain():\n    ensure_paths()\n\n    # Verify NDK install\n    try:\n        with open(Path(ndk_path, \"ONDK_VERSION\"), \"r\") as ondk_ver:\n            assert ondk_ver.read().strip(\" \\t\\r\\n\") == ondk_version\n    except:\n        error('Unmatched NDK. Please install/upgrade NDK with \"build.py ndk\"')\n\n    if sccache := shutil.which(\"sccache\"):\n        os.environ[\"RUSTC_WRAPPER\"] = sccache\n        os.environ[\"NDK_CCACHE\"] = sccache\n        os.environ[\"CARGO_INCREMENTAL\"] = \"0\"\n    if ccache := shutil.which(\"ccache\"):\n        os.environ[\"NDK_CCACHE\"] = ccache\n\n\ndef build_native():\n    ensure_toolchain()\n\n    if \"targets\" not in vars(args) or not args.targets:\n        targets = default_targets\n    else:\n        targets = set(args.targets) & support_targets\n        if not targets:\n            return\n\n    header(\"* Building: \" + \" \".join(targets))\n\n    dump_flags_native()\n    build_rust_src(targets)\n    build_cpp_src(targets)\n\n\n############\n# Build App\n############\n\n\ndef find_jdk():\n    env = os.environ.copy()\n    if \"ANDROID_STUDIO\" in env:\n        studio = env[\"ANDROID_STUDIO\"]\n        jbr = Path(studio, \"jbr\", \"bin\")\n        if not jbr.exists():\n            jbr = Path(studio, \"Contents\", \"jbr\", \"Contents\", \"Home\", \"bin\")\n        if jbr.exists():\n            env[\"PATH\"] = f'{jbr}{os.pathsep}{env[\"PATH\"]}'\n\n    no_jdk = False\n    try:\n        proc = subprocess.run(\n            \"javac -version\",\n            stdout=subprocess.DEVNULL,\n            stderr=subprocess.DEVNULL,\n            env=env,\n            shell=True,\n        )\n        no_jdk = proc.returncode != 0\n    except FileNotFoundError:\n        no_jdk = True\n\n    if no_jdk:\n        error(\n            \"Please set Android Studio's path to environment variable ANDROID_STUDIO,\\n\"\n            + \"or install JDK 21 and make sure 'javac' is available in PATH\"\n        )\n\n    return env\n\n\ndef dump_flags_app():\n    flag_txt = f\"abiList={','.join(build_abis.keys())}\\n\"\n    flag_txt += f\"version={config['version']}\\n\"\n    flag_txt += f\"versionCode={config['versionCode']}\\n\"\n\n    app_build_dir = Path(\"app\", \"build\")\n    app_build_dir.mkdir(parents=True, exist_ok=True)\n    write_if_diff(app_build_dir / \"flags.prop\", flag_txt)\n\n\ndef build_apk(module: str):\n    ensure_paths()\n    dump_flags_app()\n    env = find_jdk()\n    config_path = args.config.resolve()\n\n    os.chdir(\"app\")\n    build_type = \"Release\" if args.release else \"Debug\"\n    proc = execv(\n        [\n            gradlew,\n            f\"{module}:assemble{build_type}\",\n            f\"-PconfigPath={config_path}\",\n        ],\n        env=env,\n    )\n    os.chdir(\"..\")\n    if proc.returncode != 0:\n        error(f\"Build {module} failed!\")\n\n    build_type = build_type.lower()\n\n    paths = module.split(\":\")\n\n    apk = f\"{paths[-1]}-{build_type}.apk\"\n    source = Path(\"app\", *paths, \"build\", \"outputs\", \"apk\", build_type, apk)\n    target = config[\"outdir\"] / apk\n    mv(source, target)\n    return target\n\n\ndef build_app():\n    header(\"* Building the Magisk app\")\n    apk = build_apk(\":apk\")\n\n    build_type = \"release\" if args.release else \"debug\"\n\n    # Rename apk-variant.apk to app-variant.apk\n    source = apk\n    target = apk.parent / apk.name.replace(\"apk-\", \"app-\")\n    mv(source, target)\n    header(f\"Output: {target}\")\n\n    # Stub building is directly integrated into the main app\n    # build process. Copy the stub APK into output directory.\n    source = Path(\"app\", \"core\", \"src\", build_type, \"assets\", \"stub.apk\")\n    target = config[\"outdir\"] / f\"stub-{build_type}.apk\"\n    cp(source, target)\n\n\ndef build_app_ng():\n    header(\"* Building the next generation Magisk app\")\n    apk = build_apk(\":apk-ng\")\n    header(f\"Output: {apk}\")\n\n\ndef build_stub():\n    header(\"* Building the stub app\")\n    apk = build_apk(\":stub\")\n    header(f\"Output: {apk}\")\n\n\ndef build_test():\n    old_release = args.release\n    # Test APK has to be built as release to prevent classname clash\n    args.release = True\n    try:\n        header(\"* Building the test app\")\n        source = build_apk(\":test\")\n        target = source.parent / \"test.apk\"\n        mv(source, target)\n        header(f\"Output: {target}\")\n    finally:\n        args.release = old_release\n\n\n################\n# Build General\n################\n\n\ndef cleanup():\n    ensure_paths()\n    if args.targets:\n        targets: set[str] = set(args.targets) & clean_targets\n        if \"native\" in targets:\n            targets.add(\"cpp\")\n            targets.add(\"rust\")\n    else:\n        targets = clean_targets\n\n    if \"cpp\" in targets:\n        header(\"* Cleaning C++\")\n        rm_rf(Path(\"native\", \"libs\"))\n        rm_rf(Path(\"native\", \"obj\"))\n\n    if \"rust\" in targets:\n        header(\"* Cleaning Rust\")\n        rm_rf(Path(\"native\", \"out\", \"rust\"))\n        rm(Path(\"native\", \"src\", \"boot\", \"proto\", \"mod.rs\"))\n        rm(Path(\"native\", \"src\", \"boot\", \"proto\", \"update_metadata.rs\"))\n        for rs_gen in glob.glob(\"native/**/*-rs.*pp\", recursive=True):\n            rm(Path(rs_gen))\n\n    if \"native\" in targets:\n        header(\"* Cleaning native\")\n        rm_rf(Path(\"native\", \"out\"))\n        rm_rf(Path(\"tools\", \"elf-cleaner\", \"target\"))\n\n    if \"app\" in targets:\n        header(\"* Cleaning app\")\n        os.chdir(\"app\")\n        execv([gradlew, \":clean\"], env=find_jdk())\n        os.chdir(\"..\")\n\n\ndef build_all():\n    build_native()\n    build_app()\n    build_app_ng()\n    build_test()\n\n\n############\n# Utilities\n############\n\n\ndef gen_ide():\n    ensure_paths()\n\n    # Dump flags for both native and app\n    dump_flags_native()\n    dump_flags_app()\n\n    if not args.abi:\n        # Find the first 64-bit abi in build_abis\n        for abi in build_abis.keys():\n            if \"64\" in abi:\n                args.abi = abi\n                break\n        # If no 64-bit abi is found, use the first abi\n        args.abi = next(iter(build_abis.keys()))\n\n    set_build_abis({args.abi})\n\n    # Run build.rs to generate Rust/C++ FFI bindings\n    os.chdir(Path(\"native\", \"src\"))\n    run_cargo([\"check\", \"--target\", build_abis[args.abi]])\n    os.chdir(Path(\"..\", \"..\"))\n\n    # Generate compilation database\n    rm_rf(Path(\"native\", \"compile_commands.json\"))\n    run_ndk_build(\n        [\n            \"B_MAGISK=1\",\n            \"B_INIT=1\",\n            \"B_BOOT=1\",\n            \"B_POLICY=1\",\n            \"B_PRELOAD=1\",\n            \"B_PROP=1\",\n            \"B_CRT0=1\",\n            \"compile_commands.json\",\n        ]\n    )\n\n\ndef clippy_cli():\n    ensure_toolchain()\n    global force_out\n    force_out = True\n    if args.abi:\n        set_build_abis(set(args.abi))\n    else:\n        set_build_abis(default_abis)\n\n    if not args.release and not args.debug:\n        # If none is specified, run both\n        args.release = True\n        args.debug = True\n\n    os.chdir(Path(\"native\", \"src\"))\n    cmds = [\"clippy\", \"--no-deps\", \"--target\"]\n    for triple in build_abis.values():\n        if args.debug:\n            run_cargo(cmds + [triple])\n        if args.release:\n            run_cargo(cmds + [triple, \"--release\"])\n    os.chdir(Path(\"..\", \"..\"))\n\n\ndef cargo_cli():\n    global force_out\n    force_out = True\n    if len(args.commands) >= 1 and args.commands[0] == \"--\":\n        args.commands = args.commands[1:]\n    os.chdir(Path(\"native\", \"src\"))\n    run_cargo(args.commands)\n    os.chdir(Path(\"..\", \"..\"))\n\n\ndef setup_ndk():\n    ensure_paths()\n    url = f\"https://github.com/topjohnwu/ondk/releases/download/{ondk_version}/ondk-{ondk_version}-{os_name}.tar.xz\"\n    ndk_archive = url.split(\"/\")[-1]\n    ondk_path = Path(ndk_root, f\"ondk-{ondk_version}\")\n\n    header(f\"* Downloading and extracting {ndk_archive}\")\n    rm_rf(ondk_path)\n    with urllib.request.urlopen(url) as response:\n        with tarfile.open(mode=\"r|xz\", fileobj=response) as tar:\n            if hasattr(tarfile, \"data_filter\"):\n                tar.extractall(ndk_root, filter=\"tar\")\n            else:\n                tar.extractall(ndk_root)\n\n    rm_rf(ndk_path)\n    mv(ondk_path, ndk_path)\n\n\ndef setup_rustup():\n    wrapper_dir = Path(args.wrapper_dir)\n    rm_rf(wrapper_dir)\n    wrapper_dir.mkdir(mode=0o755, parents=True, exist_ok=True)\n    if \"CARGO_HOME\" in os.environ:\n        cargo_home = Path(os.environ[\"CARGO_HOME\"])\n    else:\n        cargo_home = Path.home() / \".cargo\"\n    cargo_bin = cargo_home / \"bin\"\n    for src in cargo_bin.iterdir():\n        tgt = wrapper_dir / src.name\n        tgt.symlink_to(f\"rustup{EXE_EXT}\")\n\n    # Build rustup-wrapper\n    wrapper_src = Path(\"tools\", \"rustup-wrapper\")\n    cargo_toml = wrapper_src / \"Cargo.toml\"\n    cmds = [\"build\", \"--release\", f\"--manifest-path={cargo_toml}\"]\n    if args.verbose > 1:\n        cmds.append(\"--verbose\")\n    run_cargo(cmds)\n\n    # Replace rustup with wrapper\n    wrapper = wrapper_dir / (f\"rustup{EXE_EXT}\")\n    wrapper.unlink(missing_ok=True)\n    cp(wrapper_src / \"target\" / \"release\" / (f\"rustup-wrapper{EXE_EXT}\"), wrapper)\n    wrapper.chmod(0o755)\n\n\n##################\n# AVD and testing\n##################\n\n\ndef push_files(script: Path):\n    if args.build:\n        build_all()\n    ensure_adb()\n\n    abi = cmd_out([adb_path, \"shell\", \"getprop\", \"ro.product.cpu.abi\"])\n    if not abi:\n        error(\"Cannot detect emulator ABI\")\n\n    if args.apk:\n        apk = Path(args.apk)\n    else:\n        apk = Path(\n            config[\"outdir\"], (\"app-release.apk\" if args.release else \"app-debug.apk\")\n        )\n\n    # Extract busybox from APK\n    busybox = Path(config[\"outdir\"], \"busybox\")\n    with ZipFile(apk) as zf:\n        with zf.open(f\"lib/{abi}/libbusybox.so\") as libbb:\n            with open(busybox, \"wb\") as bb:\n                bb.write(libbb.read())\n\n    try:\n        proc = execv([adb_path, \"push\", busybox, script, \"/data/local/tmp\"])\n        if proc.returncode != 0:\n            error(\"adb push failed!\")\n    finally:\n        rm_rf(busybox)\n\n    proc = execv([adb_path, \"push\", apk, \"/data/local/tmp/magisk.apk\"])\n    if proc.returncode != 0:\n        error(\"adb push failed!\")\n\n\ndef setup_avd():\n    header(\"* Setting up emulator\")\n\n    push_files(Path(\"scripts\", \"live_setup.sh\"))\n\n    proc = execv([adb_path, \"shell\", \"sh\", \"/data/local/tmp/live_setup.sh\"])\n    if proc.returncode != 0:\n        error(\"live_setup.sh failed!\")\n\n\ndef patch_avd_file():\n    input = Path(args.image)\n    output = Path(args.output)\n\n    header(f\"* Patching {input.name}\")\n\n    push_files(Path(\"scripts\", \"host_patch.sh\"))\n\n    proc = execv([adb_path, \"push\", input, \"/data/local/tmp\"])\n    if proc.returncode != 0:\n        error(\"adb push failed!\")\n\n    src_file = f\"/data/local/tmp/{input.name}\"\n    out_file = f\"{src_file}.magisk\"\n\n    proc = execv([adb_path, \"shell\", \"sh\", \"/data/local/tmp/host_patch.sh\", src_file])\n    if proc.returncode != 0:\n        error(\"host_patch.sh failed!\")\n\n    proc = execv([adb_path, \"pull\", out_file, output])\n    if proc.returncode != 0:\n        error(\"adb pull failed!\")\n\n    header(f\"Output: {output}\")\n\n\n##########################\n# Config, paths, argparse\n##########################\n\n\ndef ensure_paths():\n    global sdk_path, ndk_root, ndk_path, rust_sysroot\n    global ndk_build, gradlew, adb_path\n\n    # Skip if already initialized\n    if \"sdk_path\" in globals():\n        return\n\n    try:\n        sdk_path = Path(os.environ[\"ANDROID_HOME\"])\n    except KeyError:\n        try:\n            sdk_path = Path(os.environ[\"ANDROID_SDK_ROOT\"])\n        except KeyError:\n            error(\"Please set Android SDK path to environment variable ANDROID_HOME\")\n\n    ndk_root = sdk_path / \"ndk\"\n    ndk_path = ndk_root / \"magisk\"\n    ndk_build = ndk_path / \"ndk-build\"\n    rust_sysroot = ndk_path / \"toolchains\" / \"rust\"\n    adb_path = sdk_path / \"platform-tools\" / \"adb\"\n    gradlew = Path.cwd() / \"app\" / \"gradlew\"\n\n\n# We allow using several functionality with only ADB\ndef ensure_adb():\n    global adb_path\n    if \"adb_path\" not in globals():\n        if adb := shutil.which(\"adb\"):\n            adb_path = Path(adb)\n        else:\n            error(\"Command 'adb' cannot be found in PATH\")\n\n\ndef parse_props(file: Path) -> dict[str, str]:\n    props = {}\n    with open(file, \"r\") as f:\n        for line in [l.strip(\" \\t\\r\\n\") for l in f]:\n            if line.startswith(\"#\") or len(line) == 0:\n                continue\n            prop = line.split(\"=\")\n            if len(prop) != 2:\n                continue\n            key = prop[0].strip(\" \\t\\r\\n\")\n            value = prop[1].strip(\" \\t\\r\\n\")\n            if not key or not value:\n                continue\n            props[key] = value\n    return props\n\n\ndef set_build_abis(abis: set[str]):\n    global build_abis\n    # Try to convert several aliases to real ABI\n    abis = {abi_alias.get(k, k) for k in abis}\n    # Check any unknown ABIs\n    for k in abis - support_abis.keys():\n        error(f\"Unknown ABI: {k}\")\n    build_abis = {k: support_abis[k] for k in abis if k in support_abis}\n\n\ndef load_config():\n    commit_hash = cmd_out([\"git\", \"rev-parse\", \"--short=8\", \"HEAD\"])\n\n    # Default values\n    config[\"version\"] = commit_hash\n    config[\"versionCode\"] = 1000000\n    config[\"outdir\"] = \"out\"\n\n    # Load config.prop\n    if args.config.exists():\n        config.update(parse_props(args.config))\n\n    gradle_props = Path(\"app\", \"gradle.properties\")\n    for key, value in parse_props(gradle_props).items():\n        if key.startswith(\"magisk.\"):\n            config[key[7:]] = value\n\n    try:\n        config[\"versionCode\"] = int(config[\"versionCode\"])\n    except ValueError:\n        error('Config error: \"versionCode\" is required to be an integer')\n\n    config[\"outdir\"] = Path(config[\"outdir\"])\n    config[\"outdir\"].mkdir(mode=0o755, parents=True, exist_ok=True)\n\n    if \"abiList\" in config:\n        abis = set(re.split(\"\\\\s*,\\\\s*\", config[\"abiList\"]))\n    else:\n        abis = default_abis\n\n    set_build_abis(abis)\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description=\"Magisk build script\")\n    parser.set_defaults(func=lambda x: None)\n    parser.add_argument(\n        \"-r\", \"--release\", action=\"store_true\", help=\"compile in release mode\"\n    )\n    parser.add_argument(\n        \"-v\", \"--verbose\", action=\"count\", default=0, help=\"verbose output\"\n    )\n    parser.add_argument(\n        \"-c\",\n        \"--config\",\n        default=\"config.prop\",\n        help=\"custom config file (default: config.prop)\",\n    )\n    subparsers = parser.add_subparsers(title=\"actions\")\n\n    all_parser = subparsers.add_parser(\"all\", help=\"build everything\")\n\n    native_parser = subparsers.add_parser(\"native\", help=\"build native binaries\")\n    native_parser.add_argument(\n        \"targets\",\n        nargs=\"*\",\n        help=f\"{', '.join(support_targets)}, \\\n        or empty for defaults ({', '.join(default_targets)})\",\n    )\n\n    app_parser = subparsers.add_parser(\"app\", help=\"build the Magisk app\")\n\n    app_ng_parser = subparsers.add_parser(\n        \"app-ng\", help=\"build the next generation Magisk app\"\n    )\n\n    stub_parser = subparsers.add_parser(\"stub\", help=\"build the stub app\")\n\n    test_parser = subparsers.add_parser(\"test\", help=\"build the test app\")\n\n    clean_parser = subparsers.add_parser(\"clean\", help=\"cleanup\")\n    clean_parser.add_argument(\n        \"targets\", nargs=\"*\", help=\"native, cpp, rust, java, or empty to clean all\"\n    )\n\n    ndk_parser = subparsers.add_parser(\"ndk\", help=\"setup Magisk NDK\")\n\n    emu_parser = subparsers.add_parser(\"emulator\", help=\"setup AVD for development\")\n    emu_parser.add_argument(\"apk\", help=\"a Magisk APK to use\", nargs=\"?\")\n    emu_parser.add_argument(\n        \"-b\", \"--build\", action=\"store_true\", help=\"build before patching\"\n    )\n\n    avd_patch_parser = subparsers.add_parser(\n        \"avd_patch\", help=\"patch AVD ramdisk.img or init_boot.img\"\n    )\n    avd_patch_parser.add_argument(\"image\", help=\"path to ramdisk.img or init_boot.img\")\n    avd_patch_parser.add_argument(\"output\", help=\"output file name\")\n    avd_patch_parser.add_argument(\"--apk\", help=\"a Magisk APK to use\")\n    avd_patch_parser.add_argument(\n        \"-b\", \"--build\", action=\"store_true\", help=\"build before patching\"\n    )\n\n    cargo_parser = subparsers.add_parser(\n        \"cargo\", help=\"call 'cargo' commands against the project\"\n    )\n    cargo_parser.add_argument(\"commands\", nargs=argparse.REMAINDER)\n\n    clippy_parser = subparsers.add_parser(\"clippy\", help=\"run clippy on Rust sources\")\n    clippy_parser.add_argument(\n        \"--abi\", action=\"append\", help=\"target ABI(s) to run clippy\"\n    )\n    clippy_parser.add_argument(\n        \"-r\", \"--release\", action=\"store_true\", help=\"run clippy as release\"\n    )\n    clippy_parser.add_argument(\n        \"-d\", \"--debug\", action=\"store_true\", help=\"run clippy as debug\"\n    )\n\n    rustup_parser = subparsers.add_parser(\"rustup\", help=\"setup rustup wrapper\")\n    rustup_parser.add_argument(\n        \"wrapper_dir\", help=\"path to setup rustup wrapper binaries\"\n    )\n\n    gen_parser = subparsers.add_parser(\"gen\", help=\"generate files for IDE\")\n    gen_parser.add_argument(\"--abi\", help=\"target ABI to generate\")\n\n    # Set callbacks\n    all_parser.set_defaults(func=build_all)\n    native_parser.set_defaults(func=build_native)\n    cargo_parser.set_defaults(func=cargo_cli)\n    clippy_parser.set_defaults(func=clippy_cli)\n    rustup_parser.set_defaults(func=setup_rustup)\n    gen_parser.set_defaults(func=gen_ide)\n    app_parser.set_defaults(func=build_app)\n    app_ng_parser.set_defaults(func=build_app_ng)\n    stub_parser.set_defaults(func=build_stub)\n    test_parser.set_defaults(func=build_test)\n    emu_parser.set_defaults(func=setup_avd)\n    avd_patch_parser.set_defaults(func=patch_avd_file)\n    clean_parser.set_defaults(func=cleanup)\n    ndk_parser.set_defaults(func=setup_ndk)\n\n    if len(sys.argv) == 1:\n        parser.print_help()\n        sys.exit(1)\n\n    return parser.parse_args()\n\n\ndef main():\n    global args\n    args = parse_args()\n    args.config = Path(args.config)\n    load_config()\n    args.func()\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "config.prop.sample",
    "content": "##########################################################\n# All variables in config.prop are optional\n# Removing or leaving them blank will keep default values\n##########################################################\n\n# The version name of Magisk. Default: git HEAD short SHA1\nversion=string\n\n# Output path. Default: out\noutdir=string\n\n# List of ABIs to build, separated with ','\n# Default: armeabi-v7a,x86,arm64-v8a,x86_64\nabiList=[string]\n\n#####################################################\n# Signing configs for signing zips and APKs\n# These 4 variables has to be either all set or not\n#####################################################\n\n# Path to keystore file\nkeyStore=string\n# Keystore password\nkeyStorePass=string\n# The desired key alias in the keystore\nkeyAlias=string\n# Password of specified key alias\nkeyPass=string\n"
  },
  {
    "path": "docs/README.md",
    "content": "# Magisk Documentation\n\n- [Installation Instructions](install.md)\n- [Frequently Asked Questions](faq.md)\n- [Magisk Changelog](changes.md)\n\nThe following sections are for developers\n\n- [Building and Developing Magisk](build.md) (for developing Magisk itself)\n- [Developer Guides](guides.md) (for developers **using** Magisk)\n- [Magisk Tools](tools.md)\n- [Internal Details](details.md)\n- [Android Booting Shenanigans](boot.md)\n"
  },
  {
    "path": "docs/app_changes.md",
    "content": "# Magisk Manager Changelog\n\n### v8.0.7\n\n- Fix sepolicy rule migration when upgrading\n\n### v8.0.6\n\n- Minor UI changes\n- Update internal scripts\n\n### v8.0.5\n\n- Fix sepolicy rule copying\n\n### v8.0.4\n\n- A lot of stability changes and minor bug fixes\n- Collect device properties, app logcat, and Magisk logs when saving logs in the logs menu\n\n### v8.0.3\n\n- Switch to the new Magisk Module Repo setup in preparation to allow 3rd party repos\n- Add tapjacking protection on Superuser request dialog\n- Stability changes and bug fixes\n\n### v8.0.2\n\n- Fix an issue with requesting permission on devices older than Android 10\n- Make more files download through CDN\n\n### v8.0.1\n\n- Fix `vbmeta.img` patching for Samsung `AP.tar` files. This fixes bootloops on devices like Galaxy S10 after flashing updated AP files.\n- Properly truncate existing files before writing to prevent corrupted files\n- Prevent a possible UI loop when device ran into very low memory\n- Switch to use JSDelivr CDN for several files\n\n### v8.0.0\n\n- 100% full app rewrite! Will highlight functional changes below.\n- Add detailed device info in home screen to assist user installation\n- Support Magisk v21.0 communication protocol\n- Support patching modern Samsung `AP.tar`\n\n### v7.5.1\n\n- Fix toggling app components in MagiskHide screen\n- Update translations\n\n### v7.5.0\n\n- Support new MagiskSU communication method (ContentProvider)\n- Fix several issues with hidden stub APK\n- Support using BiometricPrompt (face unlock)\n\n### v7.4.0\n\n- Hide Magisk Manager with stub APKs on Android 9.0+\n- Allow customizing app name when hiding Magisk Manager\n- Generate random keys to sign the hidden Magisk Manager to prevent signature detections\n- Fix fingerprint UI infinite loop\n\n### v7.3.5\n\n- Sort installed modules by name\n- Better pre-5.0 support\n- Fix potential issues when patching tar files\n\n### v7.3.4\n\n- App is now fully written in Kotlin!\n- New downloading system\n- Add new \"Recovery Mode\" to Advanced Settings\n\n### v7.3.0/1/2\n\n- HUGE code base modernization, thanks @diareuse!\n- More sweet changes coming in the future!\n- Reboot device using proper API (no more abrupt reboot)\n- New floating button in Magisk logs to go to bottom\n\n### v7.2.0\n\n- Huge UI overhaul\n- More sweet changes coming in the future!\n\n### v7.1.2\n\n- Support patching Samsung AP firmware\n- Much better module downloading mechanism\n\n### v7.1.1\n\n- Fix a bug that causes some modules using new format not showing up\n\n### v7.1.0\n\n- Support the new module format\n- Support per-application component granularity MagiskHide targets (only on v19+)\n- Ask for fingerprint before deleting rules if enabled\n- Fix the bug that causes repackaging to lose settings\n- Several UI fixes\n\n### v7.0.0\n\n- Major UI redesign!\n- Render Markdown natively (no more buggy WebView!)\n- Support down to Android 4.1 (native Magisk only support Android 4.2 though)\n- Significantly improve Magisk log display performance\n- Fix post OTA scripts for A/B devices\n- Reduce memory usages when verifying and signing boot image\n- Drop support for Magisk lower than v18.0\n\n### v6.1.0\n\n- Introduce new downloading methods: no longer uses buggy system Download Manager\n- Introduce many new notifications for better user experience\n- Add support for Magisk v18.0\n- Change application name to \"Manager\" after hiding(repackaging) to prevent app name detection\n- Add built-in systemless hosts module (access in settings)\n- Auto launch the newly installed app after hiding(repackaging) and restoring Magisk Manager\n- Fix bug causing incomplete module.prop in modules to have improper UI\n\n### v6.0.1\n\n- Update to use new online module's organizing method\n- When fingerprint authentication is enabled, toggling root permissions in \"Superuser\" section now requires fingerprint beforehand\n- Fix crashes when entering MagiskHide section on some devices\n- Remove support to Magisk version lower than v15.0\n- Ask storage permissions before patching stock boot image\n- Update dark theme CardView color\n\n### v6.0.0\n\n- Update to latest AndroidX support library\n- Fix crashes when online repos contain incomplete metadata\n- Optimize BootSigner to use as little memory as possible, prevent OutOfMemoryError\n- Support new communication scheme between Magisk v17.2 and Magisk Manager\n- Enable excessive obfuscation to prevent APK analysis root detections (still not 100% obfuscated due to backwards compatibility with stable channel)\n\n### v5.9.0/v5.9.1\n\n- No more on boot notifications\n- Support new mechanism for installing to inactive slot for OTAs on A/B devices\n- Fix restore Magisk Manager settings on Android P\n- Verify existing file checksums to prevent unnecessary re-downloads\n- Update SNET extension to use new Google API, fix \"Invalid Response\" errors\n- Move fingerprint settings to magisk database to prevent the settings to be easily removed\n- Fingerprint settings are now guarded with fingerprint authentications before it can get changed\n- Prevent any files to be downloaded to /sdcard/MagiskManager\n\n### v5.8.3\n\n- Prevent invalid modules in the online repo crashing the app\n- Update Stable and Beta channel URLs\n\n### v5.8.1\n\n- Fix a bug that cause the root shell initializer not running in BusyBox environment\n\n### v5.8.0\n\n- Remain hidden when upgrading within repackaged Magisk Manager\n- New feature: support reconstructing a proper Magisk environment if error detected (e.g. after factory reset)\n- New uninstall method: download uninstaller and completely remove Magisk + Magisk Manager, following with a reboot\n- Hidden apps are now shown on the top of the list in MagiskHide fragment\n- Tons of under-the-hood bug fixes and improvements\n\n### v5.7.0\n\n- Add app shortcuts for Android 7.1+\n- Bump minimal module minMagisk requirement to 1500\n- Adjustments for new sepolicies on v16.4+\n- Fix crashes when refreshing the online repo\n\n### v5.6.4\n\n- Remove the blacklisted apps using SafetyNet (e.g. Pokemon GO)\n\n### v5.6.3\n\n- Fix repo loading UI logic\n\n### v5.6.2\n\n- Cleanup folders if installation failed\n- Add support for Android P\n\n### v5.6.1\n\n- Fix database crashes on F2FS with SQLite 3.21.0+\n- Optimize several settings options\n- Use native XML for settings migration\n\n### v5.6.0\n\n- Remove JNI requirement, Magisk Manager is now pure Java\n- Update the method of handling su database, may fix the issue that root requests won't save\n- Add the option to restore Magisk Manager after repackaging with random package name\n- Massive under-the-hood\n\n### v5.5.5\n\n- Fix crashes on Lollipop and some devices not following AOSP standards\n\n### v5.5.4\n\n- Fix dtbo on-boot detection, should follow configured dtbo patching behavior on Pixel 2 devices\n- Add fingerprint authentication for Superuser requests\n\n### v5.5.3\n\n- Update translations\n- Update internal scripts (in sync with Magisk)\n- Minor adjustments\n\n### v5.5.2\n\n- Support sorting online repos with last update\n- Fix issue that advanced installation settings won't stick\n- Prevent sudb crashing Magisk Manager\n\n### v5.5.1\n- Fix an issue in setting up superuser database, which causes some users to experience tons of root issues\n\n### v5.5.0\n\n- Fix dynamic resource loading, prevent crashes when checking SafetyNet\n- Update SignAPK to use very little RAM for supporting old devices\n- Support settings migration after hiding Magisk Manager\n- Add reboot menu in modules section\n- Add dark theme to superuser request dialogs\n- Properly handle new HIGHCOMP and add recommended KEEPVERITY and KEEPFORCEENCRYPT flags for installation\n- Support new paths for v14.6\n- Massive improvements in repackaging Magisk Manager\n\n### v5.4.3\n\n- Add flags to intent to prevent crashes\n- Update translations\n\n### v5.4.2\n\n- Support new paths and setup of v14.5\n- Support repackaging Magisk Manager for hiding (only works on v14.5+)\n- Support hardlinking global su database into app data\n- Support signing boot images (AVB 1.0)\n- Update app icon to adaptive icons\n- Remove app from MagiskHide list if uninstalled\n- Add support to save detailed logs when installing Magisk or modules\n- Fix download progress error if module is larger than 20MB\n- Changed the way how downloaded repos are processed, should be rock stable\n- Prevent crashes when database is corrupted - clear db instead\n- Fix saving wrong UID issue on multiuser mode\n- Add custom update channel support - you can now switch to your own update server!\n- Some UI adjustments and asynchronous UI performance improvements\n\n### v5.4.0\n\n- SafetyNet checks now require external code extension (for 100% FOSS)\n- Repo loading will now show real-time progress instead of blank screen\n- Show progress when downloading an online module\n- Allow secondary users to access superuser settings if allowed\n- Fix several places where external storage is needed but forgot to request\n- Fetching online repo info from sever is significantly faster thanks to multithreading\n- Pulling down Download page will now force a full refresh, thanks to the faster loading speed\n- Using new resetprop tool to properly detect MagiskHide status\n\n### v5.3.5\n\n- Fix error when MagiskManager folder doesn't exist\n- Offload many logic to scripts: script fixes will also be picked up in the app\n- Add installing Magisk to second slot on A/B partition devices\n- Support file based encryption: store necessary files into DE storage\n- Update uninstall method to self remove app and prompt user to manually reboot\n\n### v5.3.0\n\n- Add hide Magisk Manager feature - hide the app from detection\n- Add update channel settings - you can now receive beta updates through the app\n- Proper runtime permission implementation - request storage permission only when needed\n- Add boot image file patch feature - you can patch boot images without root!\n- Rewrite Magisk direct install method - merge with boot image file patch mode\n- Add feature to restore stock boot image - convenient for applying OTAs\n\n### v5.2.0\n\n- Fix force close which occurs when failure in flashing zips\n- Remove several external dependencies and rewrite a large portion of components\n- Improve MarkDown support: showing README.MD is much faster and will properly render Unicode characters (e.g. Chinese characters)\n- Add language settings: you can now switch to languages other than system default\n- Remove busybox included within APK; download through Internet if needed\n- Use Magisk internal busybox if detected\n- Busybox is added to the highest priority in PATH to create reliable shell environment\n- Always use global namespace for internal shell if possible\n\n### v5.1.1\n\n- Fix Magisk Manager hanging when reading files with no end newline\n- Massive rewrite AsyncTasks to prevent potential memory leak\n- Fix some minor issues with notifications\n- Improve update notification and popup behavior\n- Update internal uninstaller script\n\n### v5.1.0\n\n- Introduce a new flash log activity, so you know what is actually happening, just like flashing in custom recoveries!\n- Rewritten Java native shall interface: merged root shell and normal shell\n- Cleaned up implementation of repo recyclerview and adapters\n\n### v5.0.6\n\n- Fix crash when installing modules downloading from repos\n\n### v5.0.5\n\n- Fix update notifications on Android O\n- Fix crash when trying to install Magisk Manager update\n- Update translations\n\n### v5.0.4\n\n- Fix bug in su timeout\n\n### v5.0.3\n\n- Fix FC on boot on Android O\n- Adapt to Android O broadcast limitations: re-authenticate app when update is disabled on Android O\n\n### v5.0.2\n- Rewrite zip signing part, zips downloaded from repo will be properly signed and adjusted for custom recoveries\n\n### v5.0.1\n\n- Add namespace mode options\n- Fix a bug in Manager OTA system\n\n### v5.0.0\n\n- Support the new Magisk unified binary\n- Properly handle application install / uninstall root management issues\n- Add multiuser mode support\n- Add application upgrade re-authentication feature\n- Add basic integrity check for SafetyNet\n- Merged install fragment and status fragment into Magisk fragment\n- Fix theme switching glitch\n- Update translations\n\n### v4.3.3\n\n- Re-build APK with stable build tools\n\n### v4.3.2\n\n- Improve usage of Github API to support unlimited amount of online repos\n- Update translations (thanks to all contributors!!)\n\n### v4.3.1\n- Update proper Magisk busybox detection, will not be confused by busybox installed by default in custom roms\n\n### v4.3.0\n\n- Add Core Only Mode option\n- Fix crashes when selecting release note on Samsung devices\n- Hide modules using template lower than version 3\n\n### v4.2.7\n\n- Update translations\n- Update uninstall scripts\n\n### v4.2.6\n\n- Samsung crashes finally fixed (confirmed!)\n- Add settings to disable update notifications\n- Adjust Dark theme colors\n- Refined download section, now support download only when root is not detected\n- Fix crashes in boot image selection\n\n### v4.2\n\n- Change Repo cache to database\n- Dark theme refined\n- Alert Dialog buttons now properly aligned\n- Support very large online modules' zip processing\n- You can now download online modules without installing\n- Add notifications when new Magisk version is available\n- Removed changelog, donation link, support link in download cards\n- Read and display README.md for online modules\n\n### v4.1\n\n- Change MagiskHide startup\n- Reduce static data (= less memory leaks/issues)\n- Translation updates\n\n### v4.0\n\n- Whole new Superuser section for MagiskSU management!\n- Add Superuser tab in Logs section\n- Add lots of Superuser settings\n- Handle MagiskSU requests, logging, notifications\n- Controls MagiskHide initialization\n- Add disable button\n- Add uninstall button\n- Tons of improvements, not practical to list all :)\n\n### v3.1\n\n- Fix online repo inaccessible issue\n- Fix repo list card expanding issues\n- Change SafetyNet check to manually triggered\n- Update translations\n- Tons of bug fixes preventing potential crashes\n\n### v3.0\n\n- Now on Play Store\n- Add Status Section, you can check Safety Net, root status, and Magisk status in one place\n- Add Install Section, you can manually choose the boot image location and advanced options\n\n### v2.5\n\n- Add Magisk Hide section, you can now add/remove apps from Magisk Hide list\n- Support custom Magisk Version names, any string is now accepted (for custom builds)\n- Fixed modules and repos not sorted by name\n\n### v2.1\n\n- Add Magisk Hide settings\n- Add search bar in \"Downloads Sections\"\n- Fix crashes when no root is available\n- Fix trash can icon not updated when removing module\n- Prevent crash when Magisk Version is set incorrectly\n\n### v2.0\n\n- Massive refactor\n- Material Design\n- Module Management\n- Download Section\n- And much more....\n\n### v1.0\n\n- Initial release\n"
  },
  {
    "path": "docs/boot.md",
    "content": "# Android Booting Shenanigans\n\n## Terminologies\n\n- **rootdir**: the root directory (`/`). All files/folders/filesystems are stored in or mounted under rootdir. On Android, the filesystem may be either `rootfs` or the `system` partition.\n- **`initramfs`**: a section in Android's boot image that the Linux kernel will use as `rootfs`. People also use the term **ramdisk** interchangeably\n- **`recovery` and `boot` partition**: these 2 are actually very similar: both are Android boot images containing ramdisk and Linux kernel (plus some other stuff). The only difference is that booting `boot` partition will bring us to Android, while `recovery` has a minimalist self contained Linux environment for repairing and upgrading the device.\n- **SAR**: System-as-root. That is, the device uses `system` as rootdir instead of `rootfs`\n- **A/B, A-only**: For devices supporting [Seamless System Updates](https://source.android.com/devices/tech/ota/ab), it will have 2 slots of all read-only partitions; we call these **A/B devices**. To differentiate, non A/B devices will be called **A-only**\n- **2SI**: Two Stage Init. The way Android 10+ boots. More info later.\n\nHere are a few parameters to more precisely define a device's Android version:\n\n- **LV**: Launch Version. The Android version the device is **launched** with. That is, the Android version pre-installed when the device first hit the market.\n- **RV**: Running Version. The Android version the device is currently running on.\n\nWe will use **Android API level** to represent LV and RV. The mapping between API level and Android versions can be seen in [this table](https://source.android.com/setup/start/build-numbers#platform-code-names-versions-api-levels-and-ndk-releases). For example: Pixel XL is released with Android 7.1, and is running Android 10, these parameters will be `(LV = 25, RV = 29)`\n\n## Boot Methods\n\nAndroid booting can be roughly categorized into 3 major different methods. We provide a general rule of thumb to determine which method your device is most likely using, with exceptions listed separately.\n\nMethod | Initial rootdir | Final rootdir\n:---: | --- | ---\n**A** | `rootfs` | `rootfs`\n**B** | `system` | `system`\n**C** | `rootfs` | `system`\n\n- **Method A - Legacy ramdisk**: This is how *all* Android devices used to boot (good old days). The kernel uses `initramfs` as rootdir, and exec `/init` to boot.\n\t- Devices that does not fall in any of Method B and C's criteria\n- **Method B - Legacy SAR**: This method was first seen on Pixel 1. The kernel directly mounts the `system` partition as rootdir and exec `/init` to boot.\n\t- Devices with `(LV = 28)`\n\t- Google: Pixel 1 and 2. Pixel 3 and 3a when `(RV = 28)`.\n\t- OnePlus: 6 - 7\n\t- Maybe some `(LV < 29)` Android Go devices?\n- **Method C - 2SI ramdisk SAR**: This method was first seen on Pixel 3 Android 10 developer preview. The kernel uses `initramfs` as rootdir and exec `/init` in `rootfs`. This `init` is responsible to mount the `system` partition and use it as the new rootdir, then finally exec `/system/bin/init` to boot.\n\t- Devices with `(LV >= 29)`\n\t- Devices with `(LV < 28, RV >= 29)`, excluding those that were already using Method B\n\t- Google: Pixel 3 and 3a with `(RV >= 29)`\n\n### Discussion\n\nFrom documents online, Google's definition of SAR only considers how the kernel boots the device (**Initial rootdir** in the table above), meaning that only devices using **Method B** is *officially* considered an SAR device from Google's standpoint.\n\nHowever for Magisk, the real difference lies in what the device ends up using when fully booted (**Final rootdir** in the table above), meaning that **as far as Magisk's concern, both Method B and C is a form of SAR**, but just implemented differently. Every instance of SAR later mentioned in this document will refer to **Magisk's definition** unless specifically says otherwise.\n\nThe criteria for Method C is a little complicated, in layman's words: either your device is modern enough to launch with Android 10+, or you are running an Android 10+ custom ROM on a device that was using Method A.\n\n- Any Method A device running Android 10+ will automatically be using Method C\n- **Method B devices are stuck with Method B**, with the only exception being Pixel 3 and 3a, which Google retrofitted the device to adapt the new method.\n\nSAR is a very important part of [Project Treble](https://source.android.com/devices/architecture#hidl) as rootdir should be tied to the platform. This is also the reason why Method B and C comes with `(LV >= ver)` criterion as Google has enforced all OEMs to comply with updated requirements every year.\n\n## Some History\n\nWhen Google released the first generation Pixel, it also introduced [A/B (Seamless) System Updates](https://source.android.com/devices/tech/ota/ab). Due to [storage size concerns](https://source.android.com/devices/tech/ota/ab/ab_faqs), there are several differences compared to A-only, the most relevant one being the removal of `recovery` partition and the recovery ramdisk being merged into `boot`.\n\nLet's go back in time when Google is first designing A/B. If using SAR (only Boot Method B exists at that time), the kernel doesn't need `initramfs` to boot Android (because rootdir is in `system`). This mean we can be smart and just stuff the recovery ramdisk (containing the minimalist Linux environment) into `boot`, remove `recovery`, and let the kernel pick whichever rootdir to use (ramdisk or `system`) based on information from the bootloader.\n\nAs time passed from Android 7.1 to Android 10, Google introduced [Dynamic Partitions](https://source.android.com/devices/tech/ota/dynamic_partitions/implement). This is bad news for SAR, because the Linux kernel cannot directly understand this new partition format, thus unable to directly mount `system` as rootdir. This is when they came up with Boot Method C: always boot into `initramfs`, and let userspace handle the rest of booting. This includes deciding whether to boot into Android or recovery, or as they officially call: `USES_RECOVERY_AS_BOOT`.\n\nSome modern devices using A/B with 2SI also comes with `recovery_a/_b` partitions. This is officially supported with Google's standard. These devices will then only use the boot ramdisk to boot into Android as recovery is stored on a separate partition.\n\n## Piecing Things Together\n\nWith all the knowledge above, now we can categorize all Android devices into these different types:\n\nType | Boot Method | Partition | 2SI | Ramdisk in `boot`\n:---: | :---: | :---: | :---: | :---:\n**I** | A | A-only | No | `boot` ramdisk\n**II** | B | A/B | Any | `recovery` ramdisk\n**III** | B | A-only | Any | ***N/A***\n**IV** | C | Any | Yes | Hybrid ramdisk\n\nThese types are ordered chronologically by the time they were first available.\n\n- **Type I**: Good old legacy ramdisk boot\n- **Type II**: Legacy A/B devices. Pixel 1 is the first device of this type, being both the first A/B and SAR device\n- **Type III**: Late 2018 - 2019 devices that are A-only. **The worst type of device to ever exist as far as Magisk is concerned.**\n- **Type IV**: All devices using Boot Method C are Type IV. A/B Type IV ramdisk can boot into either Android or recovery based on info from bootloader; A-only Type IV ramdisk can only boot into Android.\n\nFurther details on Type III devices: Magisk is always installed in the ramdisk of a boot image. For all other device types, because their `boot` partition have ramdisk included, Magisk can be easily installed by patching boot image through the Magisk app or flash zip in custom recovery. However for Type III devices, they are **limited to install Magisk into the `recovery` partition**. Magisk will not function when booted normally; instead Type III device owners have to always reboot to recovery to maintain Magisk access.\n\nSome Type III devices' bootloader will still accept and provide `initramfs` that was manually added to the `boot` image to the kernel (e.g. some Xiaomi phones), but many device don't (e.g. Samsung S10, Note 10). It solely depends on how the OEM implements its bootloader.\n"
  },
  {
    "path": "docs/build.md",
    "content": "# Building and Development\n\n## Setup Environment\n\n- Supported platforms:\n  - Linux x64\n  - macOS x64 (Intel)\n  - macOS arm64 (Apple Silicon)\n  - Windows x64\n- Windows only: Enable [developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/enable-your-device-for-development). This is required because we need symbolic link support.\n- Install Python 3.8+:\n  - On Unix, install python3 using your favorite package manager\n  - On Windows, download and install the latest Python version on the [official website](https://www.python.org/downloads/windows/).<br>\n    Make sure to select **\"Add Python to PATH\"** during installation.\n  - (Optional on Windows): Run `pip install colorama` to install the `colorama` python package\n- Install Git:\n  - On Unix, install git with your favorite package manager\n  - On Windows, download the install the latest Git version on the [official website](https://git-scm.com/download/win).<br>\n    Make sure to **\"Enable symbolic links\"** during installation.\n- Install Android Studio and follow the instructions and go through the initial setup.\n- Set environment variable `ANDROID_HOME` to the Android SDK folder. This path can be found in Android Studio settings.\n- Setup JDK:\n  - The recommended option is to set environment variable `ANDROID_STUDIO` to the path where your Android Studio is installed. The build script will automatically find and use the bundled JDK.\n  - You can also setup JDK 17 yourself, but this guide will not cover the instructions.\n- Clone sources: `git clone --recurse-submodules https://github.com/topjohnwu/Magisk.git`\n- Run `./build.py ndk` to let the script download and install NDK for you\n\n## Building\n\n- To build everything and create the final Magisk APK, run `./build.py all`.\n- You can also build specific sub-components; call `build.py` to see your options. \\\n  For each action, use `-h` to access help (e.g. `./build.py binary -h`)\n- Configure the build by using `config.prop`. A sample `config.prop.sample` is provided.\n\n## IDE Support\n\n- Kotlin, Java, C++, and C code in the project should be supported in Android Studio out of the box. This repository can be directly opened with Android Studio as a project.\n- For Rust development, see the next section.\n- Before working on any native code, build all native code first with `./build.py binary`, as some generated code is only created during the build process.\n\n### Developing Rust\n\nFirst, install [rustup](https://www.rust-lang.org/tools/install), the official Rust toolchain manager. The Magisk NDK package [ONDK](https://github.com/topjohnwu/ondk) (the one installed with `./build.py ndk`) bundles a complete Rust toolchain, so _building_ the Magisk project itself does not require any further configuration.\n\nHowever, if you'd like to work on the Rust codebase, it'll be easier if you link ONDK's Rust toolchain in `rustup` and set it as default so several development tools and IDEs will work properly:\n\n```bash\n# Link the ONDK toolchain with the name \"magisk\"\nrustup toolchain link magisk \"$ANDROID_HOME/ndk/magisk/toolchains/rust\"\n# Set magisk as default\nrustup default magisk\n```\n\nIf you plan to use VSCode, you can then install the [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) plugin and everything should be good to go. If you plan to use Jetbrain IDEs (e.g. [Rustrover](https://www.jetbrains.com/rust/), or its Rust Plugin), we need some additional setup:\n\n- Install the official nightly toolchain and add some components. We won't actually use the nightly toolchain for anything other than tricking the IDE to cooperate; the magic happens in the wrapper we setup in the next step.\n\n```bash\nrustup toolchain install nightly\n# Add some components that is also included in ONDK\nrustup +nightly component add rust-src clippy\n```\n\n- Create a wrapper cargo bin directory to workaround `rustup` limitations\n\n```bash\n# We choose ~/.cargo/wrapper here as an example (and a good recommendation)\n# Pick any path you like, you just need to use this path in the next step\n./build.py rustup ~/.cargo/wrapper\n```\n\n- In Settings > Rust > Toolchain location, set this to the path of the wrapper directory we just created.\n- The IDE should now be fully functional, and you are able to enable `rustfmt` and use `Clippy` as the external linter.\n\n## Signing and Distribution\n\n- In release builds, the certificate of the key signing the Magisk APK will be used by Magisk's root daemon as a reference to reject and forcefully uninstall any non-matching Magisk apps to protect users from malicious and unverified Magisk APKs.\n- To do any development on Magisk itself, switch to an **official debug build and reinstall Magisk** to turn off the signature check.\n- To distribute your own Magisk builds signed with your own keys, set your signing configs in `config.prop`.\n- Check [Google's Documentation](https://developer.android.com/studio/publish/app-signing.html#generate-key) for more details on generating your own key.\n"
  },
  {
    "path": "docs/changes.md",
    "content": "# Magisk Changelog\n\n### v30.7 (2026.2.23)\n\n- [MagiskInit] Support Android 16 QPR2 sepolicy format\n- [MagiskInit] Support using klogdump partition as pre-init storage\n- [Zygisk] Support Android 16 QPR2 and higher\n- [Zygisk] Support Android XR and some Nubia devices\n- [MagiskSU] Do not drop capabilities by default, even when switching to non-root UIDs. Explicitly use the `--drop-cap` argument to drop capabilities.\n- [MagiskBoot] Better lzma format detection\n- [MagiskBoot] Fix various commandline argument parsing bugs\n\n### v30.6 (2025.12.1)\n\n- [MagiskInit] Revert a change that could result in bootloops\n\n### v30.5 (2025.12.1)\n\n- [General] Improve commandline argument parsing logic\n- [resetprop] Properly support Android versions with property overrides\n\n### v30.4 (2025.10.2)\n\n- [MagiskSU] Fix several implementation bugs\n\n### v30.3 (2025.9.29)\n\n- [General] Support installing Magisk into vendor_boot partition\n- [MagiskPolicy] Support new sepolicy binary format introduced in Android 16 QPR2\n- [Core] Migrate much more code into Rust\n- [MagiskSU] Fallback to older implementation when the kernel doesn't support zero userspace copy APIs\n\n### v30.2 (2025.8.6)\n\n- [Core] Fix an edge case breaking modules when overlayfs is involved\n- [Core] Fix module `.replace` functionality in certain situations\n- [resetprop] Reduce property modification traces\n\n### v30.1 (2025.7.3)\n\n- [Core] Fix bug in module mounting implementation\n- [MagiskSU] Add ability to restrict Linux capabilities even if running as root (uid=0)\n\n### v30.0 (2025.7.1)\n\n- [General] Various minor bug fixes\n- [Core] Migrate module implementation to Rust\n- [Core] Improve Magisk specific files injection logic\n- [MagiskBoot] Migrate compression code to Rust\n\n### v29.0 (2025.5.14)\n\n- [General] Massive internal refactoring and code migration\n- [App] Support downloading module zip files with XZ compression\n- [App] Disable app animations when system animations are disabled\n- [Core] Support systemlessly deleting files with modules using blank file nodes\n- [MagiskInit] Redesign sepolicy patching and injection logic\n- [MagiskSU] Better TTY/PTY support\n\n### v28.1 (2024.12.6)\n\n- [App] Fix stub APK download link\n- [App] Fix support for Android lower than 8.0\n- [General] Fix support for MTK Samsung devices\n- [MagiskInit] Fix a regression for 2SI devices\n- [MagiskPolicy] Fix a regression causing `overlay.d` replaced files to be not accessible\n\n### v28.0 (2024.10.10)\n\n- [General] Support 16k page size\n- [General] Add basic support for RISC-V (not built in releases)\n- [General] Use a minimal libc to build static executables (`magiskinit` and `magiskboot`) for smaller sizes\n- [Core] Remove unnecessary mirror for magic mount\n- [Core] Update boot image detection logic to support more devices\n- [MagiskInit] Rewrite 2SI logic for injecting `magiskinit` as `init`\n- [MagiskInit] Update preinit partition detection\n- [Zygisk] Update internal JNI hooking implementation\n- [MagiskPolicy] Preserve sepolicy config flag after patching\n- [MagiskPolicy] Optimize patching rules to reduce the amount of new rules being injected\n- [DenyList] Support enforcing denylist when Zygisk is disabled\n- [Resetprop] Improve implementation to workaround several property modification detections\n- [Resetprop] Update to properly work with property overlays\n- [App] Major internal code refactoring\n- [App] Support patching Samsung firmware with images larger than 8GiB\n- [App] Use user-initiated job instead of foreground services on Android 14\n- [App] Support Android 13+ built-in per-app language preferences\n- [App] Add `action.sh` support to allow modules to define an action triggered from UI\n- [MagiskBoot] Support spliting kernel images without decompression\n- [MagiskBoot] Properly support vendor boot images\n- [MagiskBoot] Disable Samsung PROCA from kernel image\n\n### v27.0 (2024.2.3)\n\n- [Zygisk] Introduce new code injection mechanism\n- [Zygisk] Support new signature introduced in U QPR2\n- [SEPolicy] Update libsepol to properly set some policy config bits\n- [MagiskBoot] Support compressing `init` so Magisk is installable on devices with small boot partitions\n- [ResetProp] Add new wait for property feature `resetprop -w`\n\n### v26.4 (2023.11.5)\n\n- [MagiskBoot] Don't pad zeros if signed boot image is larger\n- [MagiskPolicy] Fix `genfscon` and `filename_trans`\n- [MagiskPolicy] Fix bug in `libsepol`\n- [Zygisk] Fix and simplify file descriptor sanitization logic\n- [App] Prevent OOM when patching AP tarfiles\n- [App] Fix bug in device configuration detection\n- [Daemon] Fix certificate parsing of APKs\n- [General] Fix logging errors from C++ code being ignored\n\n### v26.3 (2023.9.4)\n\n- [General] Fix device information detection script\n- [General] Update BusyBox to 1.36.1\n- [General] Update toolchain that produces broken arm32 executables\n- [App] Fix root service unable to bind on OnePlus devices\n\n### v26.2 (2023.8.27)\n\n- [MagiskBoot] Support extracting boot image from `payload.bin`\n- [MagiskBoot] Support cpio files containing character files\n- [MagiskBoot] Support listing cpio content\n- [MagiskBoot] Directly handle AVB 1.0 signing and verification without going through Java implementation\n- [Daemon] Make daemon socket a fixed path in MAGISKTMP\n- [resetprop] Support printing property context\n- [resetprop] Support only printing persistent properties from storage\n- [resetprop] Properly support setting persistent properties bypassing property_service\n- [MagiskSU] Support `-g` and `-G` options\n- [MagiskSU] Support switching mount namespace to PID with `-t`\n- [MagiskPolicy] Fix patching extended permissions\n- [MagiskPolicy] Support more syntax for extended permissions\n- [MagiskPolicy] Support printing out the loaded sepolicy rules\n- [App] Support patching boot image from ROM zips\n- [App] Properly preserve `boot.img` when patching Samsung firmware with `init_boot.img`\n\n### v26.1 (2023.4.11)\n\n- [App] Fix crashing when revoking root permissions\n- [MagiskInit] Always prefer `ext4` partitions over `f2fs` when selecting the pre-init partition\n- [General] Restore module files' context/owner/group from mirror. This is a regression introduced in v26.0\n\n### v26.0 (2023.4.5)\n\n- [General] Bump minimum supported Android version to Android 6.0\n- [General] New magic mount backend. It supports loading modules into system with `overlayfs` files injected\n- [Zygisk] Release new API version 4\n- [Zygisk] Prevent crashing daemon in error\n- [Zygisk] Rewrite zygote code injection with new loader library approach\n- [Zygisk] Rewrite code unloading implementation\n- [MagiskBoot] Support amonet microloader devices\n- [MagiskBoot] Always use lz4_legacy compression on v4 boot images. This fixes boot image patching issues on Android U preview.\n- [MagiskInit] Support replacing existing \\*.rc files in `overlay.d`\n- [MagiskInit] Rewrite sepolicy.rules mounting and loading implementation\n- [App] Make stub patching 100% offline\n- [App] Support patching `init_boot.img` for Samsung ODIN firmware\n- [MagiskPolicy] Fix minor bug in command line argument parsing\n- [MagiskPolicy] Update rules to support Android U\n\n### v25.2 (2022.7.20)\n\n- [MagiskInit] Fix a potential issue when stub cpio is used\n- [MagiskInit] Fix reboot to recovery when stub cpio is used\n- [MagiskInit] Fix sepolicy.rules symlink for rootfs devices\n- [General] Better data encryption detection\n- [General] Move the whole logging infrastructure into Rust\n\n### v25.1 (2022.6.19)\n\n- [MagiskBoot] Fix ramdisk backup being incorrectly skipped\n- [MagiskBoot] Add new feature to detect unsupported dtb and abort during installation\n- [Zygisk] Change binary hijack paths\n- [App] Fix incorrect recovery mode detection and installation\n- [MagiskInit] Fix config not properly exported in legacy SAR devices\n- [General] Enforce the Magisk app to always match or be newer than `magiskd`\n\n### v25.0 (2022.6.7)\n\n- [MagiskInit] Update 2SI implementation, significantly increase device compatibility (e.g. Sony Xperia devices)\n- [MagiskInit] Introduce new `sepolicy` injection mechanism\n- [MagiskInit] Support Oculus Go\n- [MagiskInit] Support Android 13 GKIs (Pixel 6)\n- [MagiskBoot] Fix vbmeta extraction implementation\n- [App] Fix stub app on older Android versions\n- [App] [MagiskSU] Properly support apps using `sharedUserId`\n- [MagiskSU] Fix a possible crash in `magiskd`\n- [MagiskSU] Prune unused UIDs as soon as `system_server` restarts to prevent UID reuse attacks\n- [MagiskSU] Verify and enforce the installed Magisk app's certificate to match the distributor's signature\n- [MagiskSU] [Zygisk] Proper package management and detection\n- [Zygisk] Fix function hooking on devices running Android 12 with old kernels\n- [Zygisk] Fix Zygisk's self code unloading implementation\n- [DenyList] Fix DenyList on shared UID apps\n- [BusyBox] Add workaround for devices running old kernels\n\n### v24.3 (2022.3.10)\n\n- [General] Stop using `getrandom` syscall\n- [Zygisk] Update API to v3, adding new fields to `AppSpecializeArgs`\n- [App] Improve app repackaging installation workflow\n\n### v24.2 (2022.3.1)\n\n- [MagiskSU] Fix buffer overflow\n- [MagiskSU] Fix owner managed multiuser superuser settings\n- [MagiskSU] Fix command logging when using `su -c <cmd>`\n- [MagiskSU] Prevent su request indefinite blocking\n- [MagiskBoot] Support `lz4_legacy` archive with multiple magic\n- [MagiskBoot] Fix `lz4_lg` compression\n- [DenyList] Allow targeting processes running as system UID\n- [Zygisk] Workaround Samsung's \"early zygote\"\n- [Zygisk] Improved Zygisk loading mechanism\n- [Zygisk] Fix application UID tracking\n- [Zygisk] Fix improper `umask` being set in zygote\n- [App] Fix BusyBox execution test\n- [App] Improve stub loading mechanism\n- [App] Major app upgrade flow improvements\n- [General] Improve commandline error handling and messaging\n\n### v24.1 (2022.1.28)\n\n- [App] Stability improvements\n\n### v24.0 (2022.1.26)\n\n- [General] MagiskHide is removed from Magisk\n- [General] Support Android 12\n- [General] Support devices that do not support 32-bit and only runs 64-bit code\n- [General] Update BusyBox to 1.34.1\n- [Zygisk] Introduce new feature: Zygisk\n- [Zygisk] Introduce DenyList feature to revert Magisk features in user selected processes\n- [MagiskBoot] Support patching 32-bit kernel zImages\n- [MagiskBoot] Support boot image header v4\n- [MagiskBoot] Support patching out `skip_initramfs` from dtb bootargs\n- [MagiskBoot] Add new env variable `PATCHVBMETAFLAG` to configure whether vbmeta flags should be patched\n- [MagiskInit] Support loading fstab from `/system/etc` (required for Pixel 6)\n- [MagiskInit] Support `/proc/bootconfig` for loading boot configurations\n- [MagiskInit] Better support for some Meizu devices\n- [MagiskInit] Better support for some OnePlus/Oppo/Realme devices\n- [MagiskInit] Support `init.real` on some Sony devices\n- [MagiskInit] Skip loading Magisk when detecting DSU\n- [MagiskPolicy] Load `*_compat_cil_file` from system_ext\n- [MagiskSU] Use isolated devpts if the kernel supports it\n- [MagiskSU] Fix root shell if isolated mount namespace is set\n- [resetprop] Deleted properties are now wiped from memory instead of just unlinking\n- [App] Build a single APK for all ABIs\n- [App] Switch to use standard bottom navigation bar\n- [App] Downloading modules from the centralized Magisk-Modules-Repo is removed\n- [App] Support user configuration of boot image vbmeta patching\n- [App] Restore the ability to install Magisk on the other slot on some A/B devices\n- [App] Allow modules to specify an update URL for in-app update + install\n\n### v23.0 (2021.5.12)\n\n- [App] Update snet extension. This fixes SafetyNet API errors.\n- [App] Fix a bug in the stub app that causes APK installation to fail\n- [App] Hide annoying errors in logs when hidden as stub\n- [App] Fix issues when patching ODIN tar files when the app is hidden\n- [General] Remove all pre Android 5.0 support\n- [General] Update BusyBox to use proper libc\n- [General] Fix C++ undefined behaviors\n- [General] Several `sepolicy.rule` copy/installation fixes\n- [MagiskPolicy] Remove unnecessary sepolicy rules\n- [MagiskHide] Update package and process name validation logic\n- [MagiskHide] Some changes that prevents zygote deadlock\n\n### v22.1 (2021.4.9)\n\n- [App] Prevent multiple installation sessions running in parallel\n- [App] Prevent OutOfMemory crashes when checking boot signature on PXA boot images\n- [General] Proper cgroup migration implementation\n- [General] Rewrite log writer from scratch, should resolve any crashes and deadlocks\n- [General] Many scripts updates fixing regressions\n- [MagiskHide] Prevent possible deadlock when signal arrives\n- [MagiskHide] Partial match process names if necessary\n- [MagiskBoot] Preserve and patch AVB 2.0 structures/headers in boot images\n- [MagiskBoot] Properly strip out data encryption flags\n- [MagiskBoot] Prevent possible integer overflow\n- [MagiskInit] Fix `sepolicy.rule` mounting strategy\n- [resetprop] Always delete existing `ro.` props before updating. This will fix bootloops that could be caused by modifying device fingerprint properties.\n\n### v22.0 (2021.2.23)\n\n- [General] Magisk and Magisk Manager is now merged into the same package!\n- [App] The term \"Magisk Manager\" is no longer used elsewhere. We refer it as the Magisk app.\n- [App] Support hiding the Magisk app with advanced technique (stub APK loading) on Android 5.0+ (it used to be 9.0+)\n- [App] Disallow re-packaging the Magisk app on devices lower than Android 5.0\n- [App] Detect and warn about multiple invalid states and provide instructions on how to resolve it\n- [MagiskHide] Fix a bug when stopping MagiskHide does not take effect\n- [MagiskBoot] Fix bug when unpacking `lz4_lg` compressed boot images\n- [MagiskInit] Support Galaxy S21 series\n- [MagiskSU] Fix incorrect APEX paths that caused `libsqlite.so` fail to load\n\n### v21.4 (2021.1.17)\n\n- [MagiskSU] Fix `su -c` behavior that broke many root apps\n- [General] Properly handle read/write over sockets (the `broken pipe` issue)\n\n### v21.3 (2021.1.16)\n\n- [MagiskInit] Avoid mounting `f2fs` userdata as it may result in kernel crashes. This shall fix a lot of bootloops\n- [MagiskBoot] Fix a minor header checksum bug for `DHTB` header and ASUS `blob` image formats\n- [MagiskHide] Allowing hiding isolated processes if the mount namespace is separated\n\n### v21.2 (2020.12.28)\n\n- [MagiskInit] Detect 2SI after mounting `system_root` on legacy SAR devices\n- [General] Make sure `post-fs-data` scripts cannot block more than 35 seconds\n- [General] Fix the `magisk --install-module` command\n- [General] Trim Windows newline when reading files\n- [General] Directly log to file to prevent `logcat` weirdness\n- [MagiskBoot] Fix header dump/load for header v3 images\n\n### v21.1 (2020.11.13)\n\n- [MagiskBoot] Support boot header v3 (Pixel 5 and 4a 5G)\n- [MagiskBoot] Distinguish `lz4_lg` and `lz4_legacy` (Pixel 5 and 4a 5G)\n- [MagiskBoot] Support vendor boot images (for dev, not relevant for Magisk installation)\n- [MagiskInit] Support kernel cmdline `androidboot.fstab_suffix`\n- [MagiskInit] Support kernel initialized dm-verity on legacy SAR\n- [General] Significantly broaden sepolicy.rule compatibility\n- [General] Add Magisk binaries to `PATH` when executing boot scripts\n- [General] Update `--remove-modules` command implementation\n- [General] Make Magisk properly survive after factory reset on Android 11\n- [MagiskSU] Add APEX package `com.android.i18n` to `LD_LIBRARY_PATH` when linking `libsqlite.so`\n- [MagiskHide] Support hiding apps installed in secondary users (e.g. work profile)\n- [MagiskHide] Make zygote detection more robust\n\n### v21.0 (2020.10.3)\n\n- [General] Support Android 11 🎉\n- [General] Add Safe Mode detection. Disable all modules when the device is booting into Safe Mode.\n- [General] Increase `post-fs-data` mode timeout from 10 seconds to 40 seconds\n- [MagiskInit] Rewritten 2SI support from scratch\n- [MagiskInit] Support when no `/sbin` folder exists (Android 11)\n- [MagiskInit] Dump fstab from device-tree to rootfs and force `init` to use it for 2SI devices\n- [MagiskInit] Strip out AVB for 2SI as it may cause bootloop\n- [Modules] Rewritten module mounting logic from scratch\n- [MagiskSU] For Android 8.0+, a completely new policy setup is used. This reduces compromises in Android's sandbox, providing more policy isolation and better security for root users.\n- [MagiskSU] Isolated mount namespace will now first inherit from parent process, then isolate itself from the world\n- [MagiskSU] Update communication protocol with Magisk Manager to work with the hardened SELinux setup\n- [MagiskPolicy] Optimize match all rules. This will significantly reduce policy binary size and save memory and improve general kernel performance.\n- [MagiskPolicy] Support declaring new types and attributes\n- [MagiskPolicy] Make policy statement closer to stock `*.te` format. Please check updated documentation or `magiskpolicy --help` for more details.\n- [MagiskBoot] Support compressed `extra` blobs\n- [MagiskBoot] Pad boot images to original size with zeros\n- [MagiskHide] Manipulate additional vendor properties\n\n### v20.4 (2020.3.23)\n\n- [MagiskInit] Fix potential bootloop in A-only 2SI devices\n- [MagiskInit] Properly support Tegra partition naming\n- [General] Load libsqlite.so dynamically, which removes the need to use wrapper scripts on Android 10+\n- [General] Detect API level with a fallback method on some devices\n- [General] Workaround possible bug in x86 kernel readlinkat system call\n- [BusyBox] Enable SELinux features. Add chcon/runcon etc., and '-Z' option to many applets\n- [BusyBox] Introduce standalone mode. More details in release notes\n- [MagiskHide] Disable MagiskHide by default\n- [MagiskHide] Add more potential detectable system properties\n- [MagiskHide] Add workaround for Xiaomi devices bootloop when MagiskHide is enabled on cross region ROMs\n- [MagiskBoot] Support patching special Motorolla DTB format\n- [MagiskPolicy] Support 'genfscon' sepolicy rules\n- [Scripts] Support NAND based boot images (character nodes in /dev/block)\n- [Scripts] Better addon.d (both v1 and v2) support\n- [Scripts] Support Lineage Recovery for Android 10+\n\n### v20.3 (2020.1.10)\n\n- [MagiskBoot] Fix `lz4_legacy` decompression\n\n### v20.2 (2020.1.2)\n\n- [MagiskSU] Properly handle communication between daemon and application (root request prompt)\n- [MagiskInit] Fix logging in kmsg\n- [MagiskBoot] Support patching dtb/dtbo partition formats\n- [General] Support pre-init sepolicy patch in modules\n- [Scripts] Update magisk stock image backup format\n\n### v20.1 (2019.11.2)\n\n- [MagiskSU] Support component name agnostic communication (for stub APK)\n- [MagiskBoot] Set proper `header_size` in boot image headers (fix vbmeta error on Samsung devices)\n- [MagiskHide] Scan zygote multiple times\n- [MagiskInit] Support recovery images without /sbin/recovery binary. This will fix some A/B devices unable to boot to recovery after flashing Magisk\n- [General] Move acct to prevent daemon being killed\n- [General] Make sure \"--remove-modules\" will execute uninstall.sh after removal\n\n### v20.0 (2019.10.11)\n\n- [MagiskBoot] Support inject/modify `mnt_point` value in DTB fstab\n- [MagiskBoot] Support patching QCDT\n- [MagiskBoot] Support patching DTBH\n- [MagiskBoot] Support patching PXA-DT\n- [MagiskInit] [2SI] Support non A/B setup (Android 10)\n- [MagiskHide] Fix bug that reject process names with \":\"\n- [MagicMount] Fix a bug that cause /product mirror not created\n\n### v19.4 (2019.9.19)\n\n- [MagiskInit] [SAR] Boot system-as-root devices with system mounted as /\n- [MagiskInit] [2SI] Support 2-stage-init for A/B devices (Pixel 3 Android 10)\n- [MagiskInit] [initramfs] Delay sbin overlay creation to post-fs-data\n- [MagiskInit] [SARCompat] Old system-as-root implementation is deprecated, no more future changes\n- [MagiskInit] Add overlay.d support for root directory overlay for new system-as-root implementation\n- [MagiskSU] Unblock all signals in root shells (fix bash on Android)\n- [MagicMount] Support replacing files in /product\n- [MagiskHide] Support Android 10's Zygote blastula pool\n- [MagiskHide] All random strings now also have random length\n- [MagiskBoot] Allow no recompression for ramdisk.cpio\n- [MagiskBoot] Support some weird Huawei boot images\n- [General] Add new `--remove-modules` command to remove modules without root in ADB shell\n- [General] Support Android 10 new APEX libraries (Project Mainline)\n\n### v19.3 (2019.6.5)\n\n- [MagiskHide] Hugely improve process monitor implementation, hopefully should no longer cause 100% CPU and daemon crashes\n- [MagiskInit] Wait for partitions to be ready for early mount, should fix bootloops on a handful of devices\n- [MagiskInit] Support EROFS used in EMUI 9.1\n- [MagiskSU] Properly implement mount namespace isolation\n- [MagiskBoot] Proper checksum calculation for header v2\n\n### v19.2 (2019.5.20)\n\n- [General] Fix uninstaller\n- [General] Fix bootloops on some devices with tmpfs mounting to /data\n- [MagiskInit] Add Kirin hi6250 support\n- [MagiskSU] Stop claiming device focus for su logging/notify if feasible.\n  This fix issues with users locking Magisk Manager with app lock, and prevent\n  video apps get messed up when an app is requesting root in the background.\n\n### v19.1 (2019.5.1)\n\n- [General] Support recovery based Magisk\n- [General] Support Android Q Beta 2\n- [MagiskInit] New sbin overlay setup process for better compatibility\n- [MagiskInit] Allow long pressing volume up to boot to recovery in recovery mode\n- [MagicMount] Use proper system_root mirror\n- [MagicMount] Use self created device nodes for mirrors\n- [MagicMount] Do not allow adding new files/folders in partition root folder (e.g. /system or /vendor)\n\n### v19.0 (2019.3.28)\n\n- [General] Remove usage of magisk.img\n- [General] Add 64 bit magisk binary for native 64 bit support\n- [General] Support A only system-as-root devices that released with Android 9.0\n- [General] Support non EXT4 system and vendor partitions\n- [MagiskHide] Use Zygote ptracing for monitoring new processes\n- [MagiskHide] Targets are now per-application component\n- [MagiskInit] Support Android Q (no logical partition support yet!)\n- [MagiskPolicy] Support Android Q new split sepolicy setup\n- [MagiskInit] Move sbin overlay creation from main daemon post-fs-data to early-init\n- [General] Service scripts now run in parallel\n- [MagiskInit] Directly inject magisk services to init.rc\n- [General] Use lzma2 compressed ramdisk in extreme conditions\n- [MagicMount] Clone attributes from original file if exists\n- [MagiskSU] Use `ACTION_REBOOT` intent to workaround some OEM broadcast restrictions\n- [General] Use `skip_mount` instead of `auto_mount`: from opt-in to opt-out\n\n### v18.1 (2019.2.4)\n\n- [General] Support EMUI 9.0\n- [General] Support Kirin 960 devices\n- [General] Support down to Android 4.2\n- [General] Major code base modernization under-the-hood\n\n### v18.0 (2018.12.8)\n\n- [General] Migrate all code base to C++\n- [General] Modify database natively instead of going through Magisk Manager\n- [General] Deprecate path /sbin/.core, please start using /sbin/.magisk\n- [General] Boot scripts are moved from `<magisk_img>/.core/<stage>.d` to `/data/adb/<stage>.d`\n- [General] Remove native systemless hosts (Magisk Manager is updated with a built-in systemless hosts module)\n- [General] Allow module post-fs-data.sh scripts to disable/remove modules\n- [MagiskHide] Use component names instead of process names as targets\n- [MagiskHide] Add procfs protection on SDK 24+ (Nougat)\n- [MagiskHide] Remove the folder /.backup to prevent detection\n- [MagiskHide] Hide list is now stored in database instead of raw textfile in images\n- [MagiskHide] Add \"--status\" option to CLI\n- [MagiskHide] Stop unmounting non-custom related mount points\n- [MagiskSU] Add `FLAG_INCLUDE_STOPPED_PACKAGES` in broadcasts to force wake Magisk Manager\n- [MagiskSU] Fix a bug causing SIGWINCH not properly detected\n- [MagiskPolicy] Support new av rules: type_change, type_member\n- [MagiskPolicy] Remove all AUDITDENY rules after patching sepolicy to log all denies for debugging\n- [MagiskBoot] Properly support extra_cmdline in boot headers\n- [MagiskBoot] Try to repair broken v1 boot image headers\n- [MagiskBoot] Add new CPIO command: \"exists\"\n\n### v17.3 (2018.10.20)\n\n- [MagiskBoot] Support boot image header v1 (Pixel 3)\n- [MagiskSU] No more linked lists for caching `su_info`\n- [MagiskSU] Parse command-lines in client side and send only options to daemon\n- [MagiskSU] Early ACK to prevent client freezes and early denies\n- [Daemon] Prevent bootloops in situations where /data is mounted twice\n- [Daemon] Prevent logcat failures when /system/bin is magic mounting, could cause MagiskHide to fail\n- [Scripts] Switch hexpatch to remove Samsung Defex to a more general pattern\n- [Scripts] Update data encryption detection for better custom recovery support\n\n### v17.2 (2018.9.21)\n\n- [ResetProp] Update to AOSP upstream to support serialized system properties\n- [MagiskInit] Randomize Magisk service names to prevent detection (e.g. FGO)\n- [MagiskSU] New communication scheme to communicate with Magisk Manager\n\n### v17.0/17.1 (2018.9.1)\n\n- [General] Bring back install to inactive slot for OTAs on A/B devices\n- [Script] Remove system based root in addon.d\n- [Script] Add proper addon.d-v2 for preserving Magisk on custom ROMs on A/B devices\n- [Script] Enable KEEPVERITY when the device is using system_root_image\n- [Script] Add hexpatch to remove Samsung defex in new Oreo kernels\n- [Daemon] Support non ext4 filesystems for mirrors (system/vendor)\n- [MagiskSU] Make pts sockets always run in dev_pts secontext, providing all terminal emulator root shell the same power as adb shells\n- [MagiskHide] Kill all processes with same UID of the target to workaround OOS embryo optimization\n- [MagiskInit] Move all sepolicy patches pre-init to prevent Pixel 2 (XL) boot service breakdown\n\n### v16.7 (2018.7.19)\n\n- [Scripts] Fix boot image patching errors on Android P (workaround the strengthened seccomp)\n- [MagiskHide] Support hardlink based ns proc mnt (old kernel support)\n- [Daemon] Fix permission of /dev/null after logcat commands, fix ADB on EMUI\n- [Daemon] Log fatal errors only on debug builds\n- [MagiskInit] Detect early mount partname from fstab in device tree\n\n### v16.6 (2018.7.8)\n\n- [General] Add wrapper script to overcome weird `LD_XXX` flags set in apps\n- [General] Prevent bootloop when flashing Magisk after full wipe on FBE devices\n- [Scripts] Support patching DTB placed in extra sections in boot images (Samsung S9/S9+)\n- [Scripts] Add support for addon.d-v2 (untested)\n- [Scripts] Fix custom recovery console output in addon.d\n- [Scripts] Fallback to parsing sysfs for detecting block devices\n- [Daemon] Check whether a valid Magisk Manager is installed on boot, if not, install stub APK embedded in magiskinit\n- [Daemon] Check whether Magisk Manager is repackaged (hidden), and prevent malware from hijacking com.topjohnwu.magisk\n- [Daemon] Introduce new daemon: magisklogd, a dedicated daemon to handle all logcat related monitoring\n- [Daemon] Replace old invincible mode with handshake between magiskd and magisklogd, one will respawn the other if disconnected\n- [Daemon] Support GSI adbd bind mounting\n- [MagiskInit] Support detecting block names in upper case (Samsung)\n- [MagiskBoot] Check DTB headers to prevent false detections within kernel binary\n- [MagiskHide] Compare mount namespace with PPID to make sure the namespace is actually separated, fix root loss\n- [MagiskSU] Simplify `su_info` caching system, should use less resources and computing power\n- [MagiskSU] Reduce the amount of broadcasting to Magisk Manager\n- [ImgTool] Separate all ext4 image related operations to a new applet called \"imgtool\"\n- [ImgTool] Use precise free space calculation methods\n- [ImgTool] Use our own set of loop devices hidden along side with sbin tmpfs overlay. This not only eliminates another possible detection method, but also fixes apps that mount OBB files as loop devices (huge thanks to dev of Pzizz for reporting this issue)\n\n### v16.4 (2018.4.29)\n\n- [Daemon] Directly check logcat command instead of detecting logd, should fix logging and MagiskHide on several Samsung devices\n- [Daemon] Fix startup Magisk Manager APK installation on Android P\n- [MagiskPolicy] Switch from AOSP u:r:su:s0 to u:r:magisk:s0 to prevent conflicts\n- [MagiskPolicy] Remove unnecessary sepolicy rules to reduce security penalty\n- [Daemon] Massive re-design /sbin tmpfs overlay and daemon start up\n- [MagiskInit] Remove `magiskinit_daemon`, the actual magisk daemon (magiskd) shall handle everything itself\n- [Daemon] Remove post-fs stage as it is very limited and also will not work on A/B devices; replaced with simple mount in post-fs-data, which will run ASAP even before the daemon is started\n- [General] Remove all 64-bit binaries as there is no point in using them; all binaries are now 32-bit only.\n  Some weirdly implemented root apps might break (e.g. Tasker, already reported to the developer), but it is not my fault :)\n- [resetprop] Add Protobuf encode/decode to support manipulating persist properties on Android P\n- [MagiskHide] Include app sub-services as hiding targets. This might significantly increase the amount of apps that could be properly hidden\n\n### v16.3 (2018.3.28)\n\n- [General] Remove symlinks used for backwards compatibility\n- [MagiskBoot] Fix a small size calculation bug\n\n### v16.2 (2018.3.18)\n\n- [General] Force use system binaries in handling ext4 images (fix module installation on Android P)\n- [MagiskHide] Change property state to disable if logd is disabled\n\n### v16.1 (2018.3.11)\n\n- [MagiskBoot] Fix MTK boot image packaging\n- [MagiskBoot] Add more Nook/Acclaim headers support\n- [MagiskBoot] Support unpacking DTB with empty kernel image\n- [MagiskBoot] Update high compression mode detection logic\n- [Daemon] Support new mke2fs tool on Android P\n- [resetprop] Support Android P new property context files\n- [MagiskPolicy] Add new rules for Android P\n\n### v16.0 (2018.2.22)\n\n- [MagiskInit] Support non `skip_initramfs` devices with slot suffix (Huawei Treble)\n- [MagiskPolicy] Add rules for Magisk Manager\n- [Compiler] Workaround an NDK compiler bug that causes bootloops\n\n### v15.4 (2018.2.13)\n\n- [MagiskBoot] Support Samsung PXA, DHTB header images\n- [MagiskBoot] Support ASUS blob images\n- [MagiskBoot] Support Nook Green Loader images\n- [MagiskBoot] Support pure ramdisk images\n- [MagiskInit] Prevent OnePlus angela `sepolicy_debug` from loading\n- [MagiskInit] Obfuscate Magisk socket entry to prevent detection and security\n- [Daemon] Fix subfolders in /sbin shadowed by overlay\n- [Daemon] Obfuscate binary names to prevent naive detections\n- [Daemon] Check logd before force trying to start logcat in a loop\n\n### v15.3 (2018.1.12)\n\n- [Daemon] Fix the bug that only one script would be executed in post-fs-data.d/service.d\n- [Daemon] Add `MS_SILENT` flag when mounting, should fix some devices that cannot mount magisk.img\n- [MagiskBoot] Fix potential segmentation fault when patching ramdisk, should fix some installation failures\n\n### v15.2 (2018.1.1)\n\n- [MagiskBoot] Fix dtb verity patches, should fix dm-verity bootloops on newer devices placing fstabs in dtb\n- [MagiskPolicy] Add new rules for proper Samsung support, should fix MagiskHide\n- [MagiskInit] Support non `skip_initramfs` devices using split sepolicies (e.g. Zenfone 4 Oreo)\n- [Daemon] Use specific logcat buffers, some devices does not support all log buffers\n- [scripts] Update scripts to double check whether boot slot is available, some devices set a boot slot without A/B partitions\n\n### v15.1 (2017.12.29)\n\n- [MagiskBoot] Fix faulty code in ramdisk patches which causes bootloops in some config and fstab format combos\n\n### v15.0 (2017.12.26)\n\n- [Daemon] Fix the bug that Magisk cannot properly detect /data encryption state\n- [Daemon] Add merging `/cache/magisk.img` and `/data/adb/magisk_merge.img` support\n- [Daemon] Update to upstream libsepol to support cutting edge split policy custom ROM cil compilations\n\n### v14.6 (2017.12.22)\n\n- [General] Move all files into a safe location: /data/adb\n- [Daemon] New invincible implementation: use `magiskinit_daemon` to monitor sockets\n- [Daemon] Rewrite logcat monitor to be more efficient\n- [Daemon] Fix a bug where logcat monitor may spawn infinite logcat processes\n- [MagiskSU] Update su to work the same as proper Linux implementation:\n  Initialize window size; all environment variables will be migrated (except HOME, SHELL, USER, LOGNAME, these will be set accordingly),\n  \"--preserve-environment\" option will preserve all variables, including those four exceptions.\n  Check the Linux su manpage for more info\n- [MagiskBoot] Massive refactor, rewrite all cpio operations and CLI\n- [MagiskInit][magiskboot] Support ramdisk high compression mode\n\n### v14.5 (1456) (2017.11.23)\n\n- [Magiskinit] Fix bootloop issues on several devices\n- [misc] Build binaries with NDK r10e, should get rid of the nasty linker warning when executing magisk\n\n### v14.5 (1455) (2017.11.23)\n\n- [Daemon] Moved internal path to /sbin/.core, new image mountpoint is /sbin/.core/img\n- [MagiskSU] Support switching package name, used when Magisk Manager is hidden\n- [MagiskHide] Add temporary /magisk removal\n- [MagiskHide] All changes above contributes to hiding from nasty apps like FGO and several banking apps\n- [Magiskinit] Use magiskinit for all devices (dynamic initramfs)\n- [Magiskinit] Fix Xiaomi A1 support\n- [Magiskinit] Add Pixel 2 (XL) support\n- [Magiskboot] Add support to remove avb-verity in dtbo.img\n- [Magiskboot] Fix typo in handling MTK boot image headers\n- [script] Along with updates in Magisk Manager, add support to sign boot images (AVB 1.0)\n- [script] Add dtbo.img backup and restore support\n- [misc] Many small adjustments to properly support old platforms like Android 5.0\n\n### v14.3 (2017.10.15)\n\n- [MagiskBoot] Fix Pixel C installation\n- [MagiskBoot] Handle special `lz4_legacy` format properly, should fix all LG devices\n- [Daemon] New universal logcat monitor is added, support plug-and-play to worker threads\n- [Daemon] Invincible mode: daemon will be restarted by init, everything should seamlessly through daemon restarts\n- [Daemon] Add new restorecon action, will go through and fix all Magisk files with selinux unlabeled to `system_file` context\n- [Daemon] Add brute-force image resizing mode, should prevent the notorious Samsung crappy resize2fs from affecting the result\n- [resetprop] Add new \"-p\" flag, used to toggle whether alter/access the actual persist storage for persist props\n\n### v14.2 (2017.9.28)\n\n- [MagicMount] Clone attributes to tmpfs mountpoint, should fix massive module breakage\n\n### v14.1 (2017.9.28)\n\n- [MagiskInit] Introduce a new init binary to support `skip_initramfs` devices (Pixel family)\n- [script] Fix typo in update-binary for x86 devices\n- [script] Fix stock boot image backup not moved to proper location\n- [script] Add functions to support A/B slot and `skip_initramfs` devices\n- [script] Detect Meizu boot blocks\n- [MagiskBoot] Add decompress zImage support\n- [MagiskBoot] Support extracting dtb appended to zImage block\n- [MagiskBoot] Support patching fstab within dtb\n- [Daemon/MagiskSU] Proper file based encryption support\n- [Daemon] Create core folders if not exist\n- [resetprop] Fix a bug which delete props won't remove persist props not in memory\n- [MagicMount] Remove usage of dummy folder, directly mount tmpfs and construct file structure skeleton in place\n\n### v14.0 (2017.9.6)\n\n- [script] Simplify installation scripts\n- [script] Fix a bug causing backing up and restoring stock boot images failure\n- [script] Installation and uninstallation will migrate old or broken stock boot image backups to proper format\n- [script] Fix an issue with selabel setting in `util_functions.sh` on Lollipop\n- [rc script] Enable logd in post-fs to start logging as early as possible\n- [MagiskHide] magisk.img mounted is no longer a requirement\n  Devices with issues mounting magisk.img can now run in proper core-only mode\n- [MagiskBoot] Add native function to extract stock SHA1 from ramdisk\n- [b64xz] New tool to extract compressed and encoded binary dumps in shell script\n- [busybox] Add busybox to Magisk source, and embed multi-arch busybox binary into update-binary shell script\n- [busybox] Busybox is added into PATH for all boot scripts (post-fs-data.d, service.d, and all module scripts)\n- [MagiskSU] Fully fix multiuser issues\n- [Magic Mount] Fix a typo in cloning attributes\n- [Daemon] Fix the daemon crashing when boot scripts opens a subshell\n- [Daemon] Adjustments to prevent stock Samsung kernel restrictions on exec system calls for binaries started from /data\n- [Daemon] Workaround on Samsung device with weird fork behaviors\n\n### v13.3 (2017.7.18)\n\n- [MagiskHide] Update to bypass Google CTS (2017.7.17)\n- [resetprop] Properly support removing persist props\n- [uninstaller] Remove Magisk Manager and persist props\n\n### v13.2 (2017.7.14)\n\n- [magiskpolicy] Fix magiskpolicy segfault on old Android versions, should fix tons of older devices that couldn't use v13.1\n- [MagiskHide] Set proper selinux context while re-linking /sbin to hide Magisk, should potentially fix many issues\n- [MagiskBoot] Change lzma compression encoder flag from `LZMA_CHECK_CRC64` to `LZMA_CHECK_CRC32`, kernel only supports latter\n- [General] Core-only mode now properly mounts systemless hosts and magiskhide\n\n### v13.1 (2017.7.11)\n\n- [General] Merge MagiskSU, magiskhide, resetprop, magiskpolicy into one binary\n- [General] Add Android O support (tested on DP3)\n- [General] Dynamic link libselinux.so, libsqlite.so from system to greatly reduce binary size\n- [General] Remove bundled busybox because it causes a lot of issues\n- [General] Unlock all block devices for read-write support instead of emmc only (just figured not all devices uses emmc lol)\n- [Scripts] Run all ext4 image operations through magisk binary in flash scripts\n- [Scripts] Updated scripts to use magisk native commands to increase compatibility\n- [Scripts] Add addon.d survival support\n- [Scripts] Introduce `util_functions.sh`, used as a global shell script function source for all kinds of installation\n- [MagiskBoot] Moved boot patch logic into magiskboot binary\n- [MagiskSU] Does not fork new process for each request, add new threads instead\n- [MagiskSU] Added multiuser support\n- [MagiskSU] Introduce new timeout queue mechanism, prevent performance hit with poorly written su apps\n- [MagiskSU] Multiple settings moved from prop detection to database\n- [MagiskSU] Add namespace mode option support\n- [MagiskSU] Add master-mount option\n- [resetprop] Updated to latest AOSP upstream, support props from 5.0 to Android O\n- [resetprop] Renamed all functions to prevent calling functions from external libc\n- [magiskpolicy] Updated libsepol from official SELinux repo\n- [magiskpolicy] Added xperm patching support (in order to make Android O work properly)\n- [magiskpolicy] Updated rules for Android O, and Liveboot support\n- [MagiskHide] Remove pseudo permissive mode, directly hide permissive status instead\n- [MagiskHide] Remove unreliable list file monitor, change to daemon request mode\n- [MagiskHide] MagiskHide is now enabled by default\n- [MagiskHide] Update unmount policies, passes CTS in SafetyNet!\n- [MagiskHide] Add more props for hiding\n- [MagiskHide] Remove background magiskhide daemon, spawn short life process for unmounting purpose\n- [Magic Mount] Ditched shell script based mounting, use proper C program to parse and mount files. Speed is SIGNIFICANTLY improved\n\n### v12.0 (2017.3.31)\n\n- [General] Move most binaries into magisk.img (Samsung cannot run su daemon in /data)\n- [General] Move sepolicy live patch to `late_start` service\n  This shall fix the long boot times, especially on Samsung devices\n- [General] Add Samsung RKP hexpatch back, should now work on Samsung stock kernels\n- [General] Fix installation with SuperSU\n- [MagiskHide] Support other logcat `am_proc_start` patterns\n- [MagiskHide] Change /sys/fs/selinux/enforce(policy) permissions if required\n  Samsung devices cannot switch selinux states, if running on permissive custom kernel, the users will stuck at permissive\n  If this scenario is detected, change permissions to hide the permissive state, leads to SafetyNet passes\n- [MagiskHide] Add built in prop rules to fake KNOX status\n  Samsung apps requiring KNOX status to be 0x0 should now work (Samsung Pay not tested)\n- [MagiskHide] Remove all ro.build props, since they cause more issues than they benefit...\n- [MagiskBoot] Add lz4 legacy format support (most linux kernel using lz4 for compression is using this)\n- [MagiskBoot] Fix MTK kernels with MTK headers\n\n### v11.5/11.6 (2017.3.21)\n\n- [Magic Mount] Fix mounting issues with devices that have separate /vendor partitions\n- [MagiskBoot] Whole new boot image patching tool, please check release note for more info\n- [magiskpolicy] Rename sepolicy-inject to magiskpolicy\n- [magiskpolicy] Update a rule to allow chcon everything properly\n- [MagiskHide] Prevent multirom crashes\n- [MagiskHide] Add patches for ro.debuggable, ro.secure, ro.build.type, ro.build.tags, ro.build.selinux\n- [MagiskHide] Change /sys/fs/selinux/enforce, /sys/fs/selinux/policy permissions for Samsung compatibility\n- [MagiskSU] Fix read-only partition mounting issues\n- [MagiskSU] Disable -cn option, the option will do nothing, preserved for compatibility\n\n### v11.1 (2017.2.6)\n\n- [sepolicy-inject] Add missing messages\n- [magiskhide] Start MagiskHide with scripts\n\n### v11.0 (2017.2.6)\n\n- [Magic Mount] Support replacing symlinks.\n  Symlinks cannot be a target of a bind mounted, so they are treated the same as new files\n- [Magic Mount] Fix the issue when file/folder name contains spaces\n- [BusyBox] Updated to v1.26.2. Should fix the black screen issues of FlashFire\n- [resetprop] Support reading prop files that contains spaces in prop values\n- [MagiskSU] Adapt communication to Magisk Manager; stripped out unused data transfer\n- [MagiskSU] Implement SuperUser access option (Disable, APP only, ADB Only, APP & ADB)\n  phh Superuser app has this option but the feature isn't implemented within the su binary\n- [MagiskSU] Fixed all issues with su -c \"commands\" (run commands with root)\n  This feature is supposed to only allow one single option, but apparently adb shell su -c \"command\" doesn't work this way, and plenty of root apps don't follow the rule. The su binary will now consider everything after -c as a part of the command.\n- [MagiskSU] Removed legacy context hack for TiBack, what it currently does is slowing down the invocation\n- [MagiskSU] Preserve the current working directory after invoking su\n  Previously phh superuser will change the path to /data/data after obtaining root shell. It will now stay in the same directory where you called su\n- [MagiskSU] Daemon now also runs in u:r:su:s0 context\n- [MagiskSU] Removed an unnecessary fork, reduce running processes and speed up the invocation\n- [MagiskSU] Add -cn option to the binary\n  Not sure if this is still relevant, and also not sure if implemented correctly, but hey it's here\n- [sepolicy-inject] Complete re-write the command-line options, now nearly matches supolicy syntax\n- [sepolicy-inject] Support all matching mode for nearly every action (makes pseudo enforced possible)\n- [sepolicy-inject] Fixed an ancient bug that allocated memory isn't reset\n- [uninstaller] Now works as a independent script that can be executed at boot\n  Fully support recovery with no /data access, Magisk uninstallation with Magisk Manager\n- [Addition] Busybox, MagiskHide, hosts settings can now be applied instantly; no reboots required\n- [Addition] Add post-fs-data.d and service.d\n- [Addition] Add option to disable Magisk (MagiskSU will still be started)\n\n### v10.2 (2017.1.2)\n\n- [Magic Mount] Remove apps/priv-app from whitelist, should fix all crashes\n- [phh] Fix binary out-of-date issue\n- [scripts] Fix root disappear issue when upgrading within Magisk Manager\n\n### v10 (2017.1.2)\n\n- [Magic Mount] Use a new way to mount system (vendor) mirrors\n- [Magic Mount] Use universal way to deal with /vendor, handle both separate partition or not\n- [Magic Mount] Adding **anything to any place** is now officially supported (including /system root and /vendor root)\n- [Magic Mount] Use symlinks for mirroring back if possible, reduce bind mounts for adding files\n- [Magisk Hide] Check init namespace, zygote namespace to prevent Magic Mount breakage (a.k.a root loss)\n- [Magisk Hide] Send SIGSTOP to pause target process ASAP to prevent crashing if unmounting too late\n- [Magisk Hide] Hiding should work under any conditions, including adding libs and /system root etc.\n- [phh] Root the device if no proper root detected\n- [phh] Move `/sbin` to `/sbin_orig` and link back, fix Samsung no-suid issue\n- [scripts] Improve SuperSU integration, now uses sukernel to patch ramdisk, support SuperSU built in ramdisk restore\n- [template] Add PROPFILE option to load system.prop\n\n### v9 (2016.11.14)\n\n- **[API Change] Remove the interface for post-fs modules**\n- [resetprop] New tool \"resetprop\" is added to Magisk to replace most post-fs modules' functionality\n- [resetprop] Magisk will now patch \"ro.boot.verifiedbootstate\", \"ro.boot.flash.locked\", \"ro.boot.veritymode\" to bypass Safety Net\n- [Magic Mount] Move dummy skeleton / mirror / mountinfo filesystem tree to tmpfs\n- [Magic Mount] Rewritten dummy cloning mechanism from scratch, will result in minimal bind mounts, minimal file traversal, eliminate all possible issues that might happen in extreme cases\n- [Magic Mount] Adding new items to /system/bin, /system/vendor, /system/lib(64) is properly supported (devices with separate vendor partition is not supported yet)\n- [Magisk Hide] Rewritten from scratch, now run in daemon mode, proper list monitoring, proper mount detection, and maybe more.....\n- [Boot Image] Add support for Motorola boot image dtb, it shall now unpack correctly\n- [Uninstaller] Add removal of SuperSU custom patch script\n\n### v8 (2016.10.19)\n\n- Add Magisk Hide to bypass SafetyNet\n- Improve SuperSU integration: no longer changes the SuperSU PATH\n- Support rc script entry points not located in init.rc\n\n### v7 (2016.10.04)\n\n- Fully open source\n- Remove supolicy dependency, use my own sepolicy-injection\n- Run everything in its own selinux domain, should fix all selinux issues\n- Add Note 7 stock kernel hex patches\n- Add support to install Magisk in Magisk Manager\n- Add support for image merging for module flashing in Magisk Manager\n- Add root helpers for SuperSU auto module-ize and auto upgrading legacy phh superuser\n- New paths to toggle busybox, and support all root solutions\n- Remove root management API; both SuperSU and phh has their own superior solutions\n\n### [v6 (2016.8.21)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68298121)\n\n- Fixed the algorithm for adding new files and dummy system\n- Updated the module template with a default permission, since people tend to forget them :)\n\n### [v5 (2016.8.20)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68274534)\n\n- Hotfix for older Android versions (detect policy before patching)\n- Update uninstaller to NOT uninstall Magisk Manager, since it cause problems\n\n### [v4 (2016.8.19)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68269300)\n\n- Important: Uninstall v1 - v3 Magisk before upgrading with the uninstaller in the OP!!\n- Massive Rewrite Magisk Interface API! All previous mods are NOT compatible! Please download the latest version of the mods you use (root/xposed)\n- Mods are now installed independently in their own subfolder. This paves the way for future Magisk Manager versions to manage mods, **just like how Xposed Modules are handled**\n- Support small boot partition devices (Huawei devices)\n- Use minimal sepolicy patch in boot image for smaller ramdisk size. Live patch policies after bootup\n- Include updated open source sepolicy injection tool (source code available), support nearly all SuperSU supolicy tool's functionality\n\n### [v3 (2016.8.11)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68146978)\n\n- Fix bootimg-extract for Exynos Samsung devices (thanks to @phhusson), should fix all Samsung device issues\n- Add supolicy back to patch sepolicy (stock Samsung do not accept permissive domain)\n- Update sepolicy-injection to patch su domain for Samsung devices to use phh's root\n- Update root disable method, using more aggressive approach\n- Use lazy unmount to unmount root from system, should fix some issues with custom roms\n- Use the highest possible compression rate for ramdisk, hope to fix some devices with no boot partition space\n- Detect boot partition space insufficient, will abort installer instead of breaking your device\n\n### [v2 (2016.8.9)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68108058)\n\n- Fix verity patch. It should now work on all devices (might fix some of the unable-to-boot issues)\n- All scripts will now run in selinux permissive mode for maximum compatibility (this will **NOT** turn your device to permissive)\n- Add Nougat Developer Preview 5 support\n- Add systemless host support for AdBlock Apps (enabled by default)\n- Add support for new root disable method\n- Remove sepolicy patches that uses SuperSU's supolicy tool; it is now using a minimal set of modifications\n- Removed Magisk Manager in Magisk patch, it is now included in Magisk phh's superuser only\n\n### [v1 (2016.8.3)](https://xdaforums.com/t/magisk-general-support-discussion.3432382/post-68034103)\n\n- Initial release\n"
  },
  {
    "path": "docs/details.md",
    "content": "# Internal Details\n\n## File Structure\n\n### Paths in \"Magisk tmpfs directory\"\n\nMagisk will mount a `tmpfs` directory to store some temporary data. For devices with the `/sbin` folder, it will be chosen as it will also act as an overlay to inject binaries into `PATH`. From Android 11 onwards, the `/sbin` folder might not exist, so Magisk will use `/debug_ramdisk` as the base folder.\n\n```\n# In order to get the current base folder Magisk is using,\n# use the command `magisk --path`.\n# Binaries like magisk, magiskinit, and all symlinks to\n# applets are directly stored in this path. This means when\n# this is /sbin, these binaries will be directly in PATH.\nMAGISKTMP=$(magisk --path)\n\n# Magisk internal stuffs\nINTERNALDIR=$MAGISKTMP/.magisk\n\n# /data/adb/modules will be bind mounted here.\n# The original folder is not used due to nosuid mount flag.\n$INTERNALDIR/modules\n\n# The current Magisk installation config\n$INTERNALDIR/config\n\n# Partition mirrors\n# Each directory in this path will be mounted with the\n# partition of its directory name.\n# e.g. system, system_ext, vendor, data ...\n$INTERNALDIR/mirror\n\n# Root directory patch files\n# On system-as-root devices, / is not writable.\n# All pre-init patched files are stored here and bind mounted.\n$INTERNALDIR/rootdir\n```\n\n### Paths in `/data`\n\nSome binaries and files should be stored on non-volatile storages in `/data`. In order to prevent detection, everything has to be stored somewhere safe and undetectable in `/data`. The folder `/data/adb` was chosen because of the following advantages:\n\n- It is an existing folder on modern Android, so it cannot be used as an indication of the existence of Magisk.\n- The permission of the folder is by default `700`, owner as `root`, so non-root processes are unable to enter, read, write the folder in any possible way.\n- The folder is labeled with secontext `u:object_r:adb_data_file:s0`, and very few processes have the permission to do any interaction with that secontext.\n- The folder is located in _Device encrypted storage_, so it is accessible as soon as data is properly mounted in FBE (File-Based Encryption) devices.\n\n```\nSECURE_DIR=/data/adb\n\n# Folder storing general post-fs-data scripts\n$SECURE_DIR/post-fs-data.d\n\n# Folder storing general late_start service scripts\n$SECURE_DIR/service.d\n\n# Magisk modules\n$SECURE_DIR/modules\n\n# Magisk modules that are pending for upgrade\n# Module files are not safe to be modified when mounted\n# Modules installed through the Magisk app will be stored here\n# and will be merged into $SECURE_DIR/modules in the next reboot\n$SECURE_DIR/modules_update\n\n# Database storing settings and root permissions\nMAGISKDB=$SECURE_DIR/magisk.db\n\n# All magisk related binaries, including busybox,\n# scripts, and magisk binaries. Used in supporting\n# module installation, addon.d, the Magisk app etc.\nDATABIN=$SECURE_DIR/magisk\n\n```\n\n## Magisk Booting Process\n\n### Pre-Init\n\n`magiskinit` will replace `init` as the first program to run.\n\n- Early mount required partitions. On legacy system-as-root devices, we switch root to system; on 2SI devices, we patch the original `init` to redirect the 2nd stage init file to magiskinit and execute it to mount partitions for us.\n- Inject magisk services into `init.rc`\n- On devices using monolithic policy, load sepolicy from `/sepolicy`; otherwise we hijack nodes in selinuxfs with FIFO, set `LD_PRELOAD` to hook `security_load_policy` and assist hijacking on 2SI devices, and start a daemon to wait until init tries to load sepolicy.\n- Patch sepolicy rules. If we are using \"hijack\" method, load patched sepolicy into kernel, unblock init and exit daemon\n- Execute the original `init` to continue the boot process\n\n### post-fs-data\n\nThis triggers on `post-fs-data` when `/data` is decrypted and mounted. The daemon `magiskd` will be launched, post-fs-data scripts are executed, and module files are magic mounted.\n\n### late_start\n\nLater in the booting process, the class `late_start` will be triggered, and Magisk \"service\" mode will be started. In this mode, service scripts are executed.\n\n## Resetprop\n\nUsually, system properties are designed to only be updated by `init` and read-only to non-root processes. With root you can change properties by sending requests to `property_service` (hosted by `init`) using commands such as `setprop`, but changing read-only props (props that start with `ro.` like `ro.build.product`) and deleting properties are still prohibited.\n\n`resetprop` is implemented by distilling out the source code related to system properties from AOSP and patched to allow direct modification to property area, or `prop_area`, bypassing the need to go through `property_service`. Since we are bypassing `property_service`, there are a few caveats:\n\n- `on property:foo=bar` actions registered in `*.rc` scripts will not be triggered if property changes does not go through `property_service`. The default set property behavior of `resetprop` matches `setprop`, which **WILL** trigger events (implemented by first deleting the property then set it via `property_service`). There is a flag `-n` to disable it if you need this special behavior.\n- persist properties (props that starts with `persist.`, like `persist.sys.usb.config`) are stored in both `prop_area` and `/data/property`. By default, deleting props will **NOT** remove it from persistent storage, meaning the property will be restored after the next reboot; reading props will **NOT** read from persistent storage, as this is the behavior of `getprop`. With the flag `-p`, deleting props will remove the prop in **BOTH** `prop_area` and `/data/property`, and reading props will be read from **BOTH** `prop_area` and persistent storage.\n\n## SELinux Policies\n\nMagisk will patch the stock `sepolicy` to make sure root and Magisk operations can be done in a safe and secure way. The new domain `magisk` is effectively permissive, which is what `magiskd` and all root shell will run in. `magisk_file` is a new file type that is setup to be allowed to be accessed by every domain (unrestricted file context).\n\nBefore Android 8.0, all allowed su client domains are allowed to directly connect to `magiskd` and establish connection with the daemon to get a remote root shell. Magisk also have to relax some `ioctl` operations so root shells can function properly.\n\nAfter Android 8.0, to reduce relaxation of rules in Android's sandbox, a new SELinux model is deployed. The `magisk` binary is labelled with `magisk_exec` file type, and processes running as allowed su client domains executing the `magisk` binary (this includes the `su` command) will transit to `magisk_client` by using a `type_transition` rule. Rules strictly restrict that only `magisk` domain processes are allowed to attribute files to `magisk_exec`. Direct connection to sockets of `magiskd` are not allowed; the only way to access the daemon is through a `magisk_client` process. These changes allow us to keep the sandbox intact, and keep Magisk specific rules separated from the rest of the policies.\n\nThe full set of rules can be found in `sepolicy/rules.cpp`.\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# Frequently Asked Questions\n\n### Q: I installed a module and it bootlooped my device. Help!\n\nIf you have USB debugging enabled in developer options, connect your phone to the PC. If your device is detected (check by `adb devices`), enter ADB shell and run the command `magisk --remove-modules`. This will remove all your modules and automatically reboot the device.\n\nIf unfortunately you do not have USB debugging enabled you can boot using the Safe Mode key combo to cause Magisk to create an empty file named 'disable' in modules directories which disables modules when next booted with Magisk. Most modern Android devices support such a special key combo at boot to enter system Safe Mode as an emergency option, but **please note** that Magisk's key combo detection occurs _earlier_ than system detection so the key combo timing indicated by many online guides may need to be altered to activate Magisk's Safe Mode. (It's possible to activate system Safe Mode but not Magisk Safe Mode and vice versa.)\n\nThe following details should ensure that modules are properly disabled:\n\n1. Many online guides for entering Safe Mode say 'When the animated logo appears, press and hold the volume down button until the system boots' or similar. This may actually be _too late_ for Magisk detection however and result in activating system Safe Mode but modules are not disabled.\n2. By pressing the volume down button some seconds before the animation and releasing it as soon as the boot animation appears, Magisk's Safe Mode should be activated without activating system Safe Mode (thus avoiding disabling other device and app settings) and the device should then simply boot to normal system with modules disabled.\n3. By pressing the volume down button some seconds before the animation and holding it until the system boots, both Magisk's Safe Mode and system Safe Mode should be activated. Next, after booting back to normal system, modules will be disabled.\n\n### Q: Why is X app detecting root?\n\nMagisk no longer handles root hiding. There are plenty of Magisk/Zygisk modules available that specifically provide these functionalities, please search around 😉\n\n### Q: Magisk App shows Magisk Installed = N/A after an update but magisk su is still working.\n\nIf upgrading with App hidden (ie. you took the 'Hide the Magisk app' option), the stub app (for hiding Magisk) may remain while a full Magisk app is also installed. This creates a conflict and the full app fails to see or access root... Uninstalling and reinstalling the full app can fix this, but not if a hidden app (stub) still exists.\n\nThe solution is to check for a hidden stub app and remove it. It may not show up normally in your launcher homescreen any longer, but should be visible from general settings, Apps. The hidden app will be named 'Settings' (default) or whatever you named it during the hiding process. Note that it is possible to have multiple obfuscated apps present. Uninstall any iterations of the hidden app you find and try opening the full app again. If necessary, uninstall it and reinstall the full app matching the binaries installed. Typing magisk -c in a terminal emulator app will show the version and version code for Magisk binaries installed (despite Installed = N/A showing).\n\nAdditionally, if a 'second space', eg. Workspace, Parallel Space etc, or another sandboxed environment, eg. a Multiple User additional profile, Island app or similar, is set up, check that no iterations of Magisk (either hidden or full apps) are running within these environments.\n\n### Q: After I take the 'Hide the Magisk app' option the app icon is broken.\n\nWhen hiding the Magisk app, it will install a \"stub\" APK that has nothing in it. The only functionality this stub app has is to download the full Magisk app APK data into its internal storage and dynamically load it. Due to the fact that the stub APK is literally empty, it does not contain the image resource for the app icon.\n\nWhen you open the hidden Magisk app, it will offer you the option to create a shortcut in the homescreen (which has both the correct app name and icon) for your convenience. You can also manually ask the app to create the icon in app settings.\n\n### Q: How to use Magisk in the emulator?\n\nWith the emulator running and accessible via ADB, run `./build.py emulator <path to Magisk APK>` to temporarily install Magisk on to the emulator. The patch is not persistent, meaning Magisk will be lost after a reboot, so re-execute the script to emulate a reboot if required.\n\nThe script is only tested on the official Android Virtual Device (AVD) shipped alongside Android Studio; other emulators may work, but the emulator must have SELinux enabled.\n"
  },
  {
    "path": "docs/guides.md",
    "content": "# Developer Guides\n\n## BusyBox\n\nMagisk ships with a feature complete BusyBox binary (including full SELinux support). The executable is located at `/data/adb/magisk/busybox`. Magisk's BusyBox supports runtime toggle-able \"ASH Standalone Shell Mode\". What this standalone mode means is that when running in the `ash` shell of BusyBox, every single command will directly use the applet within BusyBox, regardless of what is set as `PATH`. For example, commands like `ls`, `rm`, `chmod` will **NOT** use what is in `PATH` (in the case of Android by default it will be `/system/bin/ls`, `/system/bin/rm`, and `/system/bin/chmod` respectively), but will instead directly call internal BusyBox applets. This makes sure that scripts always run in a predictable environment and always have the full suite of commands no matter which Android version it is running on. To force a command _not_ to use BusyBox, you have to call the executable with full paths.\n\nEvery single shell script running in the context of Magisk will be executed in BusyBox's `ash` shell with standalone mode enabled. For what is relevant to 3rd party developers, this includes all boot scripts and module installation scripts.\n\nFor those who want to use this \"Standalone Mode\" feature outside of Magisk, there are 2 ways to enable it:\n\n1. Set environment variable `ASH_STANDALONE` to `1`<br>Example: `ASH_STANDALONE=1 /data/adb/magisk/busybox sh <script>`\n2. Toggle with command-line options:<br>`/data/adb/magisk/busybox sh -o standalone <script>`\n\nTo make sure all subsequent `sh` shell executed also runs in standalone mode, option 1 is the preferred method (and this is what Magisk and the Magisk app internally use) as environment variables are inherited down to child processes.\n\n## Magisk Modules\n\nA Magisk module is a folder placed in `/data/adb/modules` with the structure below:\n\n```\n/data/adb/modules\n├── .\n├── .\n|\n├── $MODID                  <--- The folder is named with the ID of the module\n│   │\n│   │      *** Module Identity ***\n│   │\n│   ├── module.prop         <--- This file stores the metadata of the module\n│   │\n│   │      *** Main Contents ***\n│   │\n│   ├── system              <--- This folder will be mounted if skip_mount does not exist\n│   │   ├── ...\n│   │   ├── ...\n│   │   └── ...\n│   │\n│   ├── zygisk              <--- This folder contains the module's Zygisk native libraries\n│   │   ├── arm64-v8a.so\n│   │   ├── armeabi-v7a.so\n│   │   ├── riscv64.so\n│   │   ├── x86.so\n│   │   ├── x86_64.so\n│   │   └── unloaded        <--- If exists, the native libraries are incompatible\n│   │\n│   │      *** Status Flags ***\n│   │\n│   ├── skip_mount          <--- If exists, Magisk will NOT mount your system folder\n│   ├── disable             <--- If exists, the module will be disabled\n│   ├── remove              <--- If exists, the module will be removed next reboot\n│   │\n│   │      *** Optional Files ***\n│   │\n│   ├── post-fs-data.sh     <--- This script will be executed in post-fs-data\n│   ├── service.sh          <--- This script will be executed in late_start service\n|   ├── uninstall.sh        <--- This script will be executed when Magisk removes your module\n|   ├── action.sh           <--- This script will be executed when user click the action button in Magisk app\n│   ├── system.prop         <--- Properties in this file will be loaded as system properties by resetprop\n│   ├── sepolicy.rule       <--- Additional custom sepolicy rules\n│   │\n│   │      *** Auto Generated, DO NOT MANUALLY CREATE OR MODIFY ***\n│   │\n│   ├── vendor              <--- A symlink to $MODID/system/vendor\n│   ├── product             <--- A symlink to $MODID/system/product\n│   ├── system_ext          <--- A symlink to $MODID/system/system_ext\n│   │\n│   │      *** Any additional files / folders are allowed ***\n│   │\n│   ├── ...\n│   └── ...\n|\n├── another_module\n│   ├── .\n│   └── .\n├── .\n├── .\n```\n\n#### module.prop\n\nThis is the **strict** format of `module.prop`\n\n```\nid=<string>\nname=<string>\nversion=<string>\nversionCode=<int>\nauthor=<string>\ndescription=<string>\nupdateJson=<url> (optional)\n```\n\n- `id` has to match this regular expression: `^[a-zA-Z][a-zA-Z0-9._-]+$`<br>\n  ex: ✓ `a_module`, ✓ `a.module`, ✓ `module-101`, ✗ `a module`, ✗ `1_module`, ✗ `-a-module`<br>\n  This is the **unique identifier** of your module. You should not change it once published.\n- `versionCode` has to be an **integer**. This is used to compare versions\n- `updateJson` should point to a URL that downloads a JSON to provide info so the Magisk app can update the module.\n- Others that weren't mentioned above can be any **single line** string.\n- Make sure to use the `UNIX (LF)` line break type and not the `Windows (CR+LF)` or `Macintosh (CR)`.\n\nUpdate JSON format:\n\n```\n{\n    \"version\": string,\n    \"versionCode\": int,\n    \"zipUrl\": url,\n    \"changelog\": url\n}\n```\n\n#### Shell scripts (`*.sh`)\n\nPlease read the [Boot Scripts](#boot-scripts) section to understand the difference between `post-fs-data.sh` and `service.sh`. For most module developers, `service.sh` should be good enough if you just need to run a boot script. If you need to wait for boot completed, you can use `resetprop -w sys.boot_completed 0`.\n\nIn all scripts of your module, please use `MODDIR=${0%/*}` to get your module's base directory path; do **NOT** hardcode your module path in scripts.\nIf Zygisk is enabled, the environment variable `ZYGISK_ENABLED` will be set to `1`.\n\n#### The `system` folder\n\nAll files you want to replace/inject should be placed in this folder. This folder will be recursively merged into the real `/system`; that is: existing files in the real `/system` will be replaced by the one in the module's `system`, and new files in the module's `system` will be added to the real `/system`.\n\nIf you place a file named `.replace` in any of the folders, instead of merging its contents, that folder will directly replace the one in the real system. This can be very handy for swapping out an entire folder.\n\nIf you want to replace files in `/vendor`, `/product`, or `/system_ext`, please place them under `system/vendor`, `system/product`, and `system/system_ext` respectively. Magisk will transparently handle whether these partitions are in a separate partition or not.\n\nIf you want to remove a specific file or folder, please place a dummy character device with major number 0 and minor number 0 in the same path. For example, if you want to remove `/system/app/GoogleCamera`, you can `mknod GoogleCamera c 0 0` in `$MODDIR/system/app`.\n\n#### Zygisk\n\nZygisk is a feature of Magisk that allows advanced module developers to run code directly in every Android applications' processes before they are specialized and running. For more details about the Zygisk API and building a Zygisk module, please checkout the [Zygisk Module Sample](https://github.com/topjohnwu/zygisk-module-sample) project.\n\n#### system.prop\n\nThis file follows the same format as `build.prop`. Each line comprises of `[key]=[value]`.\n\n#### sepolicy.rule\n\nIf your module requires some additional sepolicy patches, please add those rules into this file. Each line in this file will be treated as a policy statement. For more details about how a policy statement is formatted, please check [magiskpolicy](tools.md#magiskpolicy)'s documentation.\n\n## Magisk Module Installer\n\nA Magisk module installer is a Magisk module packaged in a zip file that can be flashed in the Magisk app or custom recoveries such as TWRP. The simplest Magisk module installer is just a Magisk module packed as a zip file, in addition to the following files only if the module supports flashing in recovery:\n\n- `update-binary`: Download the latest [module_installer.sh](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh) and rename/copy that script as `update-binary`\n- `updater-script`: This file should only contain the string `#MAGISK`\n\nThe module installer script will setup the environment, extract the module files from the zip file to the correct location, then finalizes the installation process, which should be good enough for most simple Magisk modules.\n\n```\nmodule.zip\n│\n├── META-INF                           <--- Only needed for flashing in recovery\n│   └── com\n│       └── google\n│           └── android\n│               ├── update-binary      <--- The module_installer.sh you downloaded\n│               └── updater-script     <--- Should only contain the string \"#MAGISK\"\n│\n├── customize.sh                       <--- (Optional, more details later)\n│                                           This script will be sourced by update-binary\n├── ...\n├── ...  /* The rest of module's files */\n│\n```\n\n#### Customization\n\nIf you need to customize the module installation process, optionally you can create a script in the installer named `customize.sh`. This script will be _sourced_ (not executed!) by the module installer script after all files are extracted and default permissions and secontext are applied. This is very useful if your module require additional setup based on the device ABI, or you need to set special permissions/secontext for some of your module files.\n\nIf you would like to fully control and customize the installation process, declare `SKIPUNZIP=1` in `customize.sh` to skip all default installation steps. By doing so, your `customize.sh` will be responsible to install everything by itself.\n\nThe `customize.sh` script runs in Magisk's BusyBox `ash` shell with \"Standalone Mode\" enabled. The following variables and functions are available:\n\n##### Variables\n\n- `MAGISK_VER` (string): the version string of current installed Magisk (e.g. `v20.0`)\n- `MAGISK_VER_CODE` (int): the version code of current installed Magisk (e.g. `20000`)\n- `BOOTMODE` (bool): `true` if the module is being installed in the Magisk app\n- `MODPATH` (path): the path where your module files should be installed\n- `TMPDIR` (path): a place where you can temporarily store files\n- `ZIPFILE` (path): your module's installation zip\n- `ARCH` (string): the CPU architecture of the device. Value is either `arm`, `arm64`, `x86`, `x64`, or `riscv64`\n- `IS64BIT` (bool): `true` if `$ARCH` is either `arm64`, `x64`, or `riscv64`\n- `API` (int): the API level (Android version) of the device (e.g. `23` for Android 6.0)\n\n##### Functions\n\n```\nui_print <msg>\n    Print <msg> to console\n    Avoid using 'echo' as it will not display in custom recovery's console\n\nabort <msg>\n    Print error message <msg> to console and terminate the installation\n    Avoid using 'exit' as it will skip the termination cleanup steps\n\nset_perm <target> <owner> <group> <permission> [context]\n    If [context] is not specified, the default is \"u:object_r:system_file:s0\"\n    This function is a shorthand for the following commands:\n       chown owner.group target\n       chmod permission target\n       chcon context target\n\nset_perm_recursive <directory> <owner> <group> <dirpermission> <filepermission> [context]\n    If [context] is not specified, the default is \"u:object_r:system_file:s0\"\n    This function is a shorthand for the following psuedo code:\n      set_perm <directory> owner group dirpermission context\n      for file in <directory>:\n        set_perm file owner group filepermission context\n      for dir in <directory>:\n        set_perm_recursive dir owner group dirpermission context\n```\n\nFor convenience, you can also declare a list of folders you want to replace in the variable name `REPLACE`. The module installer script will create the `.replace` file into the folders listed in `REPLACE`. For example:\n\n```sh\nREPLACE=\"\n/system/app/YouTube\n/system/app/Bloatware\n\"\n```\n\nThe list above will result in the following files being created: `$MODPATH/system/app/YouTube/.replace` and `$MODPATH/system/app/Bloatware/.replace`.\n\nFor convenience, you can also declare a list of files/folders you want to remove in the variable name `REMOVE`. The module installer script will create the corresponding dummy devices. For example:\n\n```sh\nREMOVE=\"\n/system/app/YouTube\n/system/fonts/Roboto.ttf\n\"\n```\n\nThe list above will result in the following dummy devices being created: `$MODPATH/system/app/YouTube` and `$MODPATH/system/fonts/Roboto.ttf`.\n\n#### Notes\n\n- When your module is downloaded with the Magisk app, `update-binary` will be **forcefully** replaced with the latest [`module_installer.sh`](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). **DO NOT** try to add any custom logic in `update-binary`.\n- Due to historical reasons, **DO NOT** add a file named `install.sh` in your module installer zip.\n- **DO NOT** call `exit` at the end of `customize.sh`. The module installer script has to perform some cleanups before exiting.\n\n## Boot Scripts\n\nIn Magisk, you can run boot scripts in 2 different modes: **post-fs-data** and **late_start service** mode.\n\n- post-fs-data mode\n  - This stage is BLOCKING. The boot process is paused before execution is done, or 40 seconds have passed.\n  - Scripts run before any modules are mounted. This allows a module developer to dynamically adjust their modules before it gets mounted.\n  - This stage happens before Zygote is started, which pretty much means everything in Android\n  - **WARNING:** using `setprop` will deadlock the boot process! Please use `resetprop -n <prop_name> <prop_value>` instead.\n  - **Only run scripts in this mode if necessary.**\n- late_start service mode\n  - This stage is NON-BLOCKING. Your script runs in parallel with the rest of the booting process.\n  - **This is the recommended stage to run most scripts.**\n\nIn Magisk, there are also 2 kinds of scripts: **general scripts** and **module scripts**.\n\n- General Scripts\n  - Placed in `/data/adb/post-fs-data.d` or `/data/adb/service.d`\n  - Only executed if the script is set as executable (`chmod +x script.sh`)\n  - Scripts in `post-fs-data.d` runs in post-fs-data mode, and scripts in `service.d` runs in late_start service mode.\n  - Modules should **NOT** add general scripts during installation\n- Module Scripts\n  - Placed in the module's own folder\n  - Only executed if the module is enabled\n  - `post-fs-data.sh` runs in post-fs-data mode, and `service.sh` runs in late_start service mode.\n\nAll boot scripts will run in Magisk's BusyBox `ash` shell with \"Standalone Mode\" enabled.\n\n## Root Directory Overlay System\n\nSince `/` is read-only on system-as-root devices, Magisk provides an overlay system to enable developers to replace files in rootdir or add new `*.rc` scripts. This feature is designed mostly for custom kernel developers.\n\nOverlay files shall be placed in the `overlay.d` folder in boot image ramdisk, and they follow these rules:\n\n1. Each `*.rc` file (except for `init.rc`) in `overlay.d` will be read and concatenated **AFTER** `init.rc` if it does not exist in the root directory, otherwise it will **REPLACE** the existing one.\n2. Existing files can be replaced by files located at the same relative path\n3. Files that correspond to a non-existing file will be ignored\n\nTo add additional files which you can refer to in your custom `*.rc` scripts, add them into `overlay.d/sbin`. The 3 rules above do not apply to anything in this folder; instead, they will be directly copied to Magisk's internal `tmpfs` directory (which used to always be `/sbin`).\n\nStarting from Android 11, the `/sbin` folder may no longer exists, and in that scenario, Magisk uses `/debug_ramdisk` instead. Every occurrence of the pattern `${MAGISKTMP}` in your `*.rc` scripts will be replaced with the Magisk `tmpfs` folder when `magiskinit` injects it into `init.rc`. On pre Android 11 devices, `${MAGISKTMP}` will simply be replaced with `/sbin`, so **NEVER** hardcode `/sbin` in the `*.rc` scripts when referencing these additional files.\n\nHere is an example of how to setup `overlay.d` with a custom `*.rc` script:\n\n```\nramdisk\n│\n├── overlay.d\n│   ├── sbin\n│   │   ├── libfoo.ko      <--- These 2 files will be copied\n│   │   └── myscript.sh    <--- into Magisk's tmpfs directory\n│   ├── custom.rc          <--- This file will be injected into init.rc\n│   ├── res\n│   │   └── random.png     <--- This file will replace /res/random.png\n│   └── new_file           <--- This file will be ignored because\n│                               /new_file does not exist\n├── res\n│   └── random.png         <--- This file will be replaced by\n│                               /overlay.d/res/random.png\n├── ...\n├── ...  /* The rest of initramfs files */\n│\n```\n\nHere is an example of the `custom.rc`:\n\n```\n# Use ${MAGISKTMP} to refer to Magisk's tmpfs directory\n\non early-init\n    setprop sys.example.foo bar\n    insmod ${MAGISKTMP}/libfoo.ko\n    start myservice\n\nservice myservice ${MAGISKTMP}/myscript.sh\n    oneshot\n```\n"
  },
  {
    "path": "docs/install.md",
    "content": "# Installation\n\nIf you already have Magisk installed, it is **strongly recommended** to upgrade directly via the Magisk app using its \"Direct Install\" method. The following tutorial is only for the initial installation.\n\n## Getting Started\n\nBefore you start:\n\n- This tutorial assumes you understand how to use `adb` and `fastboot`\n- If you plan to also install custom kernels, install it after Magisk\n- Your device's bootloader has to be unlocked\n\n---\n\nDownload and install the latest [Magisk app](https://github.com/topjohnwu/Magisk/releases/latest). In the home screen, you should see:\n\n<p align=\"center\"><img src=\"images/device_info.png\" width=\"500\"/></p>\n\nThe result of **Ramdisk** determines whether your device has ramdisk in the boot partition. If your device does not have boot ramdisk, read the [Magisk in Recovery](#magisk-in-recovery) section before continuing.\n\n> _(Unfortunately, there are exceptions as some devices' bootloader accepts ramdisk even if it shouldn't. In this case, you will have to follow the instructions as if your device's boot partition **does** include ramdisk. There is no way to detect this, so the only way to know for sure is to actually try. Fortunately, as far as we know, only some Xiaomi devices are known to have this property, so most people can simply ignore this piece of information.)_\n\nIf you are using a Samsung device, you can now jump to [its own section](#samsung-devices).\n\nIf your device has boot ramdisk, get a copy of the `boot.img` (or `init_boot.img` if exists).<br>\nIf your device does **NOT** have boot ramdisk, get a copy of the `recovery.img`.<br>\nYou should be able to extract the file you need from official firmware packages or your custom ROM zip.\n\nQuick recap, at this point, you should have known and prepared:\n\n1. Whether your device has boot ramdisk\n2. A `boot.img`, `init_boot.img` or `recovery.img` based on (1)\n\nLet's continue to [Patching Images](#patching-images).\n\n## Patching Images\n\n- Copy the boot/init_boot/recovery image to your device\n- Press the **Install** button in the Magisk card\n- If you are patching a recovery image, check the **\"Recovery Mode\"** option\n- Choose **\"Select and Patch a File\"** in method, and select the boot/init_boot/recovery image\n- Start the installation, and copy the patched image to your PC using ADB:<br>\n  `adb pull /sdcard/Download/magisk_patched_[random_strings].img`\n- Flash the patched boot/init_boot/recovery image to your device;<br>\n  for most devices, reboot into fastboot mode and flash with command:<br>\n  `fastboot flash boot /path/to/magisk_patched_[random_strings].img` or <br>\n  `fastboot flash init_boot /path/to/magisk_patched_[random_strings].img` or <br>\n  `fastboot flash recovery /path/to/magisk_patched_[random_strings].img` <br>\n- (Optional) If your device has a separate `vbmeta` partition, you can patch the `vbmeta` partition with command:<br>\n  `fastboot flash vbmeta --disable-verity --disable-verification vbmeta.img` (note that it may **wipe your data**)\n- Reboot and launch Magisk app (you will see a stub Magisk app if you have wiped your data; use it to bootstrap to a complete Magisk app), and you will see a prompt asking for environment fix; click and wait for the reboot\n- Voila!\n\n> Warning: **NEVER** flash patched image shared by others or patch image on another device even if they have the same device model! You may need to do a full data wipe to recover your device. **ALWAYS** patch boot image **on the same device where you want to install Magisk**.\n\n## Uninstallation\n\nThe easiest way to uninstall Magisk is directly through the Magisk app. If you insist on using custom recoveries, rename the Magisk APK to `uninstall.zip` and flash it like any other ordinary flashable zip.\n\n## Magisk in Recovery\n\nIn the case when your device does not have ramdisk in boot images, Magisk has no choice but to hijack the recovery partition. For these devices, you will have to **reboot to recovery** every time you want Magisk enabled.\n\nWhen Magisk hijacks the recovery, there is a special mechanism to allow you to _actually_ boot into recovery mode. Each device model has its own key combo to boot into recovery, as an example for Galaxy S10 it is (Power + Bixby + Volume Up). A quick search online should easily get you this info. As soon as you press the key combo and the device vibrates with a splash screen, release all buttons to boot into Magisk. If you decide to boot into the actual recovery mode, **long press volume up until you see the recovery screen**.\n\nAs a summary, after installing Magisk in recovery **(starting from power off)**:\n\n- **(Power up normally) → (System with NO Magisk)**\n- **(Recovery Key Combo) → (Splash screen) → (Release all buttons) → (System with Magisk)**\n- **(Recovery Key Combo) → (Splash screen) → (Long press volume up) → (Recovery Mode)**\n\n(Note: You **CANNOT** use custom recoveries to install or upgrade Magisk in this case!!)\n\n## Samsung Devices\n\nBefore proceeding, please acknowledge that:\n\n- Installing Magisk **WILL** trip your Knox Warranty Bit, this action is not reversible in any way.\n- Installing Magisk for the first time **REQUIRES** a full data wipe (this is **NOT** counting the data wipe when unlocking bootloader). Please make a backup of your data.\n\n### Flashing Tools\n\n- [Samsung Odin3](https://dl2018.sammobile.com/Odin.zip) (Windows only) (requires [Samsung USB Drivers](https://developer.samsung.com/android-usb-driver))\n- [Samsung Odin4](https://forum.xda-developers.com/t/official-samsung-odin-v4-1-2-1-dc05e3ea-for-linux.4453423/) (Linux only)\n- [Heimdall](https://www.glassechidna.com.au/heimdall/) (or [Grimler's fork](https://git.sr.ht/~grimler/Heimdall))\n\n### Requirements\n\nTo verify whether or not Magisk can be installed in your Samsung device, you first must check the OEM Lock and KnoxGuard (RMM) status. To do so, boot your device in Download mode with its key combo.\n\nPossible OEM Lock values are the following:\n- **ON (L)**: fully locked.\n- **ON (U)**: bootloader locked, OEM unlocking enabled.\n- **OFF (U)**: fully unlocked.\n\nTo unlock your bootloader, follow the instructions below. If no OEM Lock value is shown in Download mode, your device is probably not unlockable due to market limitations (USA/Canada devices).\n\nPossible KnoxGuard values are the following:\n\n- `Active`, `Locked`: your device has been remotely locked by your telecom operator or your insurance company.\n- `Prenormal`: your device is temporarily locked, reaching 168h of uptime should trigger unlock.\n- `Checking`, `Completed`, `Broken`: your device is unlocked.\n\nHaving KnoxGuard active will prevent you from installing/running Magisk regardless of your bootloader lock state.\n\n### Unlocking the bootloader\n\n- Allow bootloader unlocking in **Developer options → OEM unlocking**\n- Reboot to download mode: power off your device and press the download mode key combo for your device\n- Long press volume up to unlock the bootloader. **This will wipe your data and automatically reboot.**\n- Go through the initial setup. Skip through all the steps since data will be wiped again in later steps. **Connect the device to Internet during the setup.**\n- Enable developer options, and **confirm that the OEM unlocking option exists and is grayed out.** This means KnoxGuard hasn't locked your device.\n- Your bootloader now accepts unofficial images in download mode\n\n### Instructions\n\n- Download the latest firmware package for your device, you can use one of the tools below to download it directly from Samsung servers:\n  - [SamFirm.NET](https://github.com/jesec/SamFirm.NET), [samfirm.js](https://github.com/jesec/samfirm.js)\n  - [Frija](https://forum.xda-developers.com/s10-plus/how-to/tool-frija-samsung-firmware-downloader-t3910594)\n  - [Samloader](https://forum.xda-developers.com/s10-plus/how-to/tool-samloader-samfirm-frija-replacement-t4105929)\n  - [Bifrost](https://forum.xda-developers.com/t/tool-samsung-samsung-firmware-downloader.4240719/)\n- Unzip the firmware and copy the `AP` tar file to your device. It is normally named as `AP_[device_model_sw_ver].tar.md5`\n- Press the **Install** button in the Magisk card\n- If your device does **NOT** have boot ramdisk, check the **\"Recovery Mode\"** option\n- Choose **\"Select and Patch a File\"** in method, and select the `AP` tar file\n- Start the installation, and copy the patched tar file to your PC using ADB:<br>\n  `adb pull /sdcard/Download/magisk_patched_[random_strings].tar`<br>\n  **DO NOT USE MTP** as it is known to corrupt large files.\n- Reboot to download mode. Open Odin on your PC, and flash `magisk_patched.tar` as `AP`, together with `BL`, `CP`, and `CSC` (**NOT** `HOME_CSC` because we want to **wipe data**) from the original firmware.\n- Your device should reboot automatically once Odin finished flashing. **Agree to do a factory reset if asked.**\n- If your device does **NOT** have boot ramdisk, reboot to recovery now to enable Magisk (reason stated in [Magisk in Recovery](#magisk-in-recovery)).\n- Install the Magisk app you've already downloaded and launch the app. It should show a dialog asking for additional setup.\n- Let the app do its job and automatically reboot the device. Voila!\n\n### Upgrading the OS\n\nOnce you have rooted your Samsung device, you can no longer upgrade your Android OS through OTA. To upgrade your device's OS, you have to manually download the new firmware zip file and go through the same `AP` patching process written in the previous section. **The only difference here is in the Odin flashing step: do NOT use the `CSC` tar, but instead use the `HOME_CSC` tar as we are performing an upgrade, not the initial install**.\n\n### Important Notes\n\n- **Never, ever** try to restore either `boot`, `init_boot`, `recovery`, or `vbmeta` partitions back to stock! You can brick your device by doing so, and the only way to recover from this is to do a full Odin restore with data wipe.\n- To upgrade your device with a new firmware, **NEVER** directly use the stock `AP` tar file with reasons mentioned above. **Always** patch `AP` in the Magisk app and use that instead.\n\n## Custom Recovery\n\n> **This installation method is deprecated and is maintained with minimum effort. YOU HAVE BEEN WARNED!**\n\nInstalling using custom recoveries is only possible if your device has boot ramdisk. Installing Magisk through custom recoveries on modern devices is no longer recommended. If you face any issues, please use the [Patch Image](#patching-images) method.\n\n- Download the Magisk APK\n- Rename the `.apk` file extension to `.zip`, for example: `Magisk-v24.0.apk` → `Magisk-v24.0.zip`. If you have trouble renaming the file extension (like on Windows), use a file manager on Android or the one included in the custom recovery to rename the file.\n- Flash the zip just like any other ordinary flashable zip.\n- Reboot and check whether the Magisk app is installed. If it isn't installed automatically, manually install the APK.\n- Launch the Magisk app; it will show a dialog asking for reinstallation. Do the reinstallation **directly within the app** and reboot (if you are using MTK devices that lock the boot partition after boot, please [patch the boot image](#patching-images) and flash it by custom recovery or fastboot).\n\n> Warning: the `sepolicy.rule` file of modules may be stored in the `cache` partition. DO NOT WIPE THE `CACHE` PARTITION.\n"
  },
  {
    "path": "docs/ota.md",
    "content": "## OTA Upgrade Guides\nMagisk does not modify most read-only partitions, which means applying official OTAs is much simpler. Here are the tutorials for several different kind of devices to apply OTAs and preserve Magisk after the installation (if possible). This is just a general guide as procedures for each device may vary.\n\n**NOTE: In order to apply OTAs, you HAVE to make sure you haven't modified and read-only partitions yourself (such as `/system` or `/vendor`) in any way. Even remounting the partition to rw will tamper block verification!!**\n\n### Prerequisites\n- Please disable *Automatic system updates* in developer options, so it won't install OTAs without your acknowledgement.\n\n<p align=\"center\"><img src=\"images/disable_auto_ota.png\" width=\"250\"/></p>\n\n- When an OTA is available, first go to (Magisk app → Uninstall → Restore Images). **Do not reboot or you will have Magisk uninstalled.** This will restore partitions modified by Magisk back to stock from backups made at install in order to pass pre-OTA block verifications. **This step is required before doing any of the following steps written below!**\n\n<p align=\"center\"><img src=\"images/restore_img.png\" width=\"300\"/></p>\n\n### Devices with A/B Partitions\n\nIt is possible to have the OTA installed to the inactive slot and have the Magisk app install Magisk onto the updated partitions. The out-of-the-box OTA installation works seamlessly and Magisk can be preserved after the installation.\n\n- After restoring stock images, apply OTAs as you normally would (Settings → System → System Update).\n- Wait for the installation to be fully done (both step 1: \"installing update\", and step 2: \"optimizing your device\", of the OTA), **do not press the \"Restart now\" or \"Reboot\" button!** Instead, go to (Magisk app → Install → Install to Inactive Slot) to install Magisk to the updated slot.\n\n<p align=\"center\"><img src=\"images/ota_done.png\" width=\"250\"/> <img src=\"images/install_inactive_slot.png\" width=\"250\"/></p>\n\n- After installation is done, follow the final instructions at the end of the Magisk installation on how to reboot into the new slot, currently involving going back to the regular system update and hitting \"restart now\" (previous method of using reboot within Magisk App may not result in rebooting to new slot). Under-the-hood, the Magisk app tracks your device switch to the updated slot, bypassing any possible post-OTA verifications.\n\n<p align=\"center\"><img src=\"images/manager_reboot.png\" width=\"250\"/></p>\n\n### \"Non A/B\" Devices\nUnfortunately, there are no real good ways to apply OTAs on these devices. The following tutorial will not preserve Magisk; you will have to manually re-root your device after the upgrade, and this will require access to a computer. These are simply \"best practices\".\n\n- To properly install OTAs, you must have your stock recovery installed on your device. If you have custom recovery installed, you can restore it from your previous backup, or dumps found online, or factory images provided by OEMs.\nIf you decide to start by installing Magisk without touching your recovery partition, you have a few choices, either way you will end up with a Magisk rooted device, but recovery remains stock untouched:\n    - If supported, use `fastboot boot <recovery_img>` to boot the custom recovery and install Magisk.\n    - If you have a copy of your stock image dump, install Magisk by using the Magisk app's \"patch images\" feature\n- Once you restored back to stock recovery and other images, download the OTA. Optionally, once you have downloaded the OTA update zip, find a way to extract the zip (as it usually involved root)\n- Apply the OTA and reboot your device. This will use the official stock OTA installation mechanism of your device to upgrade your system.\n- Once it's done you will be left with an upgraded, 100% stock, un-rooted device. You will have to manually flash Magisk back. Consider using the methods stated in step 1. to flash Magisk without touching the recovery partition if you want to receive stock OTAs frequently.\n"
  },
  {
    "path": "docs/tools.md",
    "content": "# Magisk Tools\n\nMagisk comes with a huge collections of tools for installation, daemons, and utilities for developers. This documentation covers the 4 binaries and all included applets. The binaries and applets are shown below:\n\n```\nmagiskboot                 /* binary */\nmagiskinit                 /* binary */\nmagiskpolicy               /* binary */\nsupolicy -> magiskpolicy\nmagisk                     /* binary */\nresetprop -> magisk\nsu -> magisk\n```\n\n### magiskboot\n\nA tool to unpack / repack boot images, parse / patch / extract cpio, patch dtb, hex patch binaries, and compress / decompress files with multiple algorithms.\n\n`magiskboot` natively supports (which means it does not rely on external tools) common compression formats including `gzip`, `lz4`, `lz4_legacy` , `lz4_lg` ([the LG edition](https://events.static.linuxfound.org/sites/events/files/lcjpcojp13_klee.pdf) of `lz4_legacy`, only used on LG), `lzma`, `xz`, and `bzip2`.\n\nThe concept of `magiskboot` is to make boot image modification simpler. For unpacking, it parses the header and extracts all sections in the image, decompressing on-the-fly if compression is detected in any sections. For repacking, the original boot image is required so the original headers can be used, changing only the necessary entries such as section sizes and checksum. All sections will be compressed back to the original format if required. The tool also supports many CPIO and DTB operations.\n\n```\nUsage: ./magiskboot <action> [args...]\n\nSupported actions:\n  unpack [-n] [-h] <bootimg>\n    Unpack <bootimg> to its individual components, each component to\n    a file with its corresponding file name in the current directory.\n    Supported components: kernel, kernel_dtb, ramdisk.cpio, second,\n    dtb, extra, and recovery_dtbo.\n    By default, each component will be automatically decompressed\n    on-the-fly before writing to the output file.\n    If '-n' is provided, all decompression operations will be skipped;\n    each component will remain untouched, dumped in its original format.\n    If '-h' is provided, the boot image header information will be\n    dumped to the file 'header', which can be used to modify header\n    configurations during repacking.\n    Return values:\n    0:valid    1:error    2:chromeos\n\n  repack [-n] <origbootimg> [outbootimg]\n    Repack boot image components using files from the current directory\n    to [outbootimg], or 'new-boot.img' if not specified. Current directory\n    should only contain required files for [outbootimg], or incorrect\n    [outbootimg] may be produced.\n    <origbootimg> is the original boot image used to unpack the components.\n    By default, each component will be automatically compressed using its\n    corresponding format detected in <origbootimg>. If a component file\n    in the current directory is already compressed, then no addition\n    compression will be performed for that specific component.\n    If '-n' is provided, all compression operations will be skipped.\n    If env variable PATCHVBMETAFLAG is set to true, all disable flags in\n    the boot image's vbmeta header will be set.\n\n  verify <bootimg> [x509.pem]\n    Check whether the boot image is signed with AVB 1.0 signature.\n    Optionally provide a certificate to verify whether the image is\n    signed by the public key certificate.\n    Return value:\n    0:valid    1:error\n\n  sign <bootimg> [name] [x509.pem pk8]\n    Sign <bootimg> with AVB 1.0 signature.\n    Optionally provide the name of the image (default: '/boot').\n    Optionally provide the certificate/private key pair for signing.\n    If the certificate/private key pair is not provided, the AOSP\n    verity key bundled in the executable will be used.\n\n  extract <payload.bin> [partition] [outfile]\n    Extract [partition] from <payload.bin> to [outfile].\n    If [outfile] is not specified, then output to '[partition].img'.\n    If [partition] is not specified, then attempt to extract either\n    'init_boot' or 'boot'. Which partition was chosen can be determined\n    by whichever 'init_boot.img' or 'boot.img' exists.\n    <payload.bin> can be '-' to be STDIN.\n\n  hexpatch <file> <hexpattern1> <hexpattern2>\n    Search <hexpattern1> in <file>, and replace it with <hexpattern2>\n\n  cpio <incpio> [commands...]\n    Do cpio commands to <incpio> (modifications are done in-place)\n    Each command is a single argument, add quotes for each command.\n    Supported commands:\n      exists ENTRY\n        Return 0 if ENTRY exists, else return 1\n      rm [-r] ENTRY\n        Remove ENTRY, specify [-r] to remove recursively\n      mkdir MODE ENTRY\n        Create directory ENTRY in permissions MODE\n      ln TARGET ENTRY\n        Create a symlink to TARGET with the name ENTRY\n      mv SOURCE DEST\n        Move SOURCE to DEST\n      add MODE ENTRY INFILE\n        Add INFILE as ENTRY in permissions MODE; replaces ENTRY if exists\n      extract [ENTRY OUT]\n        Extract ENTRY to OUT, or extract all entries to current directory\n      test\n        Test the cpio's status\n        Return value is 0 or bitwise or-ed of following values:\n        0x1:Magisk    0x2:unsupported\n      patch\n        Apply ramdisk patches\n        Configure with env variables: KEEPVERITY KEEPFORCEENCRYPT\n      backup ORIG\n        Create ramdisk backups from ORIG\n      restore\n        Restore ramdisk from ramdisk backup stored within incpio\n\n  dtb <file> <action> [args...]\n    Do dtb related actions to <file>\n    Supported actions:\n      print [-f]\n        Print all contents of dtb for debugging\n        Specify [-f] to only print fstab nodes\n      patch\n        Search for fstab and remove verity/avb\n        Modifications are done directly to the file in-place\n        Configure with env variables: KEEPVERITY\n      test\n        Test the fstab's status\n        Return values:\n        0:valid    1:error\n\n  split <file>\n    Split image.*-dtb into kernel + kernel_dtb\n\n  sha1 <file>\n    Print the SHA1 checksum for <file>\n\n  cleanup\n    Cleanup the current working directory\n\n  compress[=format] <infile> [outfile]\n    Compress <infile> with [format] to [outfile].\n    <infile>/[outfile] can be '-' to be STDIN/STDOUT.\n    If [format] is not specified, then gzip will be used.\n    If [outfile] is not specified, then <infile> will be replaced\n    with another file suffixed with a matching file extension.\n    Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg \n\n  decompress <infile> [outfile]\n    Detect format and decompress <infile> to [outfile].\n    <infile>/[outfile] can be '-' to be STDIN/STDOUT.\n    If [outfile] is not specified, then <infile> will be replaced\n    with another file removing its archive format file extension.\n    Supported formats: gzip zopfli xz lzma bzip2 lz4 lz4_legacy lz4_lg \n```\n\n### magiskinit\n\nThis binary will replace `init` in the ramdisk of a Magisk patched boot image. It is originally created for supporting devices using system-as-root, but the tool is extended to support all devices and became a crucial part of Magisk. More details can be found in the **Pre-Init** section in [Magisk Booting Process](details.md#magisk-booting-process).\n\n### magiskpolicy\n\n(This tool is aliased to `supolicy` for compatibility with SuperSU's sepolicy tool)\n\nThis tool could be used for advanced developers to modify SELinux policies. In common scenarios like Linux server admins, they would directly modify the SELinux policy sources (`*.te`) and recompile the `sepolicy` binary, but here on Android we directly patch the binary file (or runtime policies).\n\nAll processes spawned from the Magisk daemon, including root shells and all its forks, are running in the context `u:r:magisk:s0`. The rule used on all Magisk installed systems can be viewed as stock `sepolicy` with these patches: `magiskpolicy --magisk 'allow magisk * * *'`.\n\n```\nUsage: ./magiskpolicy [--options...] [policy statements...]\n\nOptions:\n   --help            show help message for policy statements\n   --load FILE       load monolithic sepolicy from FILE\n   --load-split      load from precompiled sepolicy or compile\n                     split cil policies\n   --compile-split   compile split cil policies\n   --save FILE       dump monolithic sepolicy to FILE\n   --live            immediately load sepolicy into the kernel\n   --magisk          apply built-in Magisk sepolicy rules\n   --apply FILE      apply rules from FILE, read and parsed\n                     line by line as policy statements\n                     (multiple --apply are allowed)\n\nIf neither --load, --load-split, nor --compile-split is specified,\nit will load from current live policies (/sys/fs/selinux/policy)\n\nOne policy statement should be treated as one parameter;\nthis means each policy statement should be enclosed in quotes.\nMultiple policy statements can be provided in a single command.\n\nStatements has a format of \"<rule_name> [args...]\".\nArguments labeled with (^) can accept one or more entries. Multiple\nentries consist of a space separated list enclosed in braces ({}).\nArguments labeled with (*) are the same as (^), but additionally\nsupport the match-all operator (*).\n\nExample: \"allow { s1 s2 } { t1 t2 } class *\"\nWill be expanded to:\n\nallow s1 t1 class { all-permissions-of-class }\nallow s1 t2 class { all-permissions-of-class }\nallow s2 t1 class { all-permissions-of-class }\nallow s2 t2 class { all-permissions-of-class }\n\nSupported policy statements:\n\n\"allow *source_type *target_type *class *perm_set\"\n\"deny *source_type *target_type *class *perm_set\"\n\"auditallow *source_type *target_type *class *perm_set\"\n\"dontaudit *source_type *target_type *class *perm_set\"\n\n\"allowxperm *source_type *target_type *class operation xperm_set\"\n\"auditallowxperm *source_type *target_type *class operation xperm_set\"\n\"dontauditxperm *source_type *target_type *class operation xperm_set\"\n- The only supported operation is 'ioctl'\n- xperm_set format is either 'low-high', 'value', or '*'.\n  '*' will be treated as '0x0000-0xFFFF'.\n  All values should be written in hexadecimal.\n\n\"permissive ^type\"\n\"enforce ^type\"\n\n\"typeattribute ^type ^attribute\"\n\n\"type type_name ^(attribute)\"\n- Argument 'attribute' is optional, default to 'domain'\n\n\"attribute attribute_name\"\n\n\"type_transition source_type target_type class default_type (object_name)\"\n- Argument 'object_name' is optional\n\n\"type_change source_type target_type class default_type\"\n\"type_member source_type target_type class default_type\"\n\n\"genfscon fs_name partial_path fs_context\"\n```\n\n### magisk\n\nWhen the magisk binary is called with the name `magisk`, it works as a utility tool with many helper functions and the entry points for several Magisk services.\n\n```\nUsage: magisk [applet [arguments]...]\n   or: magisk [options]...\n\nOptions:\n   -c                        print current binary version\n   -v                        print running daemon version\n   -V                        print running daemon version code\n   --list                    list all available applets\n   --remove-modules [-n]     remove all modules, reboot if -n is not provided\n   --install-module ZIP      install a module zip file\n\nAdvanced Options (Internal APIs):\n   --daemon                  manually start magisk daemon\n   --stop                    remove all magisk changes and stop daemon\n   --[init trigger]          callback on init triggers. Valid triggers:\n                             post-fs-data, service, boot-complete, zygote-restart\n   --unlock-blocks           set BLKROSET flag to OFF for all block devices\n   --restorecon              restore selinux context on Magisk files\n   --clone-attr SRC DEST     clone permission, owner, and selinux context\n   --clone SRC DEST          clone SRC to DEST\n   --sqlite SQL              exec SQL commands to Magisk database\n   --path                    print Magisk tmpfs mount path\n   --denylist ARGS           denylist config CLI\n   --preinit-device          resolve a device to store preinit files\n\nAvailable applets:\n    su, resetprop\n\nUsage: magisk --denylist [action [arguments...] ]\nActions:\n   status          Return the enforcement status\n   enable          Enable denylist enforcement\n   disable         Disable denylist enforcement\n   add PKG [PROC]  Add a new target to the denylist\n   rm PKG [PROC]   Remove target(s) from the denylist\n   ls              Print the current denylist\n   exec CMDs...    Execute commands in isolated mount\n                   namespace and do all unmounts\n```\n\n### su\n\nAn applet of `magisk`, the MagiskSU entry point. Good old `su` command.\n\n```\nUsage: su [options] [-] [user [argument...]]\n\nOptions:\n  -c, --command COMMAND         Pass COMMAND to the invoked shell\n  -g, --group GROUP             Specify the primary group\n  -G, --supp-group GROUP        Specify a supplementary group.\n                                The first specified supplementary group is also used\n                                as a primary group if the option -g is not specified.\n  -Z, --context CONTEXT         Change SELinux context\n  -t, --target PID              PID to take mount namespace from\n  -h, --help                    Display this help message and exit\n  -, -l, --login                Pretend the shell to be a login shell\n  -m, -p,\n  --preserve-environment        Preserve the entire environment\n  -s, --shell SHELL             Use SHELL instead of the default /system/bin/sh\n  -v, --version                 Display version number and exit\n  -V                            Display version code and exit\n  -mm, -M,\n  --mount-master                Force run in the global mount namespace\n```\n\n### resetprop\n\nAn applet of `magisk`. An advanced system property manipulation utility. Check the [Resetprop Details](details.md#resetprop) for more background information.\n\n```\nUsage: resetprop [flags] [options...]\n\nOptions:\n   -h, --help        show this message\n   (no arguments)    print all properties\n   NAME              get property\n   NAME VALUE        set property entry NAME with VALUE\n   --file FILE       load props from FILE\n   --delete NAME     delete property\n\nFlags:\n   -v      print verbose output to stderr\n   -n      set props without going through property_service\n           (this flag only affects setprop)\n   -p      read/write props from/to persistent storage\n           (this flag only affects getprop and delprop)\n```\n"
  },
  {
    "path": "native/.gitignore",
    "content": "/build\nobj\nlibs\n/.externalNativeBuild\n/.cxx\n*-rs.cpp\n*-rs.hpp\n/compile_commands.json\n"
  },
  {
    "path": "native/src/.cargo/config.toml",
    "content": "[build]\n# Set arm64 as the default target\n# The actual compilation will have the target overriden by command-line.\ntarget = \"aarch64-linux-android\"\n# Enable cross language LTO, and explicitly set dwarf-version for ThinLTO\nrustflags = [\n    \"-Z\",\n    \"dwarf-version=4\",\n    \"-C\",\n    \"linker-plugin-lto\",\n    \"-C\",\n    \"force-unwind-tables=no\",\n]\ntarget-dir = \"../out/rust\"\n\n[unstable]\nbuild-std = [\"std\", \"panic_abort\"]\nbuild-std-features = [\"optimize_for_size\"]\nprofile-rustflags = true\n\n[profile.release]\nrustflags = [\"-Z\", \"location-detail=none\", \"-Z\", \"fmt-debug=none\"]\n"
  },
  {
    "path": "native/src/Android-rs.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\n###########################\n# Rust compilation outputs\n###########################\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := magisk-rs\nLOCAL_EXPORT_C_INCLUDES := src/core/include\nLOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagisk-rs.a\nifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))\nLOCAL_SRC_FILES := $(LOCAL_LIB)\ninclude $(PREBUILT_STATIC_LIBRARY)\nelse\ninclude $(BUILD_STATIC_LIBRARY)\nendif\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := boot-rs\nLOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskboot-rs.a\nifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))\nLOCAL_SRC_FILES := $(LOCAL_LIB)\ninclude $(PREBUILT_STATIC_LIBRARY)\nelse\ninclude $(BUILD_STATIC_LIBRARY)\nendif\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := init-rs\nLOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskinit-rs.a\nifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))\nLOCAL_SRC_FILES := $(LOCAL_LIB)\ninclude $(PREBUILT_STATIC_LIBRARY)\nelse\ninclude $(BUILD_STATIC_LIBRARY)\nendif\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := policy-rs\nLOCAL_LIB = ../out/$(TARGET_ARCH_ABI)/libmagiskpolicy-rs.a\nifneq (,$(wildcard $(LOCAL_PATH)/$(LOCAL_LIB)))\nLOCAL_SRC_FILES := $(LOCAL_LIB)\ninclude $(PREBUILT_STATIC_LIBRARY)\nelse\ninclude $(BUILD_STATIC_LIBRARY)\nendif\n"
  },
  {
    "path": "native/src/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\n########################\n# Binaries\n########################\n\nifdef B_MAGISK\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := magisk\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    libsystemproperties \\\n    liblsplt \\\n    libmagisk-rs\n\nLOCAL_SRC_FILES := \\\n    core/applets.cpp \\\n    core/scripting.cpp \\\n    core/sqlite.cpp \\\n    core/utils.cpp \\\n    core/core-rs.cpp \\\n    core/resetprop/sys.cpp \\\n    core/su/su.cpp \\\n    core/zygisk/entry.cpp \\\n    core/zygisk/module.cpp \\\n    core/zygisk/hook.cpp \\\n    core/deny/cli.cpp \\\n    core/deny/utils.cpp \\\n    core/deny/logcat.cpp\n\nLOCAL_LDLIBS := -llog\nLOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt\n\ninclude $(BUILD_EXECUTABLE)\n\nendif\n\nifdef B_PRELOAD\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := init-ld\nLOCAL_SRC_FILES := init/preload.c\nLOCAL_LDFLAGS := -Wl,--strip-all\ninclude $(BUILD_SHARED_LIBRARY)\n\nendif\n\nifdef B_INIT\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := magiskinit\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    libpolicy \\\n    libxz \\\n    libinit-rs\n\nLOCAL_SRC_FILES := \\\n    init/mount.cpp \\\n    init/rootdir.cpp \\\n    init/getinfo.cpp \\\n    init/init-rs.cpp\n\nLOCAL_LDFLAGS := -static\n\nifdef B_CRT0\nLOCAL_STATIC_LIBRARIES += crt0\nLOCAL_LDFLAGS += -Wl,--defsym=vfprintf=tiny_vfprintf\nendif\n\ninclude $(BUILD_EXECUTABLE)\n\nendif\n\nifdef B_BOOT\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := magiskboot\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    liblz4 \\\n    libboot-rs\n\nLOCAL_SRC_FILES := \\\n    boot/bootimg.cpp \\\n    boot/boot-rs.cpp\n\nLOCAL_LDFLAGS := -static\n\nifdef B_CRT0\nLOCAL_STATIC_LIBRARIES += crt0\nLOCAL_LDFLAGS += -lm -Wl,--defsym=vfprintf=musl_vfprintf\nendif\n\ninclude $(BUILD_EXECUTABLE)\n\nendif\n\nifdef B_POLICY\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := magiskpolicy\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    libpolicy \\\n    libpolicy-rs\n\ninclude $(BUILD_EXECUTABLE)\n\nendif\n\nifdef B_PROP\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := resetprop\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    libsystemproperties \\\n    libmagisk-rs\n\nLOCAL_SRC_FILES := \\\n    core/applet_stub.cpp \\\n    core/resetprop/sys.cpp \\\n    core/core-rs.cpp\n\nLOCAL_CFLAGS := -DAPPLET_STUB_MAIN=resetprop_main\ninclude $(BUILD_EXECUTABLE)\n\nendif\n\n########################\n# Libraries\n########################\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := libpolicy\nLOCAL_STATIC_LIBRARIES := \\\n    libbase \\\n    libsepol\nLOCAL_C_INCLUDES := src/sepolicy/include\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\nLOCAL_SRC_FILES := \\\n    sepolicy/api.cpp \\\n    sepolicy/sepolicy.cpp \\\n    sepolicy/policydb.cpp \\\n    sepolicy/policy-rs.cpp\ninclude $(BUILD_STATIC_LIBRARY)\n\nCWD := $(LOCAL_PATH)\ninclude $(CWD)/Android-rs.mk\ninclude $(CWD)/base/Android.mk\ninclude $(CWD)/external/Android.mk\n"
  },
  {
    "path": "native/src/Application.mk",
    "content": "APP_BUILD_SCRIPT := src/Android.mk\nAPP_CFLAGS       := -Wall -Oz -fomit-frame-pointer\nAPP_CPPFLAGS     := -std=c++23\nAPP_STL          := none\nAPP_PLATFORM     := android-23\nAPP_THIN_ARCHIVE := true\nAPP_STRIP_MODE   := none\n\nifdef MAGISK_DEBUG\n\nNDK_APP_OUT \t := ./obj/debug\nAPP_CFLAGS       += -flto=thin -gdwarf-4\nAPP_LDFLAGS      += -flto=thin\n\nelse\n\nNDK_APP_OUT \t := ./obj/release\nAPP_CFLAGS       += -flto\nAPP_LDFLAGS      += -flto -Wl,--icf=all\n\nendif\n\nifdef B_CRT0\n\n# Disable all security and debugging features\nAPP_CFLAGS       += -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-stack-protector -fno-threadsafe-statics -U_FORTIFY_SOURCE\n# Override output folder to make sure all dependencies are rebuilt with new CFLAGS\nNDK_APP_OUT      := $(NDK_APP_OUT)-nolibc\n\nendif\n"
  },
  {
    "path": "native/src/Cargo.toml",
    "content": "cargo-features = [\"panic-immediate-abort\"]\n\n[workspace]\nexclude = [\"external\"]\nmembers = [\"base\", \"base/derive\", \"boot\", \"core\", \"init\", \"sepolicy\"]\nresolver = \"2\"\n\n[workspace.package]\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[workspace.dependencies]\nbase = { path = \"base\" }\nderive = { path = \"base/derive\" }\nmagiskpolicy = { path = \"sepolicy\" }\ncxx = { path = \"external/cxx-rs\" }\ncxx-gen = { path = \"external/cxx-rs/gen/lib\" }\nlibc = \"0.2.182\"\ncfg-if = \"1.0.4\"\nnum-traits = \"0.2.19\"\nnum-derive = \"0.4.2\"\nthiserror = \"2.0.18\"\nbyteorder = \"1.5.0\"\nsize = \"0.5.0\"\nbytemuck = \"1.25.0\"\nfdt = \"0.1.5\"\nconst_format = \"0.2.35\"\nbit-set = \"0.8.0\"\nsyn = \"2.0.117\"\nquote = \"1.0.44\"\nproc-macro2 = \"1.0.106\"\npb-rs = { version = \"0.10.0\", default-features = false }\nquick-protobuf = \"0.8.1\"\nflate2 = { version = \"1.1.9\", default-features = false }\nbzip2 = \"0.6.1\"\nzopfli = \"0.8.3\"\nlz4 = \"1.28.1\"\nlzma-rust2 = { version = \"0.16.2\", default-features = false }\nnix = \"0.30.1\"\nbitflags = \"2.11.0\"\n\n# Rust crypto crates are tied together\nsha1 = \"0.11.0-rc.5\"\nsha2 = \"0.11.0-rc.5\"\ndigest = \"0.11.0\"\np256 = \"0.14.0-rc.7\"\np384 = \"0.14.0-rc.7\"\np521 = \"0.14.0-rc.7\"\nrsa = \"0.10.0-rc.15\"\nx509-cert = \"0.3.0-rc.4\"\nder = \"0.8.0\"\n\n[patch.crates-io]\npb-rs = { git = \"https://github.com/topjohnwu/quick-protobuf.git\" }\nquick-protobuf = { git = \"https://github.com/topjohnwu/quick-protobuf.git\" }\nlz4-sys = { path = \"external/lz4-sys\" }\n\n[workspace.lints.clippy]\nunwrap_used = \"deny\"\n\n[profile.dev]\nopt-level = \"z\"\nlto = \"thin\"\npanic = \"immediate-abort\"\ndebug = \"none\"\n\n[profile.release]\nopt-level = \"z\"\nlto = \"fat\"\ncodegen-units = 1\npanic = \"immediate-abort\"\nstrip = true\n"
  },
  {
    "path": "native/src/base/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\n# Magisk project-wide common code\n\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := libbase\nLOCAL_C_INCLUDES := \\\n    src/include \\\n    $(LOCAL_PATH)/include \\\n    out/generated\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\nLOCAL_EXPORT_STATIC_LIBRARIES := libcxx\nLOCAL_STATIC_LIBRARIES := libcxx\nLOCAL_SRC_FILES := \\\n    base.cpp \\\n    base-rs.cpp \\\n    ../external/cxx-rs/src/cxx.cc\ninclude $(BUILD_STATIC_LIBRARY)\n"
  },
  {
    "path": "native/src/base/Cargo.toml",
    "content": "[package]\nname = \"base\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\npath = \"lib.rs\"\n\n[features]\nselinux = []\n\n[lints]\nworkspace = true\n\n[build-dependencies]\ncxx-gen = { workspace = true }\n\n[dependencies]\nderive = { workspace = true }\ncxx = { workspace = true }\nlibc = { workspace = true }\ncfg-if = { workspace = true }\nthiserror = { workspace = true }\nbytemuck = { workspace = true }\nnum-traits = { workspace = true }\nnum-derive = { workspace = true }\nconst_format = { workspace = true }\nnix = { workspace = true, features = [\"fs\", \"mount\", \"user\"] }\nbitflags = { workspace = true }\n"
  },
  {
    "path": "native/src/base/argh.rs",
    "content": "// Copyright (c) 2020 Google LLC All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\n//! Derive-based argument parsing optimized for code size and conformance\n//! to the Fuchsia commandline tools specification\n//!\n//! The public API of this library consists primarily of the `FromArgs`\n//! derive and the `from_env` function, which can be used to produce\n//! a top-level `FromArgs` type from the current program's commandline\n//! arguments.\n//!\n//! ## Basic Example\n//!\n//! ```rust,no_run\n//! use argh::FromArgs;\n//!\n//! #[derive(FromArgs)]\n//! /// Reach new heights.\n//! struct GoUp {\n//!     /// whether or not to jump\n//!     #[argh(switch, short = 'j')]\n//!     jump: bool,\n//!\n//!     /// how high to go\n//!     #[argh(option)]\n//!     height: usize,\n//!\n//!     /// an optional nickname for the pilot\n//!     #[argh(option)]\n//!     pilot_nickname: Option<String>,\n//! }\n//!\n//! let up: GoUp = argh::from_env();\n//! ```\n//!\n//! `./some_bin --help` will then output the following:\n//!\n//! ```bash\n//! Usage: cmdname [-j] --height <height> [--pilot-nickname <pilot-nickname>]\n//!\n//! Reach new heights.\n//!\n//! Options:\n//!   -j, --jump        whether or not to jump\n//!   --height          how high to go\n//!   --pilot-nickname  an optional nickname for the pilot\n//!   --help, help      display usage information\n//! ```\n//!\n//! The resulting program can then be used in any of these ways:\n//! - `./some_bin --height 5`\n//! - `./some_bin -j --height 5`\n//! - `./some_bin --jump --height 5 --pilot-nickname Wes`\n//!\n//! Switches, like `jump`, are optional and will be set to true if provided.\n//!\n//! Options, like `height` and `pilot_nickname`, can be either required,\n//! optional, or repeating, depending on whether they are contained in an\n//! `Option` or a `Vec`. Default values can be provided using the\n//! `#[argh(default = \"<your_code_here>\")]` attribute, and in this case an\n//! option is treated as optional.\n//!\n//! ```rust\n//! use argh::FromArgs;\n//!\n//! fn default_height() -> usize {\n//!     5\n//! }\n//!\n//! #[derive(FromArgs)]\n//! /// Reach new heights.\n//! #[argh(help_triggers(\"-h\", \"--help\", \"help\"))]\n//! struct GoUp {\n//!     /// an optional nickname for the pilot\n//!     #[argh(option)]\n//!     pilot_nickname: Option<String>,\n//!\n//!     /// an optional height\n//!     #[argh(option, default = \"default_height()\")]\n//!     height: usize,\n//!\n//!     /// an optional direction which is \"up\" by default\n//!     #[argh(option, default = \"String::from(\\\"only up\\\")\")]\n//!     direction: String,\n//! }\n//!\n//! fn main() {\n//!     let up: GoUp = argh::from_env();\n//! }\n//! ```\n//!\n//! Custom option types can be deserialized so long as they implement the\n//! `FromArgValue` trait (automatically implemented for all `FromStr` types).\n//! If more customized parsing is required, you can supply a custom\n//! `fn(&str) -> Result<T, String>` using the `from_str_fn` attribute:\n//!\n//! ```\n//! # use argh::FromArgs;\n//!\n//! #[derive(FromArgs)]\n//! /// Goofy thing.\n//! struct FiveStruct {\n//!     /// always five\n//!     #[argh(option, from_str_fn(always_five))]\n//!     five: usize,\n//! }\n//!\n//! fn always_five(_value: &str) -> Result<usize, String> {\n//!     Ok(5)\n//! }\n//! ```\n//!\n//! Positional arguments can be declared using `#[argh(positional)]`.\n//! These arguments will be parsed in order of their declaration in\n//! the structure:\n//!\n//! ```rust\n//! use argh::FromArgs;\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// A command with positional arguments.\n//! struct WithPositional {\n//!     #[argh(positional)]\n//!     first: String,\n//! }\n//! ```\n//!\n//! The last positional argument may include a default, or be wrapped in\n//! `Option` or `Vec` to indicate an optional or repeating positional argument.\n//!\n//! If your final positional argument has the `greedy` option on it, it will consume\n//! any arguments after it as if a `--` were placed before the first argument to\n//! match the greedy positional:\n//!\n//! ```rust\n//! use argh::FromArgs;\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// A command with a greedy positional argument at the end.\n//! struct WithGreedyPositional {\n//!     /// some stuff\n//!     #[argh(option)]\n//!     stuff: Option<String>,\n//!     #[argh(positional, greedy)]\n//!     all_the_rest: Vec<String>,\n//! }\n//! ```\n//!\n//! Now if you pass `--stuff Something` after a positional argument, it will\n//! be consumed by `all_the_rest` instead of setting the `stuff` field.\n//!\n//! Note that `all_the_rest` won't be listed as a positional argument in the\n//! long text part of help output (and it will be listed at the end of the usage\n//! line as `[all_the_rest...]`), and it's up to the caller to append any\n//! extra help output for the meaning of the captured arguments. This is to\n//! enable situations where some amount of argument processing needs to happen\n//! before the rest of the arguments can be interpreted, and shouldn't be used\n//! for regular use as it might be confusing.\n//!\n//! Subcommands are also supported. To use a subcommand, declare a separate\n//! `FromArgs` type for each subcommand as well as an enum that cases\n//! over each command:\n//!\n//! ```rust\n//! # use argh::FromArgs;\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// Top-level command.\n//! struct TopLevel {\n//!     #[argh(subcommand)]\n//!     nested: MySubCommandEnum,\n//! }\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! #[argh(subcommand)]\n//! enum MySubCommandEnum {\n//!     One(SubCommandOne),\n//!     Two(SubCommandTwo),\n//! }\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// First subcommand.\n//! #[argh(subcommand, name = \"one\")]\n//! struct SubCommandOne {\n//!     #[argh(option)]\n//!     /// how many x\n//!     x: usize,\n//! }\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// Second subcommand.\n//! #[argh(subcommand, name = \"two\")]\n//! struct SubCommandTwo {\n//!     #[argh(switch)]\n//!     /// whether to fooey\n//!     fooey: bool,\n//! }\n//! ```\n//!\n//! You can also discover subcommands dynamically at runtime. To do this,\n//! declare subcommands as usual and add a variant to the enum with the\n//! `dynamic` attribute. Instead of deriving `FromArgs`, the value inside the\n//! dynamic variant should implement `DynamicSubCommand`.\n//!\n//! ```rust\n//! # use argh::CommandInfo;\n//! # use argh::DynamicSubCommand;\n//! # use argh::EarlyExit;\n//! # use argh::FromArgs;\n//! # use once_cell::sync::OnceCell;\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// Top-level command.\n//! struct TopLevel {\n//!     #[argh(subcommand)]\n//!     nested: MySubCommandEnum,\n//! }\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! #[argh(subcommand)]\n//! enum MySubCommandEnum {\n//!     Normal(NormalSubCommand),\n//!     #[argh(dynamic)]\n//!     Dynamic(Dynamic),\n//! }\n//!\n//! #[derive(FromArgs, PartialEq, Debug)]\n//! /// Normal subcommand.\n//! #[argh(subcommand, name = \"normal\")]\n//! struct NormalSubCommand {\n//!     #[argh(option)]\n//!     /// how many x\n//!     x: usize,\n//! }\n//!\n//! /// Dynamic subcommand.\n//! #[derive(PartialEq, Debug)]\n//! struct Dynamic {\n//!     name: String\n//! }\n//!\n//! impl DynamicSubCommand for Dynamic {\n//!     fn commands() -> &'static [&'static CommandInfo] {\n//!         static RET: OnceCell<Vec<&'static CommandInfo>> = OnceCell::new();\n//!         RET.get_or_init(|| {\n//!             let mut commands = Vec::new();\n//!\n//!             // argh needs the `CommandInfo` structs we generate to be valid\n//!             // for the static lifetime. We can allocate the structures on\n//!             // the heap with `Box::new` and use `Box::leak` to get a static\n//!             // reference to them. We could also just use a constant\n//!             // reference, but only because this is a synthetic example; the\n//!             // point of using dynamic commands is to have commands you\n//!             // don't know about until runtime!\n//!             commands.push(&*Box::leak(Box::new(CommandInfo {\n//!                 name: \"dynamic_command\",\n//!                 description: \"A dynamic command\",\n//!             })));\n//!\n//!             commands\n//!         })\n//!     }\n//!\n//!     fn try_redact_arg_values(\n//!         command_name: &[&str],\n//!         args: &[&str],\n//!     ) -> Option<Result<Vec<String>, EarlyExit>> {\n//!         for command in Self::commands() {\n//!             if command_name.last() == Some(&command.name) {\n//!                 // Process arguments and redact values here.\n//!                 if !args.is_empty() {\n//!                     return Some(Err(\"Our example dynamic command never takes arguments!\"\n//!                                     .to_string().into()));\n//!                 }\n//!                 return Some(Ok(Vec::new()))\n//!             }\n//!         }\n//!         None\n//!     }\n//!\n//!     fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>> {\n//!         for command in Self::commands() {\n//!             if command_name.last() == Some(&command.name) {\n//!                 if !args.is_empty() {\n//!                     return Some(Err(\"Our example dynamic command never takes arguments!\"\n//!                                     .to_string().into()));\n//!                 }\n//!                 return Some(Ok(Dynamic { name: command.name.to_string() }))\n//!             }\n//!         }\n//!         None\n//!     }\n//! }\n//! ```\n//!\n//! Programs that are run from an environment such as cargo may find it\n//! useful to have positional arguments present in the structure but\n//! omitted from the usage output. This can be accomplished by adding\n//! the `hidden_help` attribute to that argument:\n//!\n//! ```rust\n//! # use argh::FromArgs;\n//!\n//! #[derive(FromArgs)]\n//! /// Cargo arguments\n//! struct CargoArgs {\n//!     // Cargo puts the command name invoked into the first argument,\n//!     // so we don't want this argument to show up in the usage text.\n//!     #[argh(positional, hidden_help)]\n//!     command: String,\n//!     /// an option used for internal debugging\n//!     #[argh(option, hidden_help)]\n//!     internal_debugging: String,\n//!     #[argh(positional)]\n//!     real_first_arg: String,\n//! }\n//! ```\n\nuse std::str::FromStr;\n\npub use derive::FromArgs;\n\n/// Information about a particular command used for output.\npub type CommandInfo = argh_shared::CommandInfo<'static>;\n\n/// Information about the command including the options and arguments.\npub type CommandInfoWithArgs = argh_shared::CommandInfoWithArgs<'static>;\n\n/// Information about a subcommand.\npub type SubCommandInfo = argh_shared::SubCommandInfo<'static>;\n\npub use argh_shared::{ErrorCodeInfo, FlagInfo, FlagInfoKind, Optionality, PositionalInfo};\n\n/// Structured information about the command line arguments.\npub trait ArgsInfo {\n    /// Returns the argument info.\n    fn get_args_info() -> CommandInfoWithArgs;\n\n    /// Returns the list of subcommands\n    fn get_subcommands() -> Vec<SubCommandInfo> {\n        Self::get_args_info().commands\n    }\n}\n\n/// Types which can be constructed from a set of commandline arguments.\npub trait FromArgs: Sized {\n    /// Construct the type from an input set of arguments.\n    ///\n    /// The first argument `command_name` is the identifier for the current command. In most cases,\n    /// users should only pass in a single item for the command name, which typically comes from\n    /// the first item from `std::env::args()`. Implementations however should append the\n    /// subcommand name in when recursively calling [FromArgs::from_args] for subcommands. This\n    /// allows `argh` to generate correct subcommand help strings.\n    ///\n    /// The second argument `args` is the rest of the command line arguments.\n    ///\n    /// # Examples\n    ///\n    /// ```rust\n    /// # use argh::FromArgs;\n    ///\n    /// /// Command to manage a classroom.\n    /// #[derive(Debug, PartialEq, FromArgs)]\n    /// struct ClassroomCmd {\n    ///     #[argh(subcommand)]\n    ///     subcommands: Subcommands,\n    /// }\n    ///\n    /// #[derive(Debug, PartialEq, FromArgs)]\n    /// #[argh(subcommand)]\n    /// enum Subcommands {\n    ///     List(ListCmd),\n    ///     Add(AddCmd),\n    /// }\n    ///\n    /// /// list all the classes.\n    /// #[derive(Debug, PartialEq, FromArgs)]\n    /// #[argh(subcommand, name = \"list\")]\n    /// struct ListCmd {\n    ///     /// list classes for only this teacher.\n    ///     #[argh(option)]\n    ///     teacher_name: Option<String>,\n    /// }\n    ///\n    /// /// add students to a class.\n    /// #[derive(Debug, PartialEq, FromArgs)]\n    /// #[argh(subcommand, name = \"add\")]\n    /// struct AddCmd {\n    ///     /// the name of the class's teacher.\n    ///     #[argh(option)]\n    ///     teacher_name: String,\n    ///\n    ///     /// the name of the class.\n    ///     #[argh(positional)]\n    ///     class_name: String,\n    /// }\n    ///\n    /// let args = ClassroomCmd::from_args(\n    ///     &[\"classroom\"],\n    ///     &[\"list\", \"--teacher-name\", \"Smith\"],\n    /// ).unwrap();\n    /// assert_eq!(\n    ///    args,\n    ///     ClassroomCmd {\n    ///         subcommands: Subcommands::List(ListCmd {\n    ///             teacher_name: Some(\"Smith\".to_string()),\n    ///         })\n    ///     },\n    /// );\n    ///\n    /// // Help returns an error, but internally returns an `Ok` status.\n    /// let early_exit = ClassroomCmd::from_args(\n    ///     &[\"classroom\"],\n    ///     &[\"help\"],\n    /// ).unwrap_err();\n    /// assert_eq!(\n    ///     early_exit,\n    ///     argh::EarlyExit {\n    ///        output: r#\"Usage: classroom <command> [<args>]\n    ///\n    /// Command to manage a classroom.\n    ///\n    /// Options:\n    ///   --help, help      display usage information\n    ///\n    /// Commands:\n    ///   list              list all the classes.\n    ///   add               add students to a class.\n    /// \"#.to_string(),\n    ///        status: Ok(()),\n    ///     },\n    /// );\n    ///\n    /// // Help works with subcommands.\n    /// let early_exit = ClassroomCmd::from_args(\n    ///     &[\"classroom\"],\n    ///     &[\"list\", \"help\"],\n    /// ).unwrap_err();\n    /// assert_eq!(\n    ///     early_exit,\n    ///     argh::EarlyExit {\n    ///        output: r#\"Usage: classroom list [--teacher-name <teacher-name>]\n    ///\n    /// list all the classes.\n    ///\n    /// Options:\n    ///   --teacher-name    list classes for only this teacher.\n    ///   --help, help      display usage information\n    /// \"#.to_string(),\n    ///        status: Ok(()),\n    ///     },\n    /// );\n    ///\n    /// // Incorrect arguments will error out.\n    /// let err = ClassroomCmd::from_args(\n    ///     &[\"classroom\"],\n    ///     &[\"lisp\"],\n    /// ).unwrap_err();\n    /// assert_eq!(\n    ///    err,\n    ///    argh::EarlyExit {\n    ///        output: \"Unrecognized argument: lisp\\n\".to_string(),\n    ///        status: Err(()),\n    ///     },\n    /// );\n    /// ```\n    fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit>;\n}\n\n/// A top-level `FromArgs` implementation that is not a subcommand.\npub trait TopLevelCommand: FromArgs {}\n\n/// A `FromArgs` implementation that can parse into one or more subcommands.\npub trait SubCommands: FromArgs {\n    /// Info for the commands.\n    const COMMANDS: &'static [&'static CommandInfo];\n\n    /// Get a list of commands that are discovered at runtime.\n    fn dynamic_commands() -> &'static [&'static CommandInfo] {\n        &[]\n    }\n}\n\n/// A `FromArgs` implementation that represents a single subcommand.\npub trait SubCommand: FromArgs {\n    /// Information about the subcommand.\n    const COMMAND: &'static CommandInfo;\n}\n\nimpl<T: SubCommand> SubCommands for T {\n    const COMMANDS: &'static [&'static CommandInfo] = &[T::COMMAND];\n}\n\n/// Trait implemented by values returned from a dynamic subcommand handler.\npub trait DynamicSubCommand: Sized {\n    /// Info about supported subcommands.\n    fn commands() -> &'static [&'static CommandInfo];\n\n    /// Perform the function of `FromArgs::redact_arg_values` for this dynamic\n    /// command.\n    ///\n    /// The full list of subcommands, ending with the subcommand that should be\n    /// dynamically recognized, is passed in `command_name`. If the command\n    /// passed is not recognized, this function should return `None`. Otherwise\n    /// it should return `Some`, and the value within the `Some` has the same\n    /// semantics as the return of `FromArgs::redact_arg_values`.\n    fn try_redact_arg_values(\n        command_name: &[&str],\n        args: &[&str],\n    ) -> Option<Result<Vec<String>, EarlyExit>>;\n\n    /// Perform the function of `FromArgs::from_args` for this dynamic command.\n    ///\n    /// The full list of subcommands, ending with the subcommand that should be\n    /// dynamically recognized, is passed in `command_name`. If the command\n    /// passed is not recognized, this function should return `None`. Otherwise\n    /// it should return `Some`, and the value within the `Some` has the same\n    /// semantics as the return of `FromArgs::from_args`.\n    fn try_from_args(command_name: &[&str], args: &[&str]) -> Option<Result<Self, EarlyExit>>;\n}\n\n/// Information to display to the user about why a `FromArgs` construction exited early.\n///\n/// This can occur due to either failed parsing or a flag like `--help`.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct EarlyExit {\n    /// The output to display to the user of the commandline tool.\n    pub output: String,\n    /// If the early exit is caused by help triggers.\n    pub is_help: bool,\n}\n\nimpl From<String> for EarlyExit {\n    fn from(err_msg: String) -> Self {\n        Self {\n            output: err_msg,\n            is_help: false,\n        }\n    }\n}\n\n/// Types which can be constructed from a single commandline value.\n///\n/// Any field type declared in a struct that derives `FromArgs` must implement\n/// this trait. A blanket implementation exists for types implementing\n/// `FromStr<Error: Display>`. Custom types can implement this trait\n/// directly.\npub trait FromArgValue: Sized {\n    /// Construct the type from a commandline value, returning an error string\n    /// on failure.\n    fn from_arg_value(value: &str) -> Result<Self, String>;\n}\n\nimpl<T> FromArgValue for T\nwhere\n    T: FromStr,\n    T::Err: std::fmt::Display,\n{\n    fn from_arg_value(value: &str) -> Result<Self, String> {\n        T::from_str(value).map_err(|x| x.to_string())\n    }\n}\n\n// The following items are all used by the generated code, and should not be considered part\n// of this library's public API surface.\n\n#[doc(hidden)]\npub trait ParseFlag {\n    fn set_flag(&mut self, arg: &str);\n}\n\nimpl<T: Flag> ParseFlag for T {\n    fn set_flag(&mut self, _arg: &str) {\n        <T as Flag>::set_flag(self);\n    }\n}\n\n// A trait for for slots that reserve space for a value and know how to parse that value\n// from a command-line `&str` argument.\n//\n// This trait is only implemented for the type `ParseValueSlotTy`. This indirection is\n// necessary to allow abstracting over `ParseValueSlotTy` instances with different\n// generic parameters.\n#[doc(hidden)]\npub trait ParseValueSlot {\n    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String>;\n}\n\n// The concrete type implementing the `ParseValueSlot` trait.\n//\n// `T` is the type to be parsed from a single string.\n// `Slot` is the type of the container that can hold a value or values of type `T`.\n#[doc(hidden)]\npub struct ParseValueSlotTy<Slot, T> {\n    // The slot for a parsed value.\n    pub slot: Slot,\n    // The function to parse the value from a string\n    pub parse_func: fn(&str, &str) -> Result<T, String>,\n}\n\n// `ParseValueSlotTy<Option<T>, T>` is used as the slot for all non-repeating\n// arguments, both optional and required.\nimpl<T> ParseValueSlot for ParseValueSlotTy<Option<T>, T> {\n    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {\n        if self.slot.is_some() {\n            return Err(\"duplicate values provided\".to_string());\n        }\n        self.slot = Some((self.parse_func)(arg, value)?);\n        Ok(())\n    }\n}\n\n// `ParseValueSlotTy<Vec<T>, T>` is used as the slot for repeating arguments.\nimpl<T> ParseValueSlot for ParseValueSlotTy<Vec<T>, T> {\n    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {\n        self.slot.push((self.parse_func)(arg, value)?);\n        Ok(())\n    }\n}\n\n// `ParseValueSlotTy<Option<Vec<T>>, T>` is used as the slot for optional repeating arguments.\nimpl<T> ParseValueSlot for ParseValueSlotTy<Option<Vec<T>>, T> {\n    fn fill_slot(&mut self, arg: &str, value: &str) -> Result<(), String> {\n        self.slot\n            .get_or_insert_with(Vec::new)\n            .push((self.parse_func)(arg, value)?);\n        Ok(())\n    }\n}\n\n/// A type which can be the receiver of a `Flag`.\npub trait Flag {\n    /// Creates a default instance of the flag value;\n    fn default() -> Self\n    where\n        Self: Sized;\n\n    /// Sets the flag. This function is called when the flag is provided.\n    fn set_flag(&mut self);\n}\n\nimpl Flag for bool {\n    fn default() -> Self {\n        false\n    }\n    fn set_flag(&mut self) {\n        *self = true;\n    }\n}\n\nimpl Flag for Option<bool> {\n    fn default() -> Self {\n        None\n    }\n\n    fn set_flag(&mut self) {\n        *self = Some(true);\n    }\n}\n\nmacro_rules! impl_flag_for_integers {\n    ($($ty:ty,)*) => {\n        $(\n            impl Flag for $ty {\n                fn default() -> Self {\n                    0\n                }\n                fn set_flag(&mut self) {\n                    *self = self.saturating_add(1);\n                }\n            }\n        )*\n    }\n}\n\nimpl_flag_for_integers![u8, u16, u32, u64, u128, i8, i16, i32, i64, i128,];\n\n/// This function implements argument parsing for structs.\n///\n/// `cmd_name`: The identifier for the current command.\n/// `args`: The command line arguments.\n/// `parse_options`: Helper to parse optional arguments.\n/// `parse_positionals`: Helper to parse positional arguments.\n/// `parse_subcommand`: Helper to parse a subcommand.\n/// `help_func`: Generate a help message.\n#[doc(hidden)]\npub fn parse_struct_args(\n    cmd_name: &[&str],\n    args: &[&str],\n    mut parse_options: ParseStructOptions<'_>,\n    mut parse_positionals: ParseStructPositionals<'_>,\n    mut parse_subcommand: Option<ParseStructSubCommand<'_>>,\n) -> Result<(), EarlyExit> {\n    let mut help = false;\n    let mut remaining_args = args;\n    let mut positional_index = 0;\n    let mut options_ended = false;\n\n    'parse_args: while let Some(&next_arg) = remaining_args.first() {\n        remaining_args = &remaining_args[1..];\n        if (parse_options.help_triggers.contains(&next_arg)) && !options_ended {\n            help = true;\n            continue;\n        }\n\n        if next_arg.starts_with('-') && !options_ended {\n            if next_arg == \"--\" {\n                options_ended = true;\n                continue;\n            }\n\n            if help {\n                return Err(\"Trailing arguments are not allowed after `help`.\"\n                    .to_string()\n                    .into());\n            }\n\n            parse_options.parse(next_arg, &mut remaining_args)?;\n            continue;\n        }\n\n        if let Some(ref mut parse_subcommand) = parse_subcommand\n            && parse_subcommand.parse(help, cmd_name, next_arg, remaining_args)?\n        {\n            // Unset `help`, since we handled it in the subcommand\n            help = false;\n            break 'parse_args;\n        }\n\n        options_ended |= parse_positionals.parse(&mut positional_index, next_arg)?;\n    }\n\n    if help {\n        Err(EarlyExit {\n            output: String::new(),\n            is_help: true,\n        })\n    } else {\n        Ok(())\n    }\n}\n\n#[doc(hidden)]\npub struct ParseStructOptions<'a> {\n    /// A mapping from option string literals to the entry\n    /// in the output table. This may contain multiple entries mapping to\n    /// the same location in the table if both a short and long version\n    /// of the option exist (`-z` and `--zoo`).\n    pub arg_to_slot: &'static [(&'static str, usize)],\n\n    /// The storage for argument output data.\n    pub slots: &'a mut [ParseStructOption<'a>],\n\n    /// help triggers is a list of strings that trigger printing of help\n    pub help_triggers: &'a [&'a str],\n}\n\nimpl<'a> ParseStructOptions<'a> {\n    /// Parse a commandline option.\n    ///\n    /// `arg`: the current option argument being parsed (e.g. `--foo`).\n    /// `remaining_args`: the remaining command line arguments. This slice\n    /// will be advanced forwards if the option takes a value argument.\n    fn parse(&mut self, arg: &str, remaining_args: &mut &[&str]) -> Result<(), String> {\n        let pos = self\n            .arg_to_slot\n            .iter()\n            .find_map(|&(name, pos)| if name == arg { Some(pos) } else { None })\n            .ok_or_else(|| unrecognized_argument(arg, self.arg_to_slot, self.help_triggers))?;\n\n        match self.slots[pos] {\n            ParseStructOption::Flag(ref mut b) => b.set_flag(arg),\n            ParseStructOption::Value(ref mut pvs) => {\n                let value = remaining_args\n                    .first()\n                    .ok_or_else(|| [\"No value provided for option '\", arg, \"'.\\n\"].concat())?;\n                *remaining_args = &remaining_args[1..];\n                pvs.fill_slot(arg, value).map_err(|s| {\n                    [\n                        \"Error parsing option '\",\n                        arg,\n                        \"' with value '\",\n                        value,\n                        \"': \",\n                        &s,\n                        \"\\n\",\n                    ]\n                    .concat()\n                })?;\n            }\n        }\n\n        Ok(())\n    }\n}\n\nfn unrecognized_argument(\n    given: &str,\n    arg_to_slot: &[(&str, usize)],\n    extra_suggestions: &[&str],\n) -> String {\n    // get the list of available arguments\n    let available = arg_to_slot\n        .iter()\n        .map(|(name, _pos)| *name)\n        .chain(extra_suggestions.iter().copied())\n        .collect::<Vec<&str>>();\n\n    if available.is_empty() {\n        return format!(\"Unrecognized argument: \\\"{}\\\"\\n\", given);\n    }\n\n    [\"Unrecognized argument: \", given, \"\\n\"].concat()\n}\n\n// `--` or `-` options, including a mutable reference to their value.\n#[doc(hidden)]\npub enum ParseStructOption<'a> {\n    // A flag which is set to `true` when provided.\n    Flag(&'a mut dyn ParseFlag),\n    // A value which is parsed from the string following the `--` argument,\n    // e.g. `--foo bar`.\n    Value(&'a mut dyn ParseValueSlot),\n}\n\n#[doc(hidden)]\npub struct ParseStructPositionals<'a> {\n    pub positionals: &'a mut [ParseStructPositional<'a>],\n    pub last_is_repeating: bool,\n    pub last_is_greedy: bool,\n}\n\nimpl ParseStructPositionals<'_> {\n    /// Parse the next positional argument.\n    ///\n    /// `arg`: the argument supplied by the user.\n    ///\n    /// Returns true if non-positional argument parsing should stop\n    /// after this one.\n    fn parse(&mut self, index: &mut usize, arg: &str) -> Result<bool, EarlyExit> {\n        if *index < self.positionals.len() {\n            self.positionals[*index].parse(arg)?;\n\n            if self.last_is_repeating && *index == self.positionals.len() - 1 {\n                // Don't increment position if we're at the last arg\n                // *and* the last arg is repeating. If it's also remainder,\n                // halt non-option processing after this.\n                Ok(self.last_is_greedy)\n            } else {\n                // If it is repeating, though, increment the index and continue\n                // processing options.\n                *index += 1;\n                Ok(false)\n            }\n        } else {\n            Err(EarlyExit {\n                output: unrecognized_arg(arg),\n                is_help: false,\n            })\n        }\n    }\n}\n\n#[doc(hidden)]\npub struct ParseStructPositional<'a> {\n    // The positional's name\n    pub name: &'static str,\n\n    // The function to parse the positional.\n    pub slot: &'a mut dyn ParseValueSlot,\n}\n\nimpl ParseStructPositional<'_> {\n    /// Parse a positional argument.\n    ///\n    /// `arg`: the argument supplied by the user.\n    fn parse(&mut self, arg: &str) -> Result<(), EarlyExit> {\n        self.slot.fill_slot(\"\", arg).map_err(|s| {\n            [\n                \"Error parsing positional argument '\",\n                self.name,\n                \"' with value '\",\n                arg,\n                \"': \",\n                &s,\n                \"\\n\",\n            ]\n            .concat()\n            .into()\n        })\n    }\n}\n\n// A type to simplify parsing struct subcommands.\n//\n// This indirection is necessary to allow abstracting over `FromArgs` instances with different\n// generic parameters.\n#[doc(hidden)]\npub struct ParseStructSubCommand<'a> {\n    // The subcommand commands\n    pub subcommands: &'static [&'static CommandInfo],\n\n    pub dynamic_subcommands: &'a [&'static CommandInfo],\n\n    // The function to parse the subcommand arguments.\n    #[allow(clippy::type_complexity)]\n    pub parse_func: &'a mut dyn FnMut(&[&str], &[&str]) -> Result<(), EarlyExit>,\n}\n\nimpl ParseStructSubCommand<'_> {\n    fn parse(\n        &mut self,\n        help: bool,\n        cmd_name: &[&str],\n        arg: &str,\n        remaining_args: &[&str],\n    ) -> Result<bool, EarlyExit> {\n        for subcommand in self\n            .subcommands\n            .iter()\n            .chain(self.dynamic_subcommands.iter())\n        {\n            if subcommand.name == arg {\n                let mut command = cmd_name.to_owned();\n                command.push(subcommand.name);\n                let prepended_help;\n                let remaining_args = if help {\n                    prepended_help = prepend_help(remaining_args);\n                    &prepended_help\n                } else {\n                    remaining_args\n                };\n\n                (self.parse_func)(&command, remaining_args)?;\n\n                return Ok(true);\n            }\n        }\n\n        Ok(false)\n    }\n}\n\n// Prepend `help` to a list of arguments.\n// This is used to pass the `help` argument on to subcommands.\nfn prepend_help<'a>(args: &[&'a str]) -> Vec<&'a str> {\n    [&[\"help\"], args].concat()\n}\n\n#[doc(hidden)]\npub fn print_subcommands<'a>(commands: impl Iterator<Item = &'a CommandInfo>) -> String {\n    let mut out = String::new();\n    for cmd in commands {\n        argh_shared::write_description(&mut out, cmd);\n    }\n    out\n}\n\nfn unrecognized_arg(arg: &str) -> String {\n    [\"Unrecognized argument: \", arg, \"\\n\"].concat()\n}\n\n// An error string builder to report missing required options and subcommands.\n#[doc(hidden)]\n#[derive(Default)]\npub struct MissingRequirements {\n    options: Vec<&'static str>,\n    subcommands: Option<Vec<&'static CommandInfo>>,\n    positional_args: Vec<&'static str>,\n}\n\nconst NEWLINE_INDENT: &str = \"\\n    \";\n\nimpl MissingRequirements {\n    // Add a missing required option.\n    #[doc(hidden)]\n    pub fn missing_option(&mut self, name: &'static str) {\n        self.options.push(name)\n    }\n\n    // Add a missing required subcommand.\n    #[doc(hidden)]\n    pub fn missing_subcommands(&mut self, commands: impl Iterator<Item = &'static CommandInfo>) {\n        self.subcommands = Some(commands.collect());\n    }\n\n    // Add a missing positional argument.\n    #[doc(hidden)]\n    pub fn missing_positional_arg(&mut self, name: &'static str) {\n        self.positional_args.push(name)\n    }\n\n    // If any missing options or subcommands were provided, returns an error string\n    // describing the missing args.\n    #[doc(hidden)]\n    pub fn err_on_any(&self) -> Result<(), String> {\n        if self.options.is_empty() && self.subcommands.is_none() && self.positional_args.is_empty()\n        {\n            return Ok(());\n        }\n\n        let mut output = String::new();\n\n        if !self.positional_args.is_empty() {\n            output.push_str(\"Required positional arguments not provided:\");\n            for arg in &self.positional_args {\n                output.push_str(NEWLINE_INDENT);\n                output.push_str(arg);\n            }\n        }\n\n        if !self.options.is_empty() {\n            if !self.positional_args.is_empty() {\n                output.push('\\n');\n            }\n            output.push_str(\"Required options not provided:\");\n            for option in &self.options {\n                output.push_str(NEWLINE_INDENT);\n                output.push_str(option);\n            }\n        }\n\n        if let Some(missing_subcommands) = &self.subcommands {\n            if !self.options.is_empty() {\n                output.push('\\n');\n            }\n            output.push_str(\"One of the following subcommands must be present:\");\n            output.push_str(NEWLINE_INDENT);\n            output.push_str(\"help\");\n            for subcommand in missing_subcommands {\n                output.push_str(NEWLINE_INDENT);\n                output.push_str(subcommand.name);\n            }\n        }\n\n        output.push('\\n');\n\n        Err(output)\n    }\n}\n\nmod argh_shared {\n    //! Shared functionality between argh_derive and the argh runtime.\n    //!\n    //! This library is intended only for internal use by these two crates.\n\n    /// Information about a particular command used for output.\n    pub struct CommandInfo<'a> {\n        /// The name of the command.\n        pub name: &'a str,\n        /// A short description of the command's functionality.\n        pub description: &'a str,\n    }\n\n    /// Information about the command line arguments for a given command.\n    #[derive(Debug, Default, PartialEq, Eq, Clone)]\n    pub struct CommandInfoWithArgs<'a> {\n        /// The name of the command.\n        pub name: &'a str,\n        /// A short description of the command's functionality.\n        pub description: &'a str,\n        /// Examples of usage\n        pub examples: &'a [&'a str],\n        /// Flags\n        pub flags: &'a [FlagInfo<'a>],\n        /// Notes about usage\n        pub notes: &'a [&'a str],\n        /// The subcommands.\n        pub commands: Vec<SubCommandInfo<'a>>,\n        /// Positional args\n        pub positionals: &'a [PositionalInfo<'a>],\n        /// Error code information\n        pub error_codes: &'a [ErrorCodeInfo<'a>],\n    }\n\n    /// Information about a documented error code.\n    #[derive(Debug, PartialEq, Eq)]\n    pub struct ErrorCodeInfo<'a> {\n        /// The code value.\n        pub code: i32,\n        /// Short description about what this code indicates.\n        pub description: &'a str,\n    }\n\n    /// Information about positional arguments\n    #[derive(Debug, PartialEq, Eq)]\n    pub struct PositionalInfo<'a> {\n        /// Name of the argument.\n        pub name: &'a str,\n        /// Description of the argument.\n        pub description: &'a str,\n        /// Optionality of the argument.\n        pub optionality: Optionality,\n        /// Visibility in the help for this argument.\n        /// `false` indicates this argument will not appear\n        /// in the help message.\n        pub hidden: bool,\n    }\n\n    /// Information about a subcommand.\n    /// Dynamic subcommands do not implement\n    /// get_args_info(), so the command field\n    /// only contains the name and description.\n    #[derive(Debug, Default, PartialEq, Eq, Clone)]\n    pub struct SubCommandInfo<'a> {\n        /// The subcommand name.\n        pub name: &'a str,\n        /// The information about the subcommand.\n        pub command: CommandInfoWithArgs<'a>,\n    }\n\n    /// Information about a flag or option.\n    #[derive(Debug, Default, PartialEq, Eq)]\n    pub struct FlagInfo<'a> {\n        /// The kind of flag.\n        pub kind: FlagInfoKind<'a>,\n        /// The optionality of the flag.\n        pub optionality: Optionality,\n        /// The long string of the flag.\n        pub long: &'a str,\n        /// The single character short indicator\n        /// for this flag.\n        pub short: Option<char>,\n        /// The description of the flag.\n        pub description: &'a str,\n        /// Visibility in the help for this argument.\n        /// `false` indicates this argument will not appear\n        /// in the help message.\n        pub hidden: bool,\n    }\n\n    /// The kind of flags.\n    #[derive(Debug, Default, PartialEq, Eq)]\n    pub enum FlagInfoKind<'a> {\n        /// switch represents a boolean flag,\n        #[default]\n        Switch,\n        /// option is a flag that also has an associated\n        /// value. This value is named `arg_name`.\n        Option { arg_name: &'a str },\n    }\n\n    /// The optionality defines the requirements related\n    /// to the presence of the argument on the command line.\n    #[derive(Debug, Default, PartialEq, Eq)]\n    pub enum Optionality {\n        /// Required indicates the argument is required\n        /// exactly once.\n        #[default]\n        Required,\n        /// Optional indicates the argument may or may not\n        /// be present.\n        Optional,\n        /// Repeating indicates the argument may appear zero\n        /// or more times.\n        Repeating,\n        /// Greedy is used for positional arguments which\n        /// capture the all command line input up to the next flag or\n        /// the end of the input.\n        Greedy,\n    }\n\n    pub const INDENT: &str = \"  \";\n    const DESCRIPTION_INDENT: usize = 20;\n    const WRAP_WIDTH: usize = 80;\n\n    /// Write command names and descriptions to an output string.\n    pub fn write_description(out: &mut String, cmd: &CommandInfo<'_>) {\n        let mut current_line = INDENT.to_string();\n        current_line.push_str(cmd.name);\n\n        if cmd.description.is_empty() {\n            new_line(&mut current_line, out);\n            return;\n        }\n\n        if !indent_description(&mut current_line) {\n            // Start the description on a new line if the flag names already\n            // add up to more than DESCRIPTION_INDENT.\n            new_line(&mut current_line, out);\n        }\n\n        let mut words = cmd.description.split(' ').peekable();\n        while let Some(first_word) = words.next() {\n            indent_description(&mut current_line);\n            current_line.push_str(first_word);\n\n            'inner: while let Some(&word) = words.peek() {\n                if (char_len(&current_line) + char_len(word) + 1) > WRAP_WIDTH {\n                    new_line(&mut current_line, out);\n                    break 'inner;\n                } else {\n                    // advance the iterator\n                    let _ = words.next();\n                    current_line.push(' ');\n                    current_line.push_str(word);\n                }\n            }\n        }\n        new_line(&mut current_line, out);\n    }\n\n    // Indent the current line in to DESCRIPTION_INDENT chars.\n    // Returns a boolean indicating whether or not spacing was added.\n    fn indent_description(line: &mut String) -> bool {\n        let cur_len = char_len(line);\n        if cur_len < DESCRIPTION_INDENT {\n            let num_spaces = DESCRIPTION_INDENT - cur_len;\n            line.extend(std::iter::repeat_n(' ', num_spaces));\n            true\n        } else {\n            false\n        }\n    }\n\n    fn char_len(s: &str) -> usize {\n        s.chars().count()\n    }\n\n    // Append a newline and the current line to the output,\n    // clearing the current line.\n    fn new_line(current_line: &mut String, out: &mut String) {\n        out.push('\\n');\n        out.push_str(current_line);\n        current_line.truncate(0);\n    }\n}\n"
  },
  {
    "path": "native/src/base/base.cpp",
    "content": "#include <sys/wait.h>\n#include <sys/prctl.h>\n#include <sys/mman.h>\n#include <android/log.h>\n#include <linux/fs.h>\n#include <syscall.h>\n\n#include <base.hpp>\n#include <flags.h>\n\nusing namespace std;\n\n#ifndef __call_bypassing_fortify\n#define __call_bypassing_fortify(fn) (&fn)\n#endif\n\n#ifdef __LP64__\nstatic_assert(BLKGETSIZE64 == 0x80081272);\n#else\nstatic_assert(BLKGETSIZE64 == 0x80041272);\n#endif\n\n// Override libc++ new implementation to optimize final build size\n\nvoid* operator new(std::size_t s) { return std::malloc(s); }\nvoid* operator new[](std::size_t s) { return std::malloc(s); }\nvoid  operator delete(void *p) { std::free(p); }\nvoid  operator delete[](void *p) { std::free(p); }\nvoid* operator new(std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }\nvoid* operator new[](std::size_t s, const std::nothrow_t&) noexcept { return std::malloc(s); }\nvoid  operator delete(void *p, const std::nothrow_t&) noexcept { std::free(p); }\nvoid  operator delete[](void *p, const std::nothrow_t&) noexcept { std::free(p); }\n\nrust::Vec<size_t> byte_data::patch(byte_view from, byte_view to) const {\n    rust::Vec<size_t> v;\n    if (ptr == nullptr)\n        return v;\n    auto p = ptr;\n    auto eof = ptr + sz;\n    while (p < eof) {\n        p = static_cast<uint8_t *>(memmem(p, eof - p, from.data(), from.size()));\n        if (p == nullptr)\n            return v;\n        memset(p, 0, from.size());\n        memcpy(p, to.data(), to.size());\n        v.push_back(p - ptr);\n        p += from.size();\n    }\n    return v;\n}\n\nrust::Vec<size_t> mut_u8_patch(MutByteSlice buf, ByteSlice from, ByteSlice to) {\n    byte_data data(buf);\n    return data.patch(from, to);\n}\n\nint fork_dont_care() {\n    if (int pid = xfork()) {\n        waitpid(pid, nullptr, 0);\n        return pid;\n    } else if (xfork()) {\n        exit(0);\n    }\n    return 0;\n}\n\nint fork_no_orphan() {\n    int pid = xfork();\n    if (pid)\n        return pid;\n    prctl(PR_SET_PDEATHSIG, SIGKILL);\n    if (getppid() == 1)\n        exit(1);\n    return 0;\n}\n\nint exec_command(exec_t &exec) {\n    auto pipefd = array<int, 2>{-1, -1};\n    int outfd = -1;\n\n    if (exec.fd == -1) {\n        if (xpipe2(pipefd, O_CLOEXEC) == -1)\n            return -1;\n        outfd = pipefd[1];\n    } else if (exec.fd >= 0) {\n        outfd = exec.fd;\n    }\n\n    int pid = exec.fork();\n    if (pid < 0) {\n        close(pipefd[0]);\n        close(pipefd[1]);\n        return -1;\n    } else if (pid) {\n        if (exec.fd == -1) {\n            exec.fd = pipefd[0];\n            close(pipefd[1]);\n        }\n        return pid;\n    }\n\n    // Unblock all signals\n    sigset_t set;\n    sigfillset(&set);\n    pthread_sigmask(SIG_UNBLOCK, &set, nullptr);\n\n    if (outfd >= 0) {\n        xdup2(outfd, STDOUT_FILENO);\n        if (exec.err)\n            xdup2(outfd, STDERR_FILENO);\n        close(outfd);\n    }\n\n    // Call the pre-exec callback\n    if (exec.pre_exec)\n        exec.pre_exec();\n\n    execve(exec.argv[0], (char **) exec.argv, environ);\n    PLOGE(\"execve %s\", exec.argv[0]);\n    exit(-1);\n}\n\nint exec_command_sync(exec_t &exec) {\n    int pid = exec_command(exec);\n    if (pid < 0)\n        return -1;\n    int status;\n    waitpid(pid, &status, 0);\n    return WEXITSTATUS(status);\n}\n\nint new_daemon_thread(thread_entry entry, void *arg) {\n    pthread_t thread;\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);\n    errno = pthread_create(&thread, &attr, entry, arg);\n    if (errno) {\n        PLOGE(\"pthread_create\");\n    }\n    return errno;\n}\n\nstatic char *argv0;\nstatic size_t name_len;\nvoid init_argv0(int argc, char **argv) {\n    argv0 = argv[0];\n    name_len = (argv[argc - 1] - argv[0]) + strlen(argv[argc - 1]) + 1;\n}\n\nvoid set_nice_name(Utf8CStr name) {\n    memset(argv0, 0, name_len);\n    strscpy(argv0, name.c_str(), name_len);\n    prctl(PR_SET_NAME, name.c_str());\n}\n\ntemplate<typename T, int base>\nstatic T parse_num(string_view s) {\n    T val = 0;\n    for (char c : s) {\n        if (isdigit(c)) {\n            c -= '0';\n        } else if (base > 10 && isalpha(c)) {\n            c -= isupper(c) ? 'A' - 10 : 'a' - 10;\n        } else {\n            return -1;\n        }\n        if (c >= base) {\n            return -1;\n        }\n        val *= base;\n        val += c;\n    }\n    return val;\n}\n\n/*\n * Bionic's atoi runs through strtol().\n * Use our own implementation for faster conversion.\n */\nint parse_int(string_view s) {\n    return parse_num<int, 10>(s);\n}\n\nuint32_t parse_uint32_hex(string_view s) {\n    return parse_num<uint32_t, 16>(s);\n}\n\nint switch_mnt_ns(int pid) {\n    int ret = -1;\n    int fd = syscall(__NR_pidfd_open, pid, 0);\n    if (fd > 0) {\n        ret = setns(fd, CLONE_NEWNS);\n        close(fd);\n    }\n    if (ret < 0) {\n        char mnt[32];\n        ssprintf(mnt, sizeof(mnt), \"/proc/%d/ns/mnt\", pid);\n        fd = open(mnt, O_RDONLY);\n        if (fd < 0) return 1; // Maybe process died..\n\n        // Switch to its namespace\n        ret = xsetns(fd, 0);\n        close(fd);\n    }\n    return ret;\n}\n\nstring &replace_all(string &str, string_view from, string_view to) {\n    size_t pos = 0;\n    while((pos = str.find(from, pos)) != string::npos) {\n        str.replace(pos, from.length(), to);\n        pos += to.length();\n    }\n    return str;\n}\n\ntemplate <typename T>\nstatic auto split_impl(string_view s, string_view delims) {\n    vector<T> result;\n    size_t base = 0;\n    size_t found;\n    while (true) {\n        found = s.find_first_of(delims, base);\n        result.emplace_back(s.substr(base, found - base));\n        if (found == string::npos)\n            break;\n        base = found + 1;\n    }\n    return result;\n}\n\nvector<string> split(string_view s, string_view delims) {\n    return split_impl<string>(s, delims);\n}\n\n#undef vsnprintf\nint vssprintf(char *dest, size_t size, const char *fmt, va_list ap) {\n    if (size > 0) {\n        *dest = 0;\n        return std::min(vsnprintf(dest, size, fmt, ap), (int) size - 1);\n    }\n    return -1;\n}\n\nint ssprintf(char *dest, size_t size, const char *fmt, ...) {\n    va_list va;\n    va_start(va, fmt);\n    int r = vssprintf(dest, size, fmt, va);\n    va_end(va);\n    return r;\n}\n\n#undef strlcpy\nsize_t strscpy(char *dest, const char *src, size_t size) {\n    return std::min(strlcpy(dest, src, size), size - 1);\n}\n\n#undef vsnprintf\nstatic int fmt_and_log_with_rs(LogLevel level, const char *fmt, va_list ap) {\n    constexpr int sz = 4096;\n    char buf[sz];\n    buf[0] = '\\0';\n    // Fortify logs when a fatal error occurs. Do not run through fortify again\n    int len = std::min(__call_bypassing_fortify(vsnprintf)(buf, sz, fmt, ap), sz - 1);\n    log_with_rs(level, Utf8CStr(buf, len + 1));\n    return len;\n}\n\n// Used to override external C library logging\nextern \"C\" int magisk_log_print(int prio, const char *tag, const char *fmt, ...) {\n    LogLevel level;\n    switch (prio) {\n    case ANDROID_LOG_DEBUG:\n        level = LogLevel::Debug;\n        break;\n    case ANDROID_LOG_INFO:\n        level = LogLevel::Info;\n        break;\n    case ANDROID_LOG_WARN:\n        level = LogLevel::Warn;\n        break;\n    case ANDROID_LOG_ERROR:\n        level = LogLevel::Error;\n        break;\n    default:\n        return 0;\n    }\n\n    char fmt_buf[4096];\n    auto len = strscpy(fmt_buf, tag, sizeof(fmt_buf) - 1);\n    // Prevent format specifications in the tag\n    std::replace(fmt_buf, fmt_buf + len, '%', '_');\n    len = ssprintf(fmt_buf + len, sizeof(fmt_buf) - len - 1, \": %s\", fmt) + len;\n    // Ensure the fmt string always ends with newline\n    if (fmt_buf[len - 1] != '\\n') {\n        fmt_buf[len] = '\\n';\n        fmt_buf[len + 1] = '\\0';\n    }\n    va_list argv;\n    va_start(argv, fmt);\n    int ret = fmt_and_log_with_rs(level, fmt_buf, argv);\n    va_end(argv);\n    return ret;\n}\n\n#define LOG_BODY(level)   \\\n    va_list argv;         \\\n    va_start(argv, fmt);  \\\n    fmt_and_log_with_rs(LogLevel::level, fmt, argv); \\\n    va_end(argv);         \\\n\n// LTO will optimize out the NOP function\n#if MAGISK_DEBUG\nvoid LOGD(const char *fmt, ...) { LOG_BODY(Debug) }\n#else\nvoid LOGD(const char *fmt, ...) {}\n#endif\nvoid LOGI(const char *fmt, ...) { LOG_BODY(Info) }\nvoid LOGW(const char *fmt, ...) { LOG_BODY(Warn) }\nvoid LOGE(const char *fmt, ...) { LOG_BODY(Error) }\n\n// Export raw symbol to fortify compat\nextern \"C\" void __vloge(const char* fmt, va_list ap) {\n    fmt_and_log_with_rs(LogLevel::Error, fmt, ap);\n}\n\nstring full_read(int fd) {\n    string str;\n    char buf[4096];\n    for (ssize_t len; (len = xread(fd, buf, sizeof(buf))) > 0;)\n        str.insert(str.end(), buf, buf + len);\n    return str;\n}\n\nstring full_read(const char *filename) {\n    string str;\n    if (int fd = xopen(filename, O_RDONLY | O_CLOEXEC); fd >= 0) {\n        str = full_read(fd);\n        close(fd);\n    }\n    return str;\n}\n\nvoid write_zero(int fd, size_t size) {\n    char buf[4096] = {0};\n    size_t len;\n    while (size > 0) {\n        len = sizeof(buf) > size ? size : sizeof(buf);\n        write(fd, buf, len);\n        size -= len;\n    }\n}\n\nsDIR make_dir(DIR *dp) {\n    return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });\n}\n\nsFILE make_file(FILE *fp) {\n    return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });\n}\n\nmmap_data::mmap_data(const char *name, bool rw) {\n    auto slice = rust::map_file(name, rw);\n    if (!slice.empty()) {\n        this->ptr = slice.data();\n        this->sz = slice.size();\n    }\n}\n\nmmap_data::mmap_data(int dirfd, const char *name, bool rw) {\n    auto slice = rust::map_file_at(dirfd, name, rw);\n    if (!slice.empty()) {\n        this->ptr = slice.data();\n        this->sz = slice.size();\n    }\n}\n\nmmap_data::mmap_data(int fd, size_t sz, bool rw) {\n    auto slice = rust::map_fd(fd, sz, rw);\n    if (!slice.empty()) {\n        this->ptr = slice.data();\n        this->sz = slice.size();\n    }\n}\n\nmmap_data::~mmap_data() {\n    if (ptr) munmap(ptr, sz);\n}\n\nvoid mmap_data::swap(mmap_data &o) {\n    std::swap(ptr, o.ptr);\n    std::swap(sz, o.sz);\n}\n\nstring resolve_preinit_dir(const char *base_dir) {\n    string dir = base_dir;\n    if (access((dir + \"/unencrypted\").data(), F_OK) == 0) {\n        dir += \"/unencrypted/magisk\";\n    } else if (access((dir + \"/adb\").data(), F_OK) == 0) {\n        dir += \"/adb\";\n    } else if (access((dir + \"/watchdog\").data(), F_OK) == 0) {\n        dir += \"/watchdog/magisk\";\n    } else {\n        dir += \"/magisk\";\n    }\n    return dir;\n}\n\n// FFI for Utf8CStr\n\nextern \"C\" void cxx$utf8str$new(Utf8CStr *self, const void *s, size_t len);\nextern \"C\" const char *cxx$utf8str$ptr(const Utf8CStr *self);\nextern \"C\" size_t cxx$utf8str$len(const Utf8CStr *self);\n\nUtf8CStr::Utf8CStr(const char *s, size_t len) : repr{} {\n    cxx$utf8str$new(this, s, len);\n}\n\nconst char *Utf8CStr::data() const {\n    return cxx$utf8str$ptr(this);\n}\n\nsize_t Utf8CStr::length() const {\n    return cxx$utf8str$len(this);\n}\n"
  },
  {
    "path": "native/src/base/build.rs",
    "content": "use crate::codegen::gen_cxx_binding;\n\n#[path = \"../include/codegen.rs\"]\nmod codegen;\n\nfn main() {\n    gen_cxx_binding(\"base-rs\");\n}\n"
  },
  {
    "path": "native/src/base/cstr.rs",
    "content": "use cxx::{ExternType, type_id};\nuse libc::c_char;\nuse nix::NixPath;\nuse std::borrow::Borrow;\nuse std::cmp::{Ordering, min};\nuse std::ffi::{CStr, FromBytesUntilNulError, FromBytesWithNulError, OsStr};\nuse std::fmt::{Debug, Display, Formatter, Write};\nuse std::ops::Deref;\nuse std::os::unix::ffi::OsStrExt;\nuse std::path::{Path, PathBuf};\nuse std::str::{FromStr, Utf8Error};\nuse std::{fmt, mem, slice, str};\nuse thiserror::Error;\n\nuse crate::slice_from_ptr_mut;\n\n// Utf8CStr types are UTF-8 validated and null terminated strings.\n//\n// Several Utf8CStr types:\n//\n// Utf8CStr: can only exist as reference, similar to &str\n// Utf8CString: dynamically sized buffer allocated on the heap, similar to String\n// Utf8CStrBufRef: reference to a fixed sized buffer\n// Utf8CStrBufArr<N>: fixed sized buffer allocated on the stack\n//\n// For easier usage, please use the helper functions in cstr::buf.\n//\n// In most cases, these are the types being used\n//\n// &Utf8CStr: whenever a printable null terminated string is needed\n// &mut dyn Utf8CStrBuf: whenever we need a buffer that needs to support appending\n//                       strings to the end, and has to be null terminated\n// &mut dyn Utf8CStrBuf: whenever we need a pre-allocated buffer that is large enough to fit\n//                       in the result, and has to be null terminated\n//\n// All types dereferences to &Utf8CStr.\n// Utf8CString, Utf8CStrBufRef, and Utf8CStrBufArr<N> implements Utf8CStrBuf.\n\n// Public helper functions\n\npub mod buf {\n    use super::{Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString};\n\n    #[inline(always)]\n    pub fn dynamic(capacity: usize) -> Utf8CString {\n        Utf8CString::with_capacity(capacity)\n    }\n\n    #[inline(always)]\n    pub fn default() -> Utf8CStrBufArr<4096> {\n        Utf8CStrBufArr::default()\n    }\n\n    #[inline(always)]\n    pub fn new<const N: usize>() -> Utf8CStrBufArr<N> {\n        Utf8CStrBufArr::new()\n    }\n\n    #[inline(always)]\n    pub fn wrap(buf: &mut [u8]) -> Utf8CStrBufRef<'_> {\n        Utf8CStrBufRef::from(buf)\n    }\n\n    #[inline(always)]\n    pub unsafe fn wrap_ptr<'a>(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {\n        unsafe { Utf8CStrBufRef::from_ptr(buf, len) }\n    }\n}\n\n// Trait definitions\n\npub trait Utf8CStrBuf: Display + Write + AsRef<Utf8CStr> + Deref<Target = Utf8CStr> {\n    // The length of the string without the terminating null character.\n    // assert_true(len <= capacity - 1)\n    fn len(&self) -> usize;\n    fn push_str(&mut self, s: &str) -> usize;\n    // The capacity of the internal buffer. The maximum string length this buffer can contain\n    // is capacity - 1, because the last byte is reserved for the terminating null character.\n    fn capacity(&self) -> usize;\n    fn clear(&mut self);\n    fn as_mut_ptr(&mut self) -> *mut c_char;\n    fn truncate(&mut self, new_len: usize);\n    // Rebuild the Utf8CStr based on the contents of the internal buffer. Required after any\n    // unsafe modifications directly though the pointer obtained from self.as_mut_ptr().\n    // If an error is returned, the internal buffer will be reset, resulting in an empty string.\n    fn rebuild(&mut self) -> Result<(), StrErr>;\n\n    #[inline(always)]\n    fn is_empty(&self) -> bool {\n        self.len() == 0\n    }\n}\n\npub trait StringExt {\n    fn nul_terminate(&mut self) -> &mut [u8];\n}\n\nimpl StringExt for String {\n    fn nul_terminate(&mut self) -> &mut [u8] {\n        self.reserve(1);\n        // SAFETY: the string is reserved to have enough capacity to fit in the null byte\n        // SAFETY: the null byte is explicitly added outside the string's length\n        unsafe {\n            let buf = slice::from_raw_parts_mut(self.as_mut_ptr(), self.len() + 1);\n            *buf.get_unchecked_mut(self.len()) = b'\\0';\n            buf\n        }\n    }\n}\n\nimpl StringExt for PathBuf {\n    #[allow(mutable_transmutes)]\n    fn nul_terminate(&mut self) -> &mut [u8] {\n        self.reserve(1);\n        // SAFETY: the PathBuf is reserved to have enough capacity to fit in the null byte\n        // SAFETY: the null byte is explicitly added outside the PathBuf's length\n        unsafe {\n            let bytes: &mut [u8] = mem::transmute(self.as_mut_os_str().as_bytes());\n            let buf = slice::from_raw_parts_mut(bytes.as_mut_ptr(), bytes.len() + 1);\n            *buf.get_unchecked_mut(bytes.len()) = b'\\0';\n            buf\n        }\n    }\n}\n\npub struct Utf8CString(String);\n\nimpl Default for Utf8CString {\n    fn default() -> Self {\n        Utf8CString::with_capacity(256)\n    }\n}\n\nimpl Utf8CString {\n    pub fn with_capacity(capacity: usize) -> Utf8CString {\n        Utf8CString::from(String::with_capacity(capacity))\n    }\n\n    pub fn ensure_capacity(&mut self, capacity: usize) {\n        if self.capacity() >= capacity {\n            return;\n        }\n        self.0.reserve(capacity - self.0.len())\n    }\n}\n\nimpl AsRef<Utf8CStr> for Utf8CString {\n    #[inline(always)]\n    fn as_ref(&self) -> &Utf8CStr {\n        // SAFETY: the internal string is always null terminated\n        unsafe { mem::transmute(slice::from_raw_parts(self.0.as_ptr(), self.0.len() + 1)) }\n    }\n}\n\nimpl Utf8CStrBuf for Utf8CString {\n    #[inline(always)]\n    fn len(&self) -> usize {\n        self.0.len()\n    }\n\n    fn push_str(&mut self, s: &str) -> usize {\n        self.0.push_str(s);\n        self.0.nul_terminate();\n        s.len()\n    }\n\n    fn capacity(&self) -> usize {\n        self.0.capacity()\n    }\n\n    fn clear(&mut self) {\n        self.0.clear();\n        self.0.nul_terminate();\n    }\n\n    fn as_mut_ptr(&mut self) -> *mut c_char {\n        self.0.as_mut_ptr().cast()\n    }\n\n    fn truncate(&mut self, new_len: usize) {\n        self.0.truncate(new_len);\n        self.0.nul_terminate();\n    }\n\n    fn rebuild(&mut self) -> Result<(), StrErr> {\n        // Temporarily move the internal String out\n        let mut tmp = String::new();\n        mem::swap(&mut tmp, &mut self.0);\n        let (ptr, _, capacity) = tmp.into_raw_parts();\n\n        unsafe {\n            // Validate the entire buffer, including the unused part\n            let bytes = slice::from_raw_parts(ptr, capacity);\n            match Utf8CStr::from_bytes_until_nul(bytes) {\n                Ok(s) => {\n                    // Move the String with the new length back\n                    self.0 = String::from_raw_parts(ptr, s.len(), capacity);\n                }\n                Err(e) => {\n                    // Move the String with 0 length back\n                    self.0 = String::from_raw_parts(ptr, 0, capacity);\n                    self.0.nul_terminate();\n                    return Err(e);\n                }\n            }\n        }\n\n        Ok(())\n    }\n}\n\nimpl From<String> for Utf8CString {\n    fn from(mut value: String) -> Self {\n        value.nul_terminate();\n        Utf8CString(value)\n    }\n}\n\nimpl From<&str> for Utf8CString {\n    fn from(value: &str) -> Self {\n        let mut s = String::with_capacity(value.len() + 1);\n        s.push_str(value);\n        s.nul_terminate();\n        Utf8CString(s)\n    }\n}\n\nimpl FromStr for Utf8CString {\n    type Err = String;\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        Ok(s.into())\n    }\n}\n\nimpl Borrow<Utf8CStr> for Utf8CString {\n    fn borrow(&self) -> &Utf8CStr {\n        self.deref()\n    }\n}\n\n// UTF-8 validated + null terminated reference to buffer\npub struct Utf8CStrBufRef<'a> {\n    used: usize,\n    buf: &'a mut [u8],\n}\n\nimpl<'a> Utf8CStrBufRef<'a> {\n    pub unsafe fn from_ptr(buf: *mut u8, len: usize) -> Utf8CStrBufRef<'a> {\n        unsafe { Self::from(slice_from_ptr_mut(buf, len)) }\n    }\n}\n\nimpl<'a> From<&'a mut [u8]> for Utf8CStrBufRef<'a> {\n    fn from(buf: &'a mut [u8]) -> Utf8CStrBufRef<'a> {\n        buf[0] = b'\\0';\n        Utf8CStrBufRef { used: 0, buf }\n    }\n}\n\n// UTF-8 validated + null terminated buffer on the stack\npub struct Utf8CStrBufArr<const N: usize> {\n    used: usize,\n    buf: [u8; N],\n}\n\nimpl<const N: usize> Utf8CStrBufArr<N> {\n    pub fn new() -> Self {\n        Utf8CStrBufArr {\n            used: 0,\n            buf: [0; N],\n        }\n    }\n}\n\nimpl Default for Utf8CStrBufArr<4096> {\n    fn default() -> Self {\n        Utf8CStrBufArr::<4096>::new()\n    }\n}\n\n#[derive(Debug, Error)]\npub enum StrErr {\n    #[error(transparent)]\n    Utf8Error(#[from] Utf8Error),\n    #[error(transparent)]\n    CStrWithNullError(#[from] FromBytesWithNulError),\n    #[error(transparent)]\n    CStrUntilNullError(#[from] FromBytesUntilNulError),\n    #[error(\"argument is null\")]\n    NullPointerError,\n}\n\n// UTF-8 validated + null terminated string slice\n#[repr(transparent)]\npub struct Utf8CStr([u8]);\n\nimpl Utf8CStr {\n    pub fn from_cstr(cstr: &CStr) -> Result<&Utf8CStr, StrErr> {\n        // Validate the buffer during construction\n        str::from_utf8(cstr.to_bytes())?;\n        Ok(unsafe { Self::from_bytes_unchecked(cstr.to_bytes_with_nul()) })\n    }\n\n    fn from_bytes_until_nul(bytes: &[u8]) -> Result<&Utf8CStr, StrErr> {\n        Self::from_cstr(CStr::from_bytes_until_nul(bytes)?)\n    }\n\n    pub fn from_bytes(bytes: &[u8]) -> Result<&Utf8CStr, StrErr> {\n        Self::from_cstr(CStr::from_bytes_with_nul(bytes)?)\n    }\n\n    pub fn from_string(s: &mut String) -> &Utf8CStr {\n        let buf = s.nul_terminate();\n        // SAFETY: the null byte is explicitly added to the buffer\n        unsafe { mem::transmute(buf) }\n    }\n\n    #[inline(always)]\n    pub const unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Utf8CStr {\n        unsafe { mem::transmute(bytes) }\n    }\n\n    pub unsafe fn from_ptr<'a>(ptr: *const c_char) -> Result<&'a Utf8CStr, StrErr> {\n        if ptr.is_null() {\n            return Err(StrErr::NullPointerError);\n        }\n        Self::from_cstr(unsafe { CStr::from_ptr(ptr) })\n    }\n\n    pub unsafe fn from_ptr_unchecked<'a>(ptr: *const c_char) -> &'a Utf8CStr {\n        unsafe {\n            let cstr = CStr::from_ptr(ptr);\n            Self::from_bytes_unchecked(cstr.to_bytes_with_nul())\n        }\n    }\n\n    pub unsafe fn from_raw_parts<'a>(ptr: *const c_char, len: usize) -> &'a Utf8CStr {\n        unsafe {\n            let bytes = slice::from_raw_parts(ptr.cast(), len);\n            Self::from_bytes_unchecked(bytes)\n        }\n    }\n\n    #[inline(always)]\n    pub fn as_bytes_with_nul(&self) -> &[u8] {\n        &self.0\n    }\n\n    #[inline(always)]\n    pub fn as_ptr(&self) -> *const c_char {\n        self.0.as_ptr().cast()\n    }\n\n    #[inline(always)]\n    pub fn as_cstr(&self) -> &CStr {\n        // SAFETY: Already validated as null terminated during construction\n        unsafe { CStr::from_bytes_with_nul_unchecked(&self.0) }\n    }\n\n    #[inline(always)]\n    pub fn as_utf8_cstr(&self) -> &Utf8CStr {\n        self\n    }\n\n    #[inline(always)]\n    pub fn as_str(&self) -> &str {\n        // SAFETY: Already UTF-8 validated during construction\n        // SAFETY: The length of the slice is at least 1 due to null termination check\n        unsafe { str::from_utf8_unchecked(self.0.get_unchecked(..self.0.len() - 1)) }\n    }\n}\n\nimpl Deref for Utf8CStr {\n    type Target = str;\n\n    #[inline(always)]\n    fn deref(&self) -> &str {\n        self.as_str()\n    }\n}\n\nimpl ToOwned for Utf8CStr {\n    type Owned = Utf8CString;\n\n    fn to_owned(&self) -> Utf8CString {\n        let mut s = Utf8CString::with_capacity(self.len() + 1);\n        s.push_str(self.as_str());\n        s\n    }\n}\n\nimpl AsRef<Utf8CStr> for Utf8CStr {\n    fn as_ref(&self) -> &Utf8CStr {\n        self\n    }\n}\n\nimpl NixPath for Utf8CStr {\n    #[inline(always)]\n    fn is_empty(&self) -> bool {\n        self.as_str().is_empty()\n    }\n\n    #[inline(always)]\n    fn len(&self) -> usize {\n        self.as_str().len()\n    }\n\n    #[inline(always)]\n    fn with_nix_path<T, F>(&self, f: F) -> nix::Result<T>\n    where\n        F: FnOnce(&CStr) -> T,\n    {\n        Ok(f(self.as_cstr()))\n    }\n}\n\n// Notice that we only implement ExternType on Utf8CStr *reference*\nunsafe impl ExternType for &Utf8CStr {\n    type Id = type_id!(\"Utf8CStr\");\n    type Kind = cxx::kind::Trivial;\n}\n\nmacro_rules! const_assert_eq {\n    ($left:expr, $right:expr $(,)?) => {\n        const _: [(); $left] = [(); $right];\n    };\n}\n\n// Assert ABI layout\nconst_assert_eq!(size_of::<&Utf8CStr>(), size_of::<[usize; 2]>());\nconst_assert_eq!(align_of::<&Utf8CStr>(), align_of::<[usize; 2]>());\n\n// File system path extensions types\n\n#[repr(transparent)]\npub struct FsPathFollow(Utf8CStr);\n\nimpl AsRef<Utf8CStr> for FsPathFollow {\n    #[inline(always)]\n    fn as_ref(&self) -> &Utf8CStr {\n        &self.0\n    }\n}\n\n// impl<T: AsRef<Utf8CStr>> Deref<Target = Utf8CStr> for T { ... }\nmacro_rules! impl_cstr_deref {\n    ($( ($t:ty, $($g:tt)*) )*) => {$(\n        impl<$($g)*> Deref for $t {\n            type Target = Utf8CStr;\n\n            #[inline(always)]\n            fn deref(&self) -> &Utf8CStr {\n                self.as_ref()\n            }\n        }\n    )*}\n}\n\nimpl_cstr_deref!(\n    (Utf8CStrBufRef<'_>,)\n    (Utf8CStrBufArr<N>, const N: usize)\n    (Utf8CString,)\n    (FsPathFollow,)\n);\n\n// impl<T: Deref<Target = Utf8CStr>> BoilerPlate for T { ... }\nmacro_rules! impl_cstr_misc {\n    ($( ($t:ty, $($g:tt)*) )*) => {$(\n        impl<$($g)*> AsRef<str> for $t {\n            #[inline(always)]\n            fn as_ref(&self) -> &str {\n                self.as_str()\n            }\n        }\n        impl<$($g)*> AsRef<CStr> for $t {\n            #[inline(always)]\n            fn as_ref(&self) -> &CStr {\n                self.as_cstr()\n            }\n        }\n        impl<$($g)*> AsRef<OsStr> for $t {\n            #[inline(always)]\n            fn as_ref(&self) -> &OsStr {\n                OsStr::new(self.as_str())\n            }\n        }\n        impl<$($g)*> AsRef<Path> for $t {\n            #[inline(always)]\n            fn as_ref(&self) -> &Path {\n                Path::new(self.as_str())\n            }\n        }\n        impl<$($g)*> Display for $t {\n            #[inline(always)]\n            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n                Display::fmt(self.as_str(), f)\n            }\n        }\n        impl<$($g)*> Debug for $t {\n            #[inline(always)]\n            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {\n                Debug::fmt(self.as_str(), f)\n            }\n        }\n        impl<$($g)*> PartialEq<str> for $t {\n            #[inline(always)]\n            fn eq(&self, other: &str) -> bool {\n                self.as_str() == other\n            }\n        }\n        impl<$($g)*> PartialEq<$t> for str {\n            #[inline(always)]\n            fn eq(&self, other: &$t) -> bool {\n                self == other.as_str()\n            }\n        }\n        impl<$($g)*> PartialEq<CStr> for $t {\n            #[inline(always)]\n            fn eq(&self, other: &CStr) -> bool {\n                self.as_cstr() == other\n            }\n        }\n        impl<$($g)*> PartialEq<$t> for CStr {\n            #[inline(always)]\n            fn eq(&self, other: &$t) -> bool {\n                self == other.as_cstr()\n            }\n        }\n        impl<T: AsRef<Utf8CStr> + ?Sized, $($g)*> PartialEq<T> for $t {\n            #[inline(always)]\n            fn eq(&self, other: &T) -> bool {\n                self.as_bytes_with_nul() == other.as_ref().as_bytes_with_nul()\n            }\n        }\n        impl<$($g)*> Eq for $t {}\n        impl<$($g)*> PartialOrd for $t {\n            fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\n                Some(self.cmp(other))\n            }\n        }\n        impl<$($g)*> Ord for $t {\n            fn cmp(&self, other: &Self) -> Ordering {\n                self.as_str().cmp(other.as_str())\n            }\n        }\n    )*}\n}\n\nimpl_cstr_misc!(\n    (Utf8CStr,)\n    (Utf8CStrBufRef<'_>,)\n    (Utf8CStrBufArr<N>, const N: usize)\n    (Utf8CString,)\n    (FsPathFollow,)\n);\n\nfn copy_cstr_truncate(dest: &mut [u8], src: &[u8]) -> usize {\n    if dest.len() <= 1 {\n        // Truncate\n        return 0;\n    }\n    let len = min(src.len(), dest.len() - 1);\n    if len > 0 {\n        dest[..len].copy_from_slice(&src[..len]);\n    }\n    dest[len] = b'\\0';\n    len\n}\n\n// impl<T> AsRef<Utf8CStr> for T { ... }\n// impl<T> Utf8CStrBuf for T { ... }\nmacro_rules! impl_cstr_buf {\n    ($( ($t:ty, $($g:tt)*) )*) => {$(\n        impl<$($g)*> AsRef<Utf8CStr> for $t {\n            #[inline(always)]\n            fn as_ref(&self) -> &Utf8CStr {\n                // SAFETY: the internal buffer is always UTF-8 checked\n                // SAFETY: self.used is guaranteed to always <= SIZE - 1\n                unsafe { Utf8CStr::from_bytes_unchecked(self.buf.get_unchecked(..(self.used + 1))) }\n            }\n        }\n        impl<$($g)*> Utf8CStrBuf for $t {\n            #[inline(always)]\n            fn len(&self) -> usize {\n                self.used\n            }\n            #[inline(always)]\n            fn push_str(&mut self, s: &str) -> usize {\n                // SAFETY: self.used is guaranteed to always <= SIZE - 1\n                let dest = unsafe { self.buf.get_unchecked_mut(self.used..) };\n                let len = copy_cstr_truncate(dest, s.as_bytes());\n                self.used += len;\n                len\n            }\n            #[inline(always)]\n            fn capacity(&self) -> usize {\n                self.buf.len()\n            }\n            #[inline(always)]\n            fn clear(&mut self) {\n                self.buf[0] = b'\\0';\n                self.used = 0;\n            }\n            #[inline(always)]\n            fn as_mut_ptr(&mut self) -> *mut c_char {\n                self.buf.as_mut_ptr().cast()\n            }\n            fn truncate(&mut self, new_len: usize) {\n                if self.used <= new_len {\n                    return;\n                }\n                self.buf[new_len] = b'\\0';\n                self.used = new_len;\n            }\n            fn rebuild(&mut self) -> Result<(), StrErr> {\n                // Validate the entire buffer, including the unused part\n                match Utf8CStr::from_bytes_until_nul(&self.buf) {\n                    Ok(s) => self.used = s.len(),\n                    Err(e) => {\n                        self.used = 0;\n                        self.buf[0] = b'\\0';\n                        return Err(e);\n                    }\n                }\n                Ok(())\n            }\n        }\n    )*}\n}\n\nimpl_cstr_buf!(\n    (Utf8CStrBufRef<'_>,)\n    (Utf8CStrBufArr<N>, const N: usize)\n);\n\n// impl<T: Utf8CStrBuf> Write for T { ... }\nmacro_rules! impl_cstr_buf_write {\n    ($( ($t:ty, $($g:tt)*) )*) => {$(\n        impl<$($g)*> Write for $t {\n            #[inline(always)]\n            fn write_str(&mut self, s: &str) -> fmt::Result {\n                self.push_str(s);\n                Ok(())\n            }\n        }\n    )*}\n}\n\nimpl_cstr_buf_write!(\n    (Utf8CStrBufRef<'_>,)\n    (Utf8CStrBufArr<N>, const N: usize)\n    (Utf8CString,)\n);\n\n#[macro_export]\nmacro_rules! cstr {\n    ($str:expr) => {{\n        const NULL_STR: &str = $crate::const_format::concatcp!($str, \"\\0\");\n        #[allow(unused_unsafe)]\n        unsafe {\n            $crate::Utf8CStr::from_bytes_unchecked(NULL_STR.as_bytes())\n        }\n    }};\n}\n\n#[macro_export]\nmacro_rules! raw_cstr {\n    ($str:expr) => {{ $crate::cstr!($str).as_ptr() }};\n}\n"
  },
  {
    "path": "native/src/base/cxx_extern.rs",
    "content": "// Functions in this file are only for exporting to C++, DO NOT USE IN RUST\n\nuse std::fs::File;\nuse std::io::BufReader;\nuse std::mem::ManuallyDrop;\nuse std::ops::DerefMut;\nuse std::os::fd::{BorrowedFd, FromRawFd, RawFd};\n\nuse crate::ffi::{FnBoolStr, FnBoolStrStr};\nuse crate::files::map_file_at;\npub(crate) use crate::xwrap::*;\nuse crate::{\n    BufReadExt, ResultExt, Utf8CStr, clone_attr, cstr, fclone_attr, map_fd, map_file,\n    slice_from_ptr,\n};\nuse cfg_if::cfg_if;\nuse libc::{c_char, mode_t};\nuse nix::fcntl::OFlag;\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn canonical_path(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => {\n                let mut buf = cstr::buf::wrap_ptr(buf, bufsz);\n                path.realpath(&mut buf)\n                    .log()\n                    .map_or(-1_isize, |_| buf.len() as isize)\n            }\n            Err(_) => -1,\n        }\n    }\n}\n\n#[unsafe(export_name = \"mkdirs\")]\nunsafe extern \"C\" fn mkdirs_for_cxx(path: *const c_char, mode: mode_t) -> i32 {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => path.mkdirs(mode).map_or(-1, |_| 0),\n            Err(_) => -1,\n        }\n    }\n}\n\n#[unsafe(export_name = \"rm_rf\")]\nunsafe extern \"C\" fn rm_rf_for_cxx(path: *const c_char) -> bool {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => path.remove_all().is_ok(),\n            Err(_) => false,\n        }\n    }\n}\n\npub(crate) fn map_file_for_cxx(path: &Utf8CStr, rw: bool) -> &'static mut [u8] {\n    map_file(path, rw).log().unwrap_or(&mut [])\n}\n\npub(crate) fn map_file_at_for_cxx(fd: RawFd, path: &Utf8CStr, rw: bool) -> &'static mut [u8] {\n    unsafe {\n        map_file_at(BorrowedFd::borrow_raw(fd), path, rw)\n            .log()\n            .unwrap_or(&mut [])\n    }\n}\n\npub(crate) fn map_fd_for_cxx(fd: RawFd, sz: usize, rw: bool) -> &'static mut [u8] {\n    unsafe {\n        map_fd(BorrowedFd::borrow_raw(fd), sz, rw)\n            .log()\n            .unwrap_or(&mut [])\n    }\n}\n\npub(crate) unsafe fn readlinkat(\n    dirfd: RawFd,\n    path: *const c_char,\n    buf: *mut u8,\n    bufsz: usize,\n) -> isize {\n    unsafe {\n        // readlinkat() may fail on x86 platform, returning random value\n        // instead of number of bytes placed in buf (length of link)\n        cfg_if! {\n            if #[cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))] {\n                libc::memset(buf.cast(), 0, bufsz);\n                let mut r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);\n                if r > 0 {\n                    r = libc::strlen(buf.cast()) as isize;\n                }\n            } else {\n                let r = libc::readlinkat(dirfd, path, buf.cast(), bufsz - 1);\n                if r >= 0 {\n                    *buf.offset(r) = b'\\0';\n                }\n            }\n        }\n        r\n    }\n}\n\n#[unsafe(export_name = \"cp_afc\")]\nunsafe extern \"C\" fn cp_afc_for_cxx(src: *const c_char, dest: *const c_char) -> bool {\n    unsafe {\n        if let Ok(src) = Utf8CStr::from_ptr(src)\n            && let Ok(dest) = Utf8CStr::from_ptr(dest)\n        {\n            return src.copy_to(dest).is_ok();\n        }\n        false\n    }\n}\n\n#[unsafe(export_name = \"mv_path\")]\nunsafe extern \"C\" fn mv_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {\n    unsafe {\n        if let Ok(src) = Utf8CStr::from_ptr(src)\n            && let Ok(dest) = Utf8CStr::from_ptr(dest)\n        {\n            return src.move_to(dest).is_ok();\n        }\n        false\n    }\n}\n\n#[unsafe(export_name = \"link_path\")]\nunsafe extern \"C\" fn link_path_for_cxx(src: *const c_char, dest: *const c_char) -> bool {\n    unsafe {\n        if let Ok(src) = Utf8CStr::from_ptr(src)\n            && let Ok(dest) = Utf8CStr::from_ptr(dest)\n        {\n            return src.link_to(dest).is_ok();\n        }\n        false\n    }\n}\n\n#[unsafe(export_name = \"clone_attr\")]\nunsafe extern \"C\" fn clone_attr_for_cxx(src: *const c_char, dest: *const c_char) -> bool {\n    unsafe {\n        if let Ok(src) = Utf8CStr::from_ptr(src)\n            && let Ok(dest) = Utf8CStr::from_ptr(dest)\n        {\n            return clone_attr(src, dest).log().is_ok();\n        }\n        false\n    }\n}\n\n#[unsafe(export_name = \"fclone_attr\")]\nunsafe extern \"C\" fn fclone_attr_for_cxx(a: RawFd, b: RawFd) -> bool {\n    fclone_attr(a, b).log().is_ok()\n}\n\n#[unsafe(export_name = \"cxx$utf8str$new\")]\nunsafe extern \"C\" fn str_new(this: &mut &Utf8CStr, s: *const u8, len: usize) {\n    unsafe {\n        *this = Utf8CStr::from_bytes(slice_from_ptr(s, len)).unwrap_or(cstr!(\"\"));\n    }\n}\n\n#[unsafe(export_name = \"cxx$utf8str$ptr\")]\nunsafe extern \"C\" fn str_ptr(this: &&Utf8CStr) -> *const u8 {\n    this.as_ptr().cast()\n}\n\n#[unsafe(export_name = \"cxx$utf8str$len\")]\nunsafe extern \"C\" fn str_len(this: &&Utf8CStr) -> usize {\n    this.len()\n}\n\npub(crate) fn parse_prop_file_rs(name: &Utf8CStr, f: &FnBoolStrStr) {\n    if let Ok(file) = name.open(OFlag::O_RDONLY) {\n        BufReader::new(file).for_each_prop(|key, value| f.call(key, value))\n    }\n}\n\npub(crate) fn file_readline_for_cxx(fd: RawFd, f: &FnBoolStr) {\n    let mut fd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });\n    BufReader::new(fd.deref_mut()).for_each_line(|line| f.call(Utf8CStr::from_string(line)));\n}\n"
  },
  {
    "path": "native/src/base/derive/Cargo.toml",
    "content": "[package]\nname = \"derive\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\npath = \"lib.rs\"\nproc-macro = true\n\n[dependencies]\nsyn = { workspace = true }\nquote = { workspace = true }\nproc-macro2 = { workspace = true }\n"
  },
  {
    "path": "native/src/base/derive/argh/errors.rs",
    "content": "// Copyright (c) 2020 Google LLC All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nuse proc_macro2::{Span, TokenStream};\nuse quote::ToTokens;\nuse std::cell::RefCell;\n\n/// A type for collecting procedural macro errors.\n#[derive(Default)]\npub struct Errors {\n    errors: RefCell<Vec<syn::Error>>,\n}\n\n/// Produce functions to expect particular literals in `syn::Expr`\nmacro_rules! expect_lit_fn {\n    ($(($fn_name:ident, $syn_type:ident, $variant:ident, $lit_name:literal),)*) => {\n        $(\n            pub fn $fn_name<'a>(&self, e: &'a syn::Expr) -> Option<&'a syn::$syn_type> {\n                if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::$variant(inner), .. }) = e {\n                    Some(inner)\n                } else {\n                    self.unexpected_lit($lit_name, e);\n                    None\n                }\n            }\n        )*\n    }\n}\n\n/// Produce functions to expect particular variants of `syn::Meta`\nmacro_rules! expect_meta_fn {\n    ($(($fn_name:ident, $syn_type:ident, $variant:ident, $meta_name:literal),)*) => {\n        $(\n            pub fn $fn_name<'a>(&self, meta: &'a syn::Meta) -> Option<&'a syn::$syn_type> {\n                if let syn::Meta::$variant(inner) = meta {\n                    Some(inner)\n                } else {\n                    self.unexpected_meta($meta_name, meta);\n                    None\n                }\n            }\n        )*\n    }\n}\n\nimpl Errors {\n    /// Issue an error like:\n    ///\n    /// Duplicate foo attribute\n    /// First foo attribute here\n    pub fn duplicate_attrs(\n        &self,\n        attr_kind: &str,\n        first: &impl syn::spanned::Spanned,\n        second: &impl syn::spanned::Spanned,\n    ) {\n        self.duplicate_attrs_inner(attr_kind, first.span(), second.span())\n    }\n\n    fn duplicate_attrs_inner(&self, attr_kind: &str, first: Span, second: Span) {\n        self.err_span(second, &[\"Duplicate \", attr_kind, \" attribute\"].concat());\n        self.err_span(first, &[\"First \", attr_kind, \" attribute here\"].concat());\n    }\n\n    expect_lit_fn![\n        (expect_lit_str, LitStr, Str, \"string\"),\n        (expect_lit_char, LitChar, Char, \"character\"),\n        (expect_lit_int, LitInt, Int, \"integer\"),\n    ];\n\n    expect_meta_fn![\n        (expect_meta_word, Path, Path, \"path\"),\n        (expect_meta_list, MetaList, List, \"list\"),\n        (\n            expect_meta_name_value,\n            MetaNameValue,\n            NameValue,\n            \"name-value pair\"\n        ),\n    ];\n\n    fn unexpected_lit(&self, expected: &str, found: &syn::Expr) {\n        fn lit_kind(lit: &syn::Lit) -> &'static str {\n            use syn::Lit::{Bool, Byte, ByteStr, Char, Float, Int, Str, Verbatim};\n            match lit {\n                Str(_) => \"string\",\n                ByteStr(_) => \"bytestring\",\n                Byte(_) => \"byte\",\n                Char(_) => \"character\",\n                Int(_) => \"integer\",\n                Float(_) => \"float\",\n                Bool(_) => \"boolean\",\n                Verbatim(_) => \"unknown (possibly extra-large integer)\",\n                _ => \"unknown literal kind\",\n            }\n        }\n\n        if let syn::Expr::Lit(syn::ExprLit { lit, .. }) = found {\n            self.err(\n                found,\n                &[\n                    \"Expected \",\n                    expected,\n                    \" literal, found \",\n                    lit_kind(lit),\n                    \" literal\",\n                ]\n                .concat(),\n            )\n        } else {\n            self.err(\n                found,\n                &[\n                    \"Expected \",\n                    expected,\n                    \" literal, found non-literal expression.\",\n                ]\n                .concat(),\n            )\n        }\n    }\n\n    fn unexpected_meta(&self, expected: &str, found: &syn::Meta) {\n        fn meta_kind(meta: &syn::Meta) -> &'static str {\n            use syn::Meta::{List, NameValue, Path};\n            match meta {\n                Path(_) => \"path\",\n                List(_) => \"list\",\n                NameValue(_) => \"name-value pair\",\n            }\n        }\n\n        self.err(\n            found,\n            &[\n                \"Expected \",\n                expected,\n                \" attribute, found \",\n                meta_kind(found),\n                \" attribute\",\n            ]\n            .concat(),\n        )\n    }\n\n    /// Issue an error relating to a particular `Spanned` structure.\n    pub fn err(&self, spanned: &impl syn::spanned::Spanned, msg: &str) {\n        self.err_span(spanned.span(), msg);\n    }\n\n    /// Issue an error relating to a particular `Span`.\n    pub fn err_span(&self, span: Span, msg: &str) {\n        self.push(syn::Error::new(span, msg));\n    }\n\n    /// Issue an error spanning over the given syntax tree node.\n    pub fn err_span_tokens<T: ToTokens>(&self, tokens: T, msg: &str) {\n        self.push(syn::Error::new_spanned(tokens, msg));\n    }\n\n    /// Push a `syn::Error` onto the list of errors to issue.\n    pub fn push(&self, err: syn::Error) {\n        self.errors.borrow_mut().push(err);\n    }\n\n    /// Convert a `syn::Result` to an `Option`, logging the error if present.\n    pub fn ok<T>(&self, r: syn::Result<T>) -> Option<T> {\n        match r {\n            Ok(v) => Some(v),\n            Err(e) => {\n                self.push(e);\n                None\n            }\n        }\n    }\n}\n\nimpl ToTokens for Errors {\n    /// Convert the errors into tokens that, when emit, will cause\n    /// the user of the macro to receive compiler errors.\n    fn to_tokens(&self, tokens: &mut TokenStream) {\n        tokens.extend(self.errors.borrow().iter().map(|e| e.to_compile_error()));\n    }\n}\n"
  },
  {
    "path": "native/src/base/derive/argh/mod.rs",
    "content": "// Copyright (c) 2020 Google LLC All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nuse syn::ext::IdentExt as _;\n\n/// Implementation of the `FromArgs` and `argh(...)` derive attributes.\n///\n/// For more thorough documentation, see the `argh` crate itself.\nextern crate proc_macro;\n\nuse errors::Errors;\nuse parse_attrs::{FieldAttrs, FieldKind, TypeAttrs, check_long_name};\nuse proc_macro2::{Span, TokenStream};\nuse quote::{ToTokens, quote, quote_spanned};\nuse std::collections::HashMap;\nuse std::str::FromStr;\nuse syn::spanned::Spanned;\nuse syn::{GenericArgument, LitStr, PathArguments, Type};\n\nmod errors;\nmod parse_attrs;\n\n/// Transform the input into a token stream containing any generated implementations,\n/// as well as all errors that occurred.\npub(crate) fn impl_from_args(input: &syn::DeriveInput) -> TokenStream {\n    let errors = &Errors::default();\n    let type_attrs = &TypeAttrs::parse(errors, input);\n    let mut output_tokens = match &input.data {\n        syn::Data::Struct(ds) => {\n            impl_from_args_struct(errors, &input.ident, type_attrs, &input.generics, ds)\n        }\n        syn::Data::Enum(de) => {\n            impl_from_args_enum(errors, &input.ident, type_attrs, &input.generics, de)\n        }\n        syn::Data::Union(_) => {\n            errors.err(input, \"`#[derive(FromArgs)]` cannot be applied to unions\");\n            TokenStream::new()\n        }\n    };\n    errors.to_tokens(&mut output_tokens);\n    output_tokens\n}\n\n/// The kind of optionality a parameter has.\nenum Optionality {\n    None,\n    Defaulted(TokenStream),\n    Optional,\n    Repeating,\n    DefaultedRepeating(TokenStream),\n}\n\nimpl PartialEq<Optionality> for Optionality {\n    fn eq(&self, other: &Optionality) -> bool {\n        use Optionality::*;\n        // NB: (Defaulted, Defaulted) can't contain the same token streams\n        matches!((self, other), (Optional, Optional) | (Repeating, Repeating))\n    }\n}\n\nimpl Optionality {\n    /// Whether or not this is `Optionality::None`\n    fn is_required(&self) -> bool {\n        matches!(self, Optionality::None)\n    }\n}\n\n/// A field of a `#![derive(FromArgs)]` struct with attributes and some other\n/// notable metadata appended.\nstruct StructField<'a> {\n    /// The original parsed field\n    field: &'a syn::Field,\n    /// The parsed attributes of the field\n    attrs: FieldAttrs,\n    /// The field name. This is contained optionally inside `field`,\n    /// but is duplicated non-optionally here to indicate that all field that\n    /// have reached this point must have a field name, and it no longer\n    /// needs to be unwrapped.\n    name: &'a syn::Ident,\n    /// Similar to `name` above, this is contained optionally inside `FieldAttrs`,\n    /// but here is fully present to indicate that we only have to consider fields\n    /// with a valid `kind` at this point.\n    kind: FieldKind,\n    // If `field.ty` is `Vec<T>` or `Option<T>`, this is `T`, otherwise it's `&field.ty`.\n    // This is used to enable consistent parsing code between optional and non-optional\n    // keyed and subcommand fields.\n    ty_without_wrapper: &'a syn::Type,\n    // Whether the field represents an optional value, such as an `Option` subcommand field\n    // or an `Option` or `Vec` keyed argument, or if it has a `default`.\n    optionality: Optionality,\n    // The `--`-prefixed name of the option, if one exists.\n    long_name: Option<String>,\n}\n\nimpl<'a> StructField<'a> {\n    /// Attempts to parse a field of a `#[derive(FromArgs)]` struct, pulling out the\n    /// fields required for code generation.\n    fn new(errors: &Errors, field: &'a syn::Field, attrs: FieldAttrs) -> Option<Self> {\n        let name = field.ident.as_ref().expect(\"missing ident for named field\");\n\n        // Ensure that one \"kind\" is present (switch, option, subcommand, positional)\n        let kind = if let Some(field_type) = &attrs.field_type {\n            field_type.kind\n        } else {\n            errors.err(\n                field,\n                concat!(\n                    \"Missing `argh` field kind attribute.\\n\",\n                    \"Expected one of: `switch`, `option`, `remaining`, `subcommand`, `positional`\",\n                ),\n            );\n            return None;\n        };\n\n        // Parse out whether a field is optional (`Option` or `Vec`).\n        let optionality;\n        let ty_without_wrapper;\n        match kind {\n            FieldKind::Switch => {\n                if !ty_expect_switch(errors, &field.ty) {\n                    return None;\n                }\n                optionality = Optionality::Optional;\n                ty_without_wrapper = &field.ty;\n            }\n            FieldKind::Option | FieldKind::Positional => {\n                if let Some(default) = &attrs.default {\n                    let tokens = match TokenStream::from_str(&default.value()) {\n                        Ok(tokens) => tokens,\n                        Err(_) => {\n                            errors.err(&default, \"Invalid tokens: unable to lex `default` value\");\n                            return None;\n                        }\n                    };\n                    // Set the span of the generated tokens to the string literal\n                    let tokens: TokenStream = tokens\n                        .into_iter()\n                        .map(|mut tree| {\n                            tree.set_span(default.span());\n                            tree\n                        })\n                        .collect();\n                    let inner = if let Some(x) = ty_inner(&[\"Vec\"], &field.ty) {\n                        optionality = Optionality::DefaultedRepeating(tokens);\n                        x\n                    } else {\n                        optionality = Optionality::Defaulted(tokens);\n                        &field.ty\n                    };\n                    ty_without_wrapper = inner;\n                } else {\n                    let mut inner = None;\n                    optionality = if let Some(x) = ty_inner(&[\"Option\"], &field.ty) {\n                        inner = Some(x);\n                        Optionality::Optional\n                    } else if let Some(x) = ty_inner(&[\"Vec\"], &field.ty) {\n                        inner = Some(x);\n                        Optionality::Repeating\n                    } else {\n                        Optionality::None\n                    };\n                    ty_without_wrapper = inner.unwrap_or(&field.ty);\n                }\n            }\n            FieldKind::SubCommand => {\n                let inner = ty_inner(&[\"Option\"], &field.ty);\n                optionality = if inner.is_some() {\n                    Optionality::Optional\n                } else {\n                    Optionality::None\n                };\n                ty_without_wrapper = inner.unwrap_or(&field.ty);\n            }\n        }\n\n        // Determine the \"long\" name of options and switches.\n        // Defaults to the kebab-cased field name if `#[argh(long = \"...\")]` is omitted.\n        // If `#[argh(long = none)]` is explicitly set, no long name will be set.\n        let long_name = match kind {\n            FieldKind::Switch | FieldKind::Option => {\n                let long_name = match &attrs.long {\n                    None => {\n                        let kebab_name = to_kebab_case(&name.unraw().to_string());\n                        check_long_name(errors, name, &kebab_name);\n                        Some(kebab_name)\n                    }\n                    Some(None) => None,\n                    Some(Some(long)) => Some(long.value()),\n                }\n                .map(|long_name| {\n                    if long_name == \"help\" {\n                        errors.err(field, \"Custom `--help` flags are not supported.\");\n                    }\n                    format!(\"--{}\", long_name)\n                });\n                if let (None, None) = (&attrs.short, &long_name) {\n                    errors.err(field, \"At least one of `short` or `long` has to be set.\")\n                };\n                long_name\n            }\n            FieldKind::SubCommand | FieldKind::Positional => None,\n        };\n\n        Some(StructField {\n            field,\n            attrs,\n            kind,\n            optionality,\n            ty_without_wrapper,\n            name,\n            long_name,\n        })\n    }\n\n    pub(crate) fn positional_arg_name(&self) -> String {\n        self.attrs\n            .arg_name\n            .as_ref()\n            .map(LitStr::value)\n            .unwrap_or_else(|| self.name.to_string().trim_matches('_').to_owned())\n    }\n\n    fn option_arg_name(&self) -> String {\n        match (&self.attrs.short, &self.long_name) {\n            (None, None) => unreachable!(\"short and long cannot both be None\"),\n            (Some(short), None) => format!(\"-{}\", short.value()),\n            (None, Some(long)) => long.clone(),\n            (Some(short), Some(long)) => format!(\"-{},{long}\", short.value()),\n        }\n    }\n}\n\nfn to_kebab_case(s: &str) -> String {\n    let words = s.split('_').filter(|word| !word.is_empty());\n    let mut res = String::with_capacity(s.len());\n    for word in words {\n        if !res.is_empty() {\n            res.push('-')\n        }\n        res.push_str(word)\n    }\n    res\n}\n\n/// Implements `FromArgs` and `TopLevelCommand` or `SubCommand` for a `#[derive(FromArgs)]` struct.\nfn impl_from_args_struct(\n    errors: &Errors,\n    name: &syn::Ident,\n    type_attrs: &TypeAttrs,\n    generic_args: &syn::Generics,\n    ds: &syn::DataStruct,\n) -> TokenStream {\n    let fields = match &ds.fields {\n        syn::Fields::Named(fields) => fields,\n        syn::Fields::Unnamed(_) => {\n            errors.err(\n                &ds.struct_token,\n                \"`#![derive(FromArgs)]` is not currently supported on tuple structs\",\n            );\n            return TokenStream::new();\n        }\n        syn::Fields::Unit => {\n            errors.err(\n                &ds.struct_token,\n                \"#![derive(FromArgs)]` cannot be applied to unit structs\",\n            );\n            return TokenStream::new();\n        }\n    };\n\n    let fields: Vec<_> = fields\n        .named\n        .iter()\n        .filter_map(|field| {\n            let attrs = FieldAttrs::parse(errors, field);\n            StructField::new(errors, field, attrs)\n        })\n        .collect();\n\n    ensure_unique_names(errors, &fields);\n    ensure_only_trailing_positionals_are_optional(errors, &fields);\n\n    let impl_span = Span::call_site();\n\n    let from_args_method = impl_from_args_struct_from_args(errors, type_attrs, &fields);\n\n    let top_or_sub_cmd_impl = top_or_sub_cmd_impl(errors, name, type_attrs, generic_args);\n\n    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();\n    let trait_impl = quote_spanned! { impl_span =>\n        #[automatically_derived]\n        impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {\n            #from_args_method\n        }\n\n        #top_or_sub_cmd_impl\n    };\n\n    trait_impl\n}\n\nfn impl_from_args_struct_from_args<'a>(\n    errors: &Errors,\n    type_attrs: &TypeAttrs,\n    fields: &'a [StructField<'a>],\n) -> TokenStream {\n    let init_fields = declare_local_storage_for_from_args_fields(fields);\n    let unwrap_fields = unwrap_from_args_fields(fields);\n    let positional_fields: Vec<&StructField<'_>> = fields\n        .iter()\n        .filter(|field| field.kind == FieldKind::Positional)\n        .collect();\n    let positional_field_idents = positional_fields.iter().map(|field| &field.field.ident);\n    let positional_field_names = positional_fields.iter().map(|field| field.name.to_string());\n    let last_positional_is_repeating = positional_fields\n        .last()\n        .map(|field| field.optionality == Optionality::Repeating)\n        .unwrap_or(false);\n    let last_positional_is_greedy = positional_fields\n        .last()\n        .map(|field| field.kind == FieldKind::Positional && field.attrs.greedy.is_some())\n        .unwrap_or(false);\n\n    let flag_output_table = fields.iter().filter_map(|field| {\n        let field_name = &field.field.ident;\n        match field.kind {\n            FieldKind::Option => Some(quote! { argh::ParseStructOption::Value(&mut #field_name) }),\n            FieldKind::Switch => Some(quote! { argh::ParseStructOption::Flag(&mut #field_name) }),\n            FieldKind::SubCommand | FieldKind::Positional => None,\n        }\n    });\n\n    let flag_str_to_output_table_map = flag_str_to_output_table_map_entries(fields);\n\n    let mut subcommands_iter = fields\n        .iter()\n        .filter(|field| field.kind == FieldKind::SubCommand)\n        .fuse();\n\n    let subcommand: Option<&StructField<'_>> = subcommands_iter.next();\n    for dup_subcommand in subcommands_iter {\n        errors.duplicate_attrs(\n            \"subcommand\",\n            subcommand.unwrap().field,\n            dup_subcommand.field,\n        );\n    }\n\n    let impl_span = Span::call_site();\n\n    let missing_requirements_ident = syn::Ident::new(\"__missing_requirements\", impl_span);\n\n    let append_missing_requirements =\n        append_missing_requirements(&missing_requirements_ident, fields);\n\n    let parse_subcommands = if let Some(subcommand) = subcommand {\n        let name = subcommand.name;\n        let ty = subcommand.ty_without_wrapper;\n        quote_spanned! { impl_span =>\n            Some(argh::ParseStructSubCommand {\n                subcommands: <#ty as argh::SubCommands>::COMMANDS,\n                dynamic_subcommands: &<#ty as argh::SubCommands>::dynamic_commands(),\n                parse_func: &mut |__command, __remaining_args| {\n                    #name = Some(<#ty as argh::FromArgs>::from_args(__command, __remaining_args)?);\n                    Ok(())\n                },\n            })\n        }\n    } else {\n        quote_spanned! { impl_span => None }\n    };\n\n    let help_triggers = get_help_triggers(type_attrs);\n\n    let method_impl = quote_spanned! { impl_span =>\n        fn from_args(__cmd_name: &[&str], __args: &[&str])\n            -> std::result::Result<Self, argh::EarlyExit>\n        {\n            #![allow(clippy::unwrap_in_result)]\n\n            #( #init_fields )*\n\n            argh::parse_struct_args(\n                __cmd_name,\n                __args,\n                argh::ParseStructOptions {\n                    arg_to_slot: &[ #( #flag_str_to_output_table_map ,)* ],\n                    slots: &mut [ #( #flag_output_table, )* ],\n                    help_triggers: &[ #( #help_triggers ),* ],\n                },\n                argh::ParseStructPositionals {\n                    positionals: &mut [\n                        #(\n                            argh::ParseStructPositional {\n                                name: #positional_field_names,\n                                slot: &mut #positional_field_idents as &mut dyn argh::ParseValueSlot,\n                            },\n                        )*\n                    ],\n                    last_is_repeating: #last_positional_is_repeating,\n                    last_is_greedy: #last_positional_is_greedy,\n                },\n                #parse_subcommands,\n            )?;\n\n            let mut #missing_requirements_ident = argh::MissingRequirements::default();\n            #(\n                #append_missing_requirements\n            )*\n            #missing_requirements_ident.err_on_any()?;\n\n            Ok(Self {\n                #( #unwrap_fields, )*\n            })\n        }\n    };\n\n    method_impl\n}\n\n/// get help triggers vector from type_attrs.help_triggers as a [`Vec<String>`]\n///\n/// Defaults to vec![\"-h\", \"--help\"] if type_attrs.help_triggers is None\nfn get_help_triggers(type_attrs: &TypeAttrs) -> Vec<String> {\n    if type_attrs.is_subcommand.is_some() {\n        // Subcommands should never have any help triggers\n        Vec::new()\n    } else {\n        type_attrs.help_triggers.as_ref().map_or_else(\n            || vec![\"-h\".to_string(), \"--help\".to_string()],\n            |s| {\n                s.iter()\n                    .filter_map(|s| {\n                        let trigger = s.value();\n                        let trigger_trimmed = trigger.trim().to_owned();\n                        if trigger_trimmed.is_empty() {\n                            None\n                        } else {\n                            Some(trigger_trimmed)\n                        }\n                    })\n                    .collect::<Vec<_>>()\n            },\n        )\n    }\n}\n\n/// Ensures that only trailing positional args are non-required.\nfn ensure_only_trailing_positionals_are_optional(errors: &Errors, fields: &[StructField<'_>]) {\n    let mut first_non_required_span = None;\n    for field in fields {\n        if field.kind == FieldKind::Positional {\n            if let Some(first) = first_non_required_span\n                && field.optionality.is_required()\n            {\n                errors.err_span(\n                    first,\n                    \"Only trailing positional arguments may be `Option`, `Vec`, or defaulted.\",\n                );\n                errors.err(\n                    &field.field,\n                    \"Later non-optional positional argument declared here.\",\n                );\n                return;\n            }\n            if !field.optionality.is_required() {\n                first_non_required_span = Some(field.field.span());\n            }\n        }\n    }\n}\n\n/// Ensures that only one short or long name is used.\nfn ensure_unique_names(errors: &Errors, fields: &[StructField<'_>]) {\n    let mut seen_short_names = HashMap::new();\n    let mut seen_long_names = HashMap::new();\n\n    for field in fields {\n        if let Some(short_name) = &field.attrs.short {\n            let short_name = short_name.value();\n            if let Some(first_use_field) = seen_short_names.get(&short_name) {\n                errors.err_span_tokens(\n                    first_use_field,\n                    &format!(\n                        \"The short name of \\\"-{}\\\" was already used here.\",\n                        short_name\n                    ),\n                );\n                errors.err_span_tokens(field.field, \"Later usage here.\");\n            }\n\n            seen_short_names.insert(short_name, &field.field);\n        }\n\n        if let Some(long_name) = &field.long_name {\n            if let Some(first_use_field) = seen_long_names.get(&long_name) {\n                errors.err_span_tokens(\n                    *first_use_field,\n                    &format!(\"The long name of \\\"{}\\\" was already used here.\", long_name),\n                );\n                errors.err_span_tokens(field.field, \"Later usage here.\");\n            }\n\n            seen_long_names.insert(long_name, field.field);\n        }\n    }\n}\n\n/// Implement `argh::TopLevelCommand` or `argh::SubCommand` as appropriate.\nfn top_or_sub_cmd_impl(\n    errors: &Errors,\n    name: &syn::Ident,\n    type_attrs: &TypeAttrs,\n    generic_args: &syn::Generics,\n) -> TokenStream {\n    let description = String::new();\n    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();\n    if type_attrs.is_subcommand.is_none() {\n        // Not a subcommand\n        quote! {\n            #[automatically_derived]\n            impl #impl_generics argh::TopLevelCommand for #name #ty_generics #where_clause {}\n        }\n    } else {\n        let empty_str = syn::LitStr::new(\"\", Span::call_site());\n        let subcommand_name = type_attrs.name.as_ref().unwrap_or_else(|| {\n            errors.err(\n                name,\n                \"`#[argh(name = \\\"...\\\")]` attribute is required for subcommands\",\n            );\n            &empty_str\n        });\n        quote! {\n            #[automatically_derived]\n            impl #impl_generics argh::SubCommand for #name #ty_generics #where_clause {\n                const COMMAND: &'static argh::CommandInfo = &argh::CommandInfo {\n                    name: #subcommand_name,\n                    description: #description,\n                };\n            }\n        }\n    }\n}\n\n/// Declare a local slots to store each field in during parsing.\n///\n/// Most fields are stored in `Option<FieldType>` locals.\n/// `argh(option)` fields are stored in a `ParseValueSlotTy` along with a\n/// function that knows how to decode the appropriate value.\nfn declare_local_storage_for_from_args_fields<'a>(\n    fields: &'a [StructField<'a>],\n) -> impl Iterator<Item = TokenStream> + 'a {\n    fields.iter().map(|field| {\n        let field_name = &field.field.ident;\n        let field_type = &field.ty_without_wrapper;\n\n        // Wrap field types in `Option` if they aren't already `Option` or `Vec`-wrapped.\n        let field_slot_type = match field.optionality {\n            Optionality::Optional | Optionality::Repeating => (&field.field.ty).into_token_stream(),\n            Optionality::None | Optionality::Defaulted(_) => {\n                quote! { std::option::Option<#field_type> }\n            }\n            Optionality::DefaultedRepeating(_) => {\n                quote! { std::option::Option<std::vec::Vec<#field_type>> }\n            }\n        };\n\n        match field.kind {\n            FieldKind::Option | FieldKind::Positional => {\n                let from_str_fn = match &field.attrs.from_str_fn {\n                    Some(from_str_fn) => from_str_fn.into_token_stream(),\n                    None => {\n                        quote! {\n                            <#field_type as argh::FromArgValue>::from_arg_value\n                        }\n                    }\n                };\n\n                quote! {\n                    let mut #field_name: argh::ParseValueSlotTy<#field_slot_type, #field_type>\n                        = argh::ParseValueSlotTy {\n                            slot: std::default::Default::default(),\n                            parse_func: |_, value| { #from_str_fn(value) },\n                        };\n                }\n            }\n            FieldKind::SubCommand => {\n                quote! { let mut #field_name: #field_slot_type = None; }\n            }\n            FieldKind::Switch => {\n                quote! { let mut #field_name: #field_slot_type = argh::Flag::default(); }\n            }\n        }\n    })\n}\n\n/// Unwrap non-optional fields and take options out of their tuple slots.\nfn unwrap_from_args_fields<'a>(\n    fields: &'a [StructField<'a>],\n) -> impl Iterator<Item = TokenStream> + 'a {\n    fields.iter().map(|field| {\n        let field_name = field.name;\n        match field.kind {\n            FieldKind::Option | FieldKind::Positional => match &field.optionality {\n                Optionality::None => quote! {\n                    #field_name: #field_name.slot.unwrap()\n                },\n                Optionality::Optional | Optionality::Repeating => {\n                    quote! { #field_name: #field_name.slot }\n                }\n                Optionality::Defaulted(tokens) | Optionality::DefaultedRepeating(tokens) => {\n                    quote! {\n                        #field_name: #field_name.slot.unwrap_or_else(|| #tokens)\n                    }\n                }\n            },\n            FieldKind::Switch => field_name.into_token_stream(),\n            FieldKind::SubCommand => match field.optionality {\n                Optionality::None => quote! { #field_name: #field_name.unwrap() },\n                Optionality::Optional | Optionality::Repeating => field_name.into_token_stream(),\n                Optionality::Defaulted(_) | Optionality::DefaultedRepeating(_) => unreachable!(),\n            },\n        }\n    })\n}\n\n/// Entries of tokens like `(\"--some-flag-key\", 5)` that map from a flag key string\n/// to an index in the output table.\nfn flag_str_to_output_table_map_entries<'a>(fields: &'a [StructField<'a>]) -> Vec<TokenStream> {\n    let mut flag_str_to_output_table_map = vec![];\n\n    for (i, field) in fields.iter().enumerate() {\n        if let Some(short) = &field.attrs.short {\n            let short = format!(\"-{}\", short.value());\n            flag_str_to_output_table_map.push(quote! { (#short, #i) });\n        }\n        if let Some(long) = &field.long_name {\n            flag_str_to_output_table_map.push(quote! { (#long, #i) });\n        }\n    }\n    flag_str_to_output_table_map\n}\n\n/// For each non-optional field, add an entry to the `argh::MissingRequirements`.\nfn append_missing_requirements<'a>(\n    // missing_requirements_ident\n    mri: &syn::Ident,\n    fields: &'a [StructField<'a>],\n) -> impl Iterator<Item = TokenStream> + 'a {\n    let mri = mri.clone();\n    fields\n        .iter()\n        .filter(|f| f.optionality.is_required())\n        .map(move |field| {\n            let field_name = field.name;\n            match field.kind {\n                FieldKind::Switch => unreachable!(\"switches are always optional\"),\n                FieldKind::Positional => {\n                    let name = field.positional_arg_name();\n                    quote! {\n                        if #field_name.slot.is_none() {\n                            #mri.missing_positional_arg(#name)\n                        }\n                    }\n                }\n                FieldKind::Option => {\n                    let name = field.option_arg_name();\n                    quote! {\n                        if #field_name.slot.is_none() {\n                            #mri.missing_option(#name)\n                        }\n                    }\n                }\n                FieldKind::SubCommand => {\n                    let ty = field.ty_without_wrapper;\n                    quote! {\n                        if #field_name.is_none() {\n                            #mri.missing_subcommands(\n                                <#ty as argh::SubCommands>::COMMANDS\n                                    .iter()\n                                    .cloned()\n                                    .chain(\n                                        <#ty as argh::SubCommands>::dynamic_commands()\n                                            .iter()\n                                            .copied()\n                                    ),\n                            )\n                        }\n                    }\n                }\n            }\n        })\n}\n\n/// Require that a type can be a `switch`.\n/// Throws an error for all types except booleans and integers\nfn ty_expect_switch(errors: &Errors, ty: &syn::Type) -> bool {\n    fn ty_can_be_switch(ty: &syn::Type) -> bool {\n        if let syn::Type::Path(path) = ty {\n            if path.qself.is_some() {\n                return false;\n            }\n            if path.path.segments.len() != 1 {\n                return false;\n            }\n            let ident = &path.path.segments[0].ident;\n            // `Option<bool>` can be used as a `switch`.\n            if ident == \"Option\"\n                && let PathArguments::AngleBracketed(args) = &path.path.segments[0].arguments\n                && let GenericArgument::Type(Type::Path(p)) = &args.args[0]\n                && p.path.segments[0].ident == \"bool\"\n            {\n                return true;\n            }\n            [\n                \"bool\", \"u8\", \"u16\", \"u32\", \"u64\", \"u128\", \"i8\", \"i16\", \"i32\", \"i64\", \"i128\",\n            ]\n            .iter()\n            .any(|path| ident == path)\n        } else {\n            false\n        }\n    }\n\n    let res = ty_can_be_switch(ty);\n    if !res {\n        errors.err(\n            ty,\n            \"switches must be of type `bool`, `Option<bool>`, or integer type\",\n        );\n    }\n    res\n}\n\n/// Returns `Some(T)` if a type is `wrapper_name<T>` for any `wrapper_name` in `wrapper_names`.\nfn ty_inner<'a>(wrapper_names: &[&str], ty: &'a syn::Type) -> Option<&'a syn::Type> {\n    if let syn::Type::Path(path) = ty {\n        if path.qself.is_some() {\n            return None;\n        }\n        // Since we only check the last path segment, it isn't necessarily the case that\n        // we're referring to `std::vec::Vec` or `std::option::Option`, but there isn't\n        // a fool proof way to check these since name resolution happens after macro expansion,\n        // so this is likely \"good enough\" (so long as people don't have their own types called\n        // `Option` or `Vec` that take one generic parameter they're looking to parse).\n        let last_segment = path.path.segments.last()?;\n        if !wrapper_names.iter().any(|name| last_segment.ident == *name) {\n            return None;\n        }\n        if let syn::PathArguments::AngleBracketed(gen_args) = &last_segment.arguments {\n            let generic_arg = gen_args.args.first()?;\n            if let syn::GenericArgument::Type(ty) = &generic_arg {\n                return Some(ty);\n            }\n        }\n    }\n    None\n}\n\n/// Implements `FromArgs` and `SubCommands` for a `#![derive(FromArgs)]` enum.\nfn impl_from_args_enum(\n    errors: &Errors,\n    name: &syn::Ident,\n    type_attrs: &TypeAttrs,\n    generic_args: &syn::Generics,\n    de: &syn::DataEnum,\n) -> TokenStream {\n    parse_attrs::check_enum_type_attrs(errors, type_attrs, &de.enum_token.span);\n\n    // An enum variant like `<name>(<ty>)`\n    struct SubCommandVariant<'a> {\n        name: &'a syn::Ident,\n        ty: &'a syn::Type,\n    }\n\n    let mut dynamic_type_and_variant = None;\n\n    let variants: Vec<SubCommandVariant<'_>> = de\n        .variants\n        .iter()\n        .filter_map(|variant| {\n            let name = &variant.ident;\n            let ty = enum_only_single_field_unnamed_variants(errors, &variant.fields)?;\n            if parse_attrs::VariantAttrs::parse(errors, variant)\n                .is_dynamic\n                .is_some()\n            {\n                if dynamic_type_and_variant.is_some() {\n                    errors.err(variant, \"Only one variant can have the `dynamic` attribute\");\n                }\n                dynamic_type_and_variant = Some((ty, name));\n                None\n            } else {\n                Some(SubCommandVariant { name, ty })\n            }\n        })\n        .collect();\n\n    let name_repeating = std::iter::repeat(name.clone());\n    let variant_ty = variants.iter().map(|x| x.ty).collect::<Vec<_>>();\n    let variant_names = variants.iter().map(|x| x.name).collect::<Vec<_>>();\n    let dynamic_from_args =\n        dynamic_type_and_variant\n            .as_ref()\n            .map(|(dynamic_type, dynamic_variant)| {\n                quote! {\n                    if let Some(result) = <#dynamic_type as argh::DynamicSubCommand>::try_from_args(\n                        command_name, args) {\n                        return result.map(#name::#dynamic_variant);\n                    }\n                }\n            });\n    let dynamic_commands = dynamic_type_and_variant.as_ref().map(|(dynamic_type, _)| {\n        quote! {\n            fn dynamic_commands() -> &'static [&'static argh::CommandInfo] {\n                <#dynamic_type as argh::DynamicSubCommand>::commands()\n            }\n        }\n    });\n\n    let (impl_generics, ty_generics, where_clause) = generic_args.split_for_impl();\n    quote! {\n        impl #impl_generics argh::FromArgs for #name #ty_generics #where_clause {\n            fn from_args(command_name: &[&str], args: &[&str])\n                -> std::result::Result<Self, argh::EarlyExit>\n            {\n                let subcommand_name = if let Some(subcommand_name) = command_name.last() {\n                    *subcommand_name\n                } else {\n                    return Err(argh::EarlyExit::from(\"no subcommand name\".to_owned()));\n                };\n\n                #(\n                    if subcommand_name == <#variant_ty as argh::SubCommand>::COMMAND.name {\n                        return Ok(#name_repeating::#variant_names(\n                            <#variant_ty as argh::FromArgs>::from_args(command_name, args)?\n                        ));\n                    }\n                )*\n\n                #dynamic_from_args\n\n                Err(argh::EarlyExit::from(\"no subcommand matched\".to_owned()))\n            }\n        }\n\n        impl #impl_generics argh::SubCommands for #name #ty_generics #where_clause {\n            const COMMANDS: &'static [&'static argh::CommandInfo] = &[#(\n                <#variant_ty as argh::SubCommand>::COMMAND,\n            )*];\n\n            #dynamic_commands\n        }\n    }\n}\n\n/// Returns `Some(Bar)` if the field is a single-field unnamed variant like `Foo(Bar)`.\n/// Otherwise, generates an error.\nfn enum_only_single_field_unnamed_variants<'a>(\n    errors: &Errors,\n    variant_fields: &'a syn::Fields,\n) -> Option<&'a syn::Type> {\n    macro_rules! with_enum_suggestion {\n        ($help_text:literal) => {\n            concat!(\n                $help_text,\n                \"\\nInstead, use a variant with a single unnamed field for each subcommand:\\n\",\n                \"    enum MyCommandEnum {\\n\",\n                \"        SubCommandOne(SubCommandOne),\\n\",\n                \"        SubCommandTwo(SubCommandTwo),\\n\",\n                \"    }\",\n            )\n        };\n    }\n\n    match variant_fields {\n        syn::Fields::Named(fields) => {\n            errors.err(\n                fields,\n                with_enum_suggestion!(\n                    \"`#![derive(FromArgs)]` `enum`s do not support variants with named fields.\"\n                ),\n            );\n            None\n        }\n        syn::Fields::Unit => {\n            errors.err(\n                variant_fields,\n                with_enum_suggestion!(\n                    \"`#![derive(FromArgs)]` does not support `enum`s with no variants.\"\n                ),\n            );\n            None\n        }\n        syn::Fields::Unnamed(fields) => {\n            if fields.unnamed.len() != 1 {\n                errors.err(\n                    fields,\n                    with_enum_suggestion!(\n                        \"`#![derive(FromArgs)]` `enum` variants must only contain one field.\"\n                    ),\n                );\n                None\n            } else {\n                // `unwrap` is okay because of the length check above.\n                let first_field = fields.unnamed.first().unwrap();\n                Some(&first_field.ty)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/base/derive/argh/parse_attrs.rs",
    "content": "// Copyright (c) 2020 Google LLC All rights reserved.\n// Use of this source code is governed by a BSD-style\n// license that can be found in the LICENSE file.\n\nuse syn::parse::Parser;\nuse syn::punctuated::Punctuated;\n\nuse super::errors::Errors;\nuse proc_macro2::Span;\nuse std::collections::hash_map::{Entry, HashMap};\n\n/// Attributes applied to a field of a `#![derive(FromArgs)]` struct.\n#[derive(Default)]\npub struct FieldAttrs {\n    pub default: Option<syn::LitStr>,\n    pub description: Option<Description>,\n    pub from_str_fn: Option<syn::ExprPath>,\n    pub field_type: Option<FieldType>,\n    pub long: Option<Option<syn::LitStr>>,\n    pub short: Option<syn::LitChar>,\n    pub arg_name: Option<syn::LitStr>,\n    pub greedy: Option<syn::Path>,\n    pub hidden_help: bool,\n}\n\n/// The purpose of a particular field on a `#![derive(FromArgs)]` struct.\n#[derive(Copy, Clone, Eq, PartialEq)]\npub enum FieldKind {\n    /// Switches are booleans that are set to \"true\" by passing the flag.\n    Switch,\n    /// Options are `--key value`. They may be optional (using `Option`),\n    /// or repeating (using `Vec`), or required (neither `Option` nor `Vec`)\n    Option,\n    /// Subcommand fields (of which there can be at most one) refer to enums\n    /// containing one of several potential subcommands. They may be optional\n    /// (using `Option`) or required (no `Option`).\n    SubCommand,\n    /// Positional arguments are parsed literally if the input\n    /// does not begin with `-` or `--` and is not a subcommand.\n    /// They are parsed in declaration order, and only the last positional\n    /// argument in a type may be an `Option`, `Vec`, or have a default value.\n    Positional,\n}\n\n/// The type of a field on a `#![derive(FromArgs)]` struct.\n///\n/// This is a simple wrapper around `FieldKind` which includes the `syn::Ident`\n/// of the attribute containing the field kind.\npub struct FieldType {\n    pub kind: FieldKind,\n    pub ident: syn::Ident,\n}\n\n/// A description of a `#![derive(FromArgs)]` struct.\n///\n/// Defaults to the docstring if one is present, or `#[argh(description = \"...\")]`\n/// if one is provided.\npub struct Description {\n    /// Whether the description was an explicit annotation or whether it was a doc string.\n    pub explicit: bool,\n    pub content: syn::LitStr,\n}\n\nimpl FieldAttrs {\n    pub fn parse(errors: &Errors, field: &syn::Field) -> Self {\n        let mut this = Self::default();\n\n        for attr in &field.attrs {\n            if is_doc_attr(attr) {\n                parse_attr_doc(errors, attr, &mut this.description);\n                continue;\n            }\n\n            let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {\n                ml\n            } else {\n                continue;\n            };\n\n            for meta in ml {\n                let name = meta.path();\n                if name.is_ident(\"arg_name\") {\n                    if let Some(m) = errors.expect_meta_name_value(&meta) {\n                        this.parse_attr_arg_name(errors, m);\n                    }\n                } else if name.is_ident(\"default\") {\n                    if let Some(m) = errors.expect_meta_name_value(&meta) {\n                        this.parse_attr_default(errors, m);\n                    }\n                } else if name.is_ident(\"description\") {\n                    if let Some(m) = errors.expect_meta_name_value(&meta) {\n                        parse_attr_description(errors, m, &mut this.description);\n                    }\n                } else if name.is_ident(\"from_str_fn\") {\n                    if let Some(m) = errors.expect_meta_list(&meta) {\n                        this.parse_attr_from_str_fn(errors, m);\n                    }\n                } else if name.is_ident(\"long\") {\n                    if let Some(m) = errors.expect_meta_name_value(&meta) {\n                        this.parse_attr_long(errors, m);\n                    }\n                } else if name.is_ident(\"option\") {\n                    parse_attr_field_type(errors, &meta, FieldKind::Option, &mut this.field_type);\n                } else if name.is_ident(\"short\") {\n                    if let Some(m) = errors.expect_meta_name_value(&meta) {\n                        this.parse_attr_short(errors, m);\n                    }\n                } else if name.is_ident(\"subcommand\") {\n                    parse_attr_field_type(\n                        errors,\n                        &meta,\n                        FieldKind::SubCommand,\n                        &mut this.field_type,\n                    );\n                } else if name.is_ident(\"switch\") {\n                    parse_attr_field_type(errors, &meta, FieldKind::Switch, &mut this.field_type);\n                } else if name.is_ident(\"positional\") {\n                    parse_attr_field_type(\n                        errors,\n                        &meta,\n                        FieldKind::Positional,\n                        &mut this.field_type,\n                    );\n                } else if name.is_ident(\"greedy\") {\n                    this.greedy = Some(name.clone());\n                } else if name.is_ident(\"hidden_help\") {\n                    this.hidden_help = true;\n                } else {\n                    errors.err(\n                        &meta,\n                        concat!(\n                            \"Invalid field-level `argh` attribute\\n\",\n                            \"Expected one of: `arg_name`, `default`, `description`, `from_str_fn`, `greedy`, \",\n                            \"`long`, `option`, `short`, `subcommand`, `switch`, `hidden_help`\",\n                        ),\n                    );\n                }\n            }\n        }\n\n        if let (Some(default), Some(field_type)) = (&this.default, &this.field_type) {\n            match field_type.kind {\n                FieldKind::Option | FieldKind::Positional => {}\n                FieldKind::SubCommand | FieldKind::Switch => errors.err(\n                    default,\n                    \"`default` may only be specified on `#[argh(option)]` \\\n                     or `#[argh(positional)]` fields\",\n                ),\n            }\n        }\n\n        match (&this.greedy, this.field_type.as_ref().map(|f| f.kind)) {\n            (Some(_), Some(FieldKind::Positional)) => {}\n            (Some(greedy), Some(_)) => errors.err(\n                &greedy,\n                \"`greedy` may only be specified on `#[argh(positional)]` \\\n                    fields\",\n            ),\n            _ => {}\n        }\n\n        if let Some(d) = &this.description {\n            check_option_description(errors, d.content.value().trim(), d.content.span());\n        }\n\n        this\n    }\n\n    fn parse_attr_from_str_fn(&mut self, errors: &Errors, m: &syn::MetaList) {\n        parse_attr_fn_name(errors, m, \"from_str_fn\", &mut self.from_str_fn)\n    }\n\n    fn parse_attr_default(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        parse_attr_single_string(errors, m, \"default\", &mut self.default);\n    }\n\n    fn parse_attr_arg_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        parse_attr_single_string(errors, m, \"arg_name\", &mut self.arg_name);\n    }\n\n    fn parse_attr_long(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        if let Some(first) = &self.long {\n            errors.duplicate_attrs(\"long\", first, m);\n        } else if let syn::Expr::Path(syn::ExprPath { path, .. }) = &m.value\n            && let Some(ident) = path.get_ident()\n            && ident.to_string().eq_ignore_ascii_case(\"none\")\n        {\n            self.long = Some(None);\n        } else if let Some(lit_str) = errors.expect_lit_str(&m.value) {\n            self.long = Some(Some(lit_str.clone()));\n        }\n        if let Some(Some(long)) = &self.long {\n            let value = long.value();\n            check_long_name(errors, long, &value);\n        }\n    }\n\n    fn parse_attr_short(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        if let Some(first) = &self.short {\n            errors.duplicate_attrs(\"short\", first, m);\n        } else if let Some(lit_char) = errors.expect_lit_char(&m.value) {\n            self.short = Some(lit_char.clone());\n            if !lit_char.value().is_ascii() {\n                errors.err(lit_char, \"Short names must be ASCII\");\n            }\n        }\n    }\n}\n\npub(crate) fn check_long_name(errors: &Errors, spanned: &impl syn::spanned::Spanned, value: &str) {\n    if !value.is_ascii() {\n        errors.err(spanned, \"Long names must be ASCII\");\n    }\n    if !value\n        .chars()\n        .all(|c| c.is_lowercase() || c == '-' || c.is_ascii_digit())\n    {\n        errors.err(\n            spanned,\n            \"Long names may only contain lowercase letters, digits, and dashes\",\n        );\n    }\n}\n\nfn parse_attr_fn_name(\n    errors: &Errors,\n    m: &syn::MetaList,\n    attr_name: &str,\n    slot: &mut Option<syn::ExprPath>,\n) {\n    if let Some(first) = slot {\n        errors.duplicate_attrs(attr_name, first, m);\n    }\n\n    *slot = errors.ok(m.parse_args());\n}\n\nfn parse_attr_field_type(\n    errors: &Errors,\n    meta: &syn::Meta,\n    kind: FieldKind,\n    slot: &mut Option<FieldType>,\n) {\n    if let Some(path) = errors.expect_meta_word(meta) {\n        if let Some(first) = slot {\n            errors.duplicate_attrs(\"field kind\", &first.ident, path);\n        } else if let Some(word) = path.get_ident() {\n            *slot = Some(FieldType {\n                kind,\n                ident: word.clone(),\n            });\n        }\n    }\n}\n\n// Whether the attribute is one like `#[<name> ...]`\nfn is_matching_attr(name: &str, attr: &syn::Attribute) -> bool {\n    attr.path().segments.len() == 1 && attr.path().segments[0].ident == name\n}\n\n/// Checks for `#[doc ...]`, which is generated by doc comments.\nfn is_doc_attr(attr: &syn::Attribute) -> bool {\n    is_matching_attr(\"doc\", attr)\n}\n\n/// Checks for `#[argh ...]`\nfn is_argh_attr(attr: &syn::Attribute) -> bool {\n    is_matching_attr(\"argh\", attr)\n}\n\n/// Filters out non-`#[argh(...)]` attributes and converts to a sequence of `syn::Meta`.\nfn argh_attr_to_meta_list(\n    errors: &Errors,\n    attr: &syn::Attribute,\n) -> Option<impl IntoIterator<Item = syn::Meta>> {\n    if !is_argh_attr(attr) {\n        return None;\n    }\n    let ml = errors.expect_meta_list(&attr.meta)?;\n    errors.ok(ml.parse_args_with(\n        syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,\n    ))\n}\n\n/// Represents a `#[derive(FromArgs)]` type's top-level attributes.\n#[derive(Default)]\npub struct TypeAttrs {\n    pub is_subcommand: Option<syn::Ident>,\n    pub name: Option<syn::LitStr>,\n    pub description: Option<Description>,\n    pub examples: Vec<syn::LitStr>,\n    pub notes: Vec<syn::LitStr>,\n    pub error_codes: Vec<(syn::LitInt, syn::LitStr)>,\n    /// Arguments that trigger printing of the help message\n    pub help_triggers: Option<Vec<syn::LitStr>>,\n}\n\nimpl TypeAttrs {\n    /// Parse top-level `#[argh(...)]` attributes\n    pub fn parse(errors: &Errors, derive_input: &syn::DeriveInput) -> Self {\n        let mut this = TypeAttrs::default();\n\n        for attr in &derive_input.attrs {\n            if is_doc_attr(attr) {\n                parse_attr_doc(errors, attr, &mut this.description);\n                continue;\n            }\n\n            let ml: Vec<syn::Meta> = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {\n                ml.into_iter().collect()\n            } else {\n                continue;\n            };\n\n            for meta in ml.iter() {\n                let name = meta.path();\n                if name.is_ident(\"description\") {\n                    if let Some(m) = errors.expect_meta_name_value(meta) {\n                        parse_attr_description(errors, m, &mut this.description);\n                    }\n                } else if name.is_ident(\"error_code\") {\n                    if let Some(m) = errors.expect_meta_list(meta) {\n                        this.parse_attr_error_code(errors, m);\n                    }\n                } else if name.is_ident(\"example\") {\n                    if let Some(m) = errors.expect_meta_name_value(meta) {\n                        this.parse_attr_example(errors, m);\n                    }\n                } else if name.is_ident(\"name\") {\n                    if let Some(m) = errors.expect_meta_name_value(meta) {\n                        this.parse_attr_name(errors, m);\n                    }\n                } else if name.is_ident(\"note\") {\n                    if let Some(m) = errors.expect_meta_name_value(meta) {\n                        this.parse_attr_note(errors, m);\n                    }\n                } else if name.is_ident(\"subcommand\") {\n                    if let Some(ident) = errors.expect_meta_word(meta).and_then(|p| p.get_ident()) {\n                        this.parse_attr_subcommand(errors, ident);\n                    }\n                } else if name.is_ident(\"help_triggers\") {\n                    if let Some(m) = errors.expect_meta_list(meta) {\n                        Self::parse_help_triggers(m, errors, &mut this);\n                    }\n                } else {\n                    errors.err(\n                        meta,\n                        concat!(\n                            \"Invalid type-level `argh` attribute\\n\",\n                            \"Expected one of: `description`, `error_code`, `example`, `name`, \",\n                            \"`note`, `subcommand`, `help_triggers`\",\n                        ),\n                    );\n                }\n            }\n\n            if this.is_subcommand.is_some() && this.help_triggers.is_some() {\n                let help_meta = ml\n                    .iter()\n                    .find(|meta| meta.path().is_ident(\"help_triggers\"))\n                    .unwrap();\n                errors.err(help_meta, \"Cannot use `help_triggers` on a subcommand\");\n            }\n        }\n\n        this.check_error_codes(errors);\n        this\n    }\n\n    /// Checks that error codes are within range for `i32` and that they are\n    /// never duplicated.\n    fn check_error_codes(&self, errors: &Errors) {\n        // map from error code to index\n        let mut map: HashMap<u64, usize> = HashMap::new();\n        for (index, (lit_int, _lit_str)) in self.error_codes.iter().enumerate() {\n            let value = match lit_int.base10_parse::<u64>() {\n                Ok(v) => v,\n                Err(e) => {\n                    errors.push(e);\n                    continue;\n                }\n            };\n            if value > (i32::MAX as u64) {\n                errors.err(lit_int, \"Error code out of range for `i32`\");\n            }\n            match map.entry(value) {\n                Entry::Occupied(previous) => {\n                    let previous_index = *previous.get();\n                    let (previous_lit_int, _previous_lit_str) = &self.error_codes[previous_index];\n                    errors.err(lit_int, &format!(\"Duplicate error code {}\", value));\n                    errors.err(\n                        previous_lit_int,\n                        &format!(\"Error code {} previously defined here\", value),\n                    );\n                }\n                Entry::Vacant(slot) => {\n                    slot.insert(index);\n                }\n            }\n        }\n    }\n\n    fn parse_attr_error_code(&mut self, errors: &Errors, ml: &syn::MetaList) {\n        errors.ok(ml.parse_args_with(|input: syn::parse::ParseStream| {\n            let err_code = input.parse()?;\n            input.parse::<syn::Token![,]>()?;\n            let err_msg = input.parse()?;\n            if let (Some(err_code), Some(err_msg)) = (\n                errors.expect_lit_int(&err_code),\n                errors.expect_lit_str(&err_msg),\n            ) {\n                self.error_codes.push((err_code.clone(), err_msg.clone()));\n            }\n            Ok(())\n        }));\n    }\n\n    fn parse_attr_example(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        parse_attr_multi_string(errors, m, &mut self.examples)\n    }\n\n    fn parse_attr_name(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        parse_attr_single_string(errors, m, \"name\", &mut self.name);\n        if let Some(name) = &self.name\n            && name.value() == \"help\"\n        {\n            errors.err(name, \"Custom `help` commands are not supported.\");\n        }\n    }\n\n    fn parse_attr_note(&mut self, errors: &Errors, m: &syn::MetaNameValue) {\n        parse_attr_multi_string(errors, m, &mut self.notes)\n    }\n\n    fn parse_attr_subcommand(&mut self, errors: &Errors, ident: &syn::Ident) {\n        if let Some(first) = &self.is_subcommand {\n            errors.duplicate_attrs(\"subcommand\", first, ident);\n        } else {\n            self.is_subcommand = Some(ident.clone());\n        }\n    }\n\n    // get the list of arguments that trigger printing of the help message as a vector of strings (help_arguments(\"-h\", \"--help\", \"help\"))\n    fn parse_help_triggers(m: &syn::MetaList, errors: &Errors, this: &mut TypeAttrs) {\n        let parser = Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;\n        match parser.parse(m.tokens.clone().into()) {\n            Ok(args) => {\n                let mut triggers = Vec::new();\n                for arg in args {\n                    if let syn::Expr::Lit(syn::ExprLit {\n                        lit: syn::Lit::Str(lit_str),\n                        ..\n                    }) = arg\n                    {\n                        triggers.push(lit_str);\n                    }\n                }\n\n                this.help_triggers = Some(triggers);\n            }\n            Err(err) => errors.push(err),\n        }\n    }\n}\n\n/// Represents an enum variant's attributes.\n#[derive(Default)]\npub struct VariantAttrs {\n    pub is_dynamic: Option<syn::Path>,\n}\n\nimpl VariantAttrs {\n    /// Parse enum variant `#[argh(...)]` attributes\n    pub fn parse(errors: &Errors, variant: &syn::Variant) -> Self {\n        let mut this = VariantAttrs::default();\n\n        let fields = match &variant.fields {\n            syn::Fields::Named(fields) => Some(&fields.named),\n            syn::Fields::Unnamed(fields) => Some(&fields.unnamed),\n            syn::Fields::Unit => None,\n        };\n\n        for field in fields.into_iter().flatten() {\n            for attr in &field.attrs {\n                if is_argh_attr(attr) {\n                    err_unused_enum_attr(errors, attr);\n                }\n            }\n        }\n\n        for attr in &variant.attrs {\n            let ml = if let Some(ml) = argh_attr_to_meta_list(errors, attr) {\n                ml\n            } else {\n                continue;\n            };\n\n            for meta in ml {\n                let name = meta.path();\n                if name.is_ident(\"dynamic\") {\n                    if let Some(prev) = this.is_dynamic.as_ref() {\n                        errors.duplicate_attrs(\"dynamic\", prev, &meta);\n                    } else {\n                        this.is_dynamic = errors.expect_meta_word(&meta).cloned();\n                    }\n                } else {\n                    errors.err(\n                        &meta,\n                        \"Invalid variant-level `argh` attribute\\n\\\n                         Variants can only have the #[argh(dynamic)] attribute.\",\n                    );\n                }\n            }\n        }\n\n        this\n    }\n}\n\nfn check_option_description(errors: &Errors, desc: &str, span: Span) {\n    let chars = &mut desc.trim().chars();\n    match (chars.next(), chars.next()) {\n        (Some(x), _) if x.is_lowercase() => {}\n        // If both the first and second letter are not lowercase,\n        // this is likely an initialism which should be allowed.\n        (Some(x), Some(y)) if !x.is_lowercase() && (y.is_alphanumeric() && !y.is_lowercase()) => {}\n        _ => {\n            errors.err_span(span, \"Descriptions must begin with a lowercase letter\");\n        }\n    }\n}\n\nfn parse_attr_single_string(\n    errors: &Errors,\n    m: &syn::MetaNameValue,\n    name: &str,\n    slot: &mut Option<syn::LitStr>,\n) {\n    if let Some(first) = slot {\n        errors.duplicate_attrs(name, first, m);\n    } else if let Some(lit_str) = errors.expect_lit_str(&m.value) {\n        *slot = Some(lit_str.clone());\n    }\n}\n\nfn parse_attr_multi_string(errors: &Errors, m: &syn::MetaNameValue, list: &mut Vec<syn::LitStr>) {\n    if let Some(lit_str) = errors.expect_lit_str(&m.value) {\n        list.push(lit_str.clone());\n    }\n}\n\nfn parse_attr_doc(errors: &Errors, attr: &syn::Attribute, slot: &mut Option<Description>) {\n    let nv = if let Some(nv) = errors.expect_meta_name_value(&attr.meta) {\n        nv\n    } else {\n        return;\n    };\n\n    // Don't replace an existing explicit description.\n    if slot.as_ref().map(|d| d.explicit).unwrap_or(false) {\n        return;\n    }\n\n    if let Some(lit_str) = errors.expect_lit_str(&nv.value) {\n        let lit_str = if let Some(previous) = slot {\n            let previous = &previous.content;\n            let previous_span = previous.span();\n            syn::LitStr::new(\n                &(previous.value() + &unescape_doc(lit_str.value())),\n                previous_span,\n            )\n        } else {\n            syn::LitStr::new(&unescape_doc(lit_str.value()), lit_str.span())\n        };\n        *slot = Some(Description {\n            explicit: false,\n            content: lit_str,\n        });\n    }\n}\n\n/// Replaces escape sequences in doc-comments with the characters they represent.\n///\n/// Rustdoc understands CommonMark escape sequences consisting of a backslash followed by an ASCII\n/// punctuation character. Any other backslash is treated as a literal backslash.\nfn unescape_doc(s: String) -> String {\n    let mut result = String::with_capacity(s.len());\n\n    let mut characters = s.chars().peekable();\n    while let Some(mut character) = characters.next() {\n        if character == '\\\\'\n            && let Some(next_character) = characters.peek()\n            && next_character.is_ascii_punctuation()\n        {\n            character = *next_character;\n            characters.next();\n        }\n\n        // Braces must be escaped as this string will be used as a format string\n        if character == '{' || character == '}' {\n            result.push(character);\n        }\n\n        result.push(character);\n    }\n\n    result\n}\n\nfn parse_attr_description(errors: &Errors, m: &syn::MetaNameValue, slot: &mut Option<Description>) {\n    let lit_str = if let Some(lit_str) = errors.expect_lit_str(&m.value) {\n        lit_str\n    } else {\n        return;\n    };\n\n    // Don't allow multiple explicit (non doc-comment) descriptions\n    if let Some(description) = slot\n        && description.explicit\n    {\n        errors.duplicate_attrs(\"description\", &description.content, lit_str);\n    }\n\n    *slot = Some(Description {\n        explicit: true,\n        content: lit_str.clone(),\n    });\n}\n\n/// Checks that a `#![derive(FromArgs)]` enum has an `#[argh(subcommand)]`\n/// attribute and that it does not have any other type-level `#[argh(...)]` attributes.\npub fn check_enum_type_attrs(errors: &Errors, type_attrs: &TypeAttrs, type_span: &Span) {\n    let TypeAttrs {\n        is_subcommand,\n        name,\n        description,\n        examples,\n        notes,\n        error_codes,\n        help_triggers,\n    } = type_attrs;\n\n    // Ensure that `#[argh(subcommand)]` is present.\n    if is_subcommand.is_none() {\n        errors.err_span(\n            *type_span,\n            concat!(\n                \"`#![derive(FromArgs)]` on `enum`s can only be used to enumerate subcommands.\\n\",\n                \"Consider adding `#[argh(subcommand)]` to the `enum` declaration.\",\n            ),\n        );\n    }\n\n    // Error on all other type-level attributes.\n    if let Some(name) = name {\n        err_unused_enum_attr(errors, name);\n    }\n    if let Some(description) = description\n        && description.explicit\n    {\n        err_unused_enum_attr(errors, &description.content);\n    }\n    if let Some(example) = examples.first() {\n        err_unused_enum_attr(errors, example);\n    }\n    if let Some(note) = notes.first() {\n        err_unused_enum_attr(errors, note);\n    }\n    if let Some(err_code) = error_codes.first() {\n        err_unused_enum_attr(errors, &err_code.0);\n    }\n    if let Some(triggers) = help_triggers\n        && let Some(trigger) = triggers.first()\n    {\n        err_unused_enum_attr(errors, trigger);\n    }\n}\n\nfn err_unused_enum_attr(errors: &Errors, location: &impl syn::spanned::Spanned) {\n    errors.err(\n        location,\n        concat!(\n            \"Unused `argh` attribute on `#![derive(FromArgs)]` enum. \",\n            \"Such `enum`s can only be used to dispatch to subcommands, \",\n            \"and should only contain the #[argh(subcommand)] attribute.\",\n        ),\n    );\n}\n"
  },
  {
    "path": "native/src/base/derive/decodable.rs",
    "content": "use proc_macro2::TokenStream;\nuse quote::{quote, quote_spanned};\nuse syn::spanned::Spanned;\nuse syn::{Data, DeriveInput, Fields, GenericParam, parse_macro_input, parse_quote};\n\npub(crate) fn derive_decodable(input: proc_macro::TokenStream) -> proc_macro::TokenStream {\n    let input = parse_macro_input!(input as DeriveInput);\n\n    let name = input.ident;\n\n    // Add a bound `T: Decodable` to every type parameter T.\n    let mut generics = input.generics;\n    for param in &mut generics.params {\n        if let GenericParam::Type(ref mut type_param) = *param {\n            type_param\n                .bounds\n                .push(parse_quote!(crate::socket::Decodable));\n        }\n    }\n\n    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();\n\n    let encode = gen_encode(&input.data);\n    let decode = gen_decode(&input.data);\n\n    let expanded = quote! {\n        // The generated impl.\n        impl #impl_generics crate::socket::Encodable for #name #ty_generics #where_clause {\n            fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {\n                #encode\n                Ok(())\n            }\n        }\n        impl #impl_generics crate::socket::Decodable for #name #ty_generics #where_clause {\n            fn decode(r: &mut impl std::io::Read) -> std::io::Result<Self> {\n                let val = #decode;\n                Ok(val)\n            }\n        }\n    };\n    proc_macro::TokenStream::from(expanded)\n}\n\n// Generate an expression to encode each field.\nfn gen_encode(data: &Data) -> TokenStream {\n    match *data {\n        Data::Struct(ref data) => {\n            match data.fields {\n                Fields::Named(ref fields) => {\n                    // Expands to an expression like\n                    //\n                    //     self.x.encode(w)?; self.y.encode(w)?; self.z.encode(w)?;\n                    let recurse = fields.named.iter().map(|f| {\n                        let name = &f.ident;\n                        quote_spanned! { f.span() =>\n                            crate::socket::Encodable::encode(&self.#name, w)?;\n                        }\n                    });\n                    quote! {\n                        #(#recurse)*\n                    }\n                }\n                _ => unimplemented!(),\n            }\n        }\n        Data::Enum(_) | Data::Union(_) => unimplemented!(),\n    }\n}\n\n// Generate an expression to decode each field.\nfn gen_decode(data: &Data) -> TokenStream {\n    match *data {\n        Data::Struct(ref data) => {\n            match data.fields {\n                Fields::Named(ref fields) => {\n                    // Expands to an expression like\n                    //\n                    //     Self { x: Decodable::decode(r)?, y: Decodable::decode(r)?, }\n                    let recurse = fields.named.iter().map(|f| {\n                        let name = &f.ident;\n                        quote_spanned! { f.span() =>\n                            #name: crate::socket::Decodable::decode(r)?,\n                        }\n                    });\n                    quote! {\n                        Self { #(#recurse)* }\n                    }\n                }\n                _ => unimplemented!(),\n            }\n        }\n        Data::Enum(_) | Data::Union(_) => unimplemented!(),\n    }\n}\n"
  },
  {
    "path": "native/src/base/derive/lib.rs",
    "content": "#![recursion_limit = \"256\"]\n\nuse proc_macro::TokenStream;\n\nmod argh;\nmod decodable;\n\n#[proc_macro_derive(Decodable)]\npub fn derive_decodable(input: TokenStream) -> TokenStream {\n    decodable::derive_decodable(input)\n}\n\n/// Entrypoint for `#[derive(FromArgs)]`.\n#[proc_macro_derive(FromArgs, attributes(argh))]\npub fn argh_derive(input: TokenStream) -> TokenStream {\n    let ast = syn::parse_macro_input!(input as syn::DeriveInput);\n    let token = argh::impl_from_args(&ast);\n    token.into()\n}\n"
  },
  {
    "path": "native/src/base/dir.rs",
    "content": "use crate::cxx_extern::readlinkat;\nuse crate::{\n    FsPathBuilder, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf, cstr, errno,\n    fd_path, fd_set_attr,\n};\nuse libc::{dirent, mode_t};\nuse nix::errno::Errno;\nuse nix::fcntl::{AtFlags, OFlag};\nuse nix::sys::stat::Mode;\nuse nix::unistd::UnlinkatFlags;\nuse std::fs::File;\nuse std::ops::Deref;\nuse std::os::fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd, RawFd};\nuse std::ptr::NonNull;\nuse std::slice;\n\npub struct DirEntry<'a> {\n    dir: &'a Directory,\n    entry: NonNull<dirent>,\n    d_name_len: usize,\n}\n\nimpl DirEntry<'_> {\n    pub fn as_ptr(&self) -> *mut dirent {\n        self.entry.as_ptr()\n    }\n\n    pub fn name(&self) -> &Utf8CStr {\n        // SAFETY: Utf8CStr is already validated in Directory::read\n        unsafe {\n            Utf8CStr::from_bytes_unchecked(slice::from_raw_parts(\n                self.d_name.as_ptr().cast(),\n                self.d_name_len,\n            ))\n        }\n    }\n\n    pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {\n        self.dir.path_at(self.name(), buf)\n    }\n\n    pub fn is_dir(&self) -> bool {\n        self.d_type == libc::DT_DIR\n    }\n\n    pub fn is_file(&self) -> bool {\n        self.d_type == libc::DT_REG\n    }\n\n    pub fn is_symlink(&self) -> bool {\n        self.d_type == libc::DT_LNK\n    }\n\n    pub fn is_block_device(&self) -> bool {\n        self.d_type == libc::DT_BLK\n    }\n\n    pub fn is_char_device(&self) -> bool {\n        self.d_type == libc::DT_CHR\n    }\n\n    pub fn is_fifo(&self) -> bool {\n        self.d_type == libc::DT_FIFO\n    }\n\n    pub fn is_socket(&self) -> bool {\n        self.d_type == libc::DT_SOCK\n    }\n\n    pub fn unlink(&self) -> OsResult<'_, ()> {\n        let flag = if self.is_dir() {\n            UnlinkatFlags::RemoveDir\n        } else {\n            UnlinkatFlags::NoRemoveDir\n        };\n        self.dir.unlink_at(self.name(), flag)\n    }\n\n    pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {\n        self.dir.read_link_at(self.name(), buf)\n    }\n\n    pub fn open_as_dir(&self) -> OsResult<'_, Directory> {\n        if !self.is_dir() {\n            return Err(OsError::new(\n                Errno::ENOTDIR,\n                \"fdopendir\",\n                Some(self.name()),\n                None,\n            ));\n        }\n        self.dir.open_as_dir_at(self.name())\n    }\n\n    pub fn open_as_file(&self, flags: OFlag) -> OsResult<'_, File> {\n        if self.is_dir() {\n            return Err(OsError::new(\n                Errno::EISDIR,\n                \"open_as_file\",\n                Some(self.name()),\n                None,\n            ));\n        }\n        self.dir.open_as_file_at(self.name(), flags, 0)\n    }\n\n    pub fn rename_to<'a, 'entry: 'a>(\n        &'entry self,\n        new_dir: impl AsFd,\n        path: &'a Utf8CStr,\n    ) -> OsResult<'a, ()> {\n        self.dir.rename_at(self.name(), new_dir, path)\n    }\n}\n\nimpl Deref for DirEntry<'_> {\n    type Target = dirent;\n\n    fn deref(&self) -> &dirent {\n        unsafe { self.entry.as_ref() }\n    }\n}\n\n#[repr(transparent)]\npub struct Directory {\n    inner: NonNull<libc::DIR>,\n}\n\npub enum WalkResult {\n    Continue,\n    Abort,\n    Skip,\n}\n\nimpl Directory {\n    fn open_at<'a>(&self, name: &'a Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'a, OwnedFd> {\n        nix::fcntl::openat(\n            self,\n            name,\n            flags | OFlag::O_CLOEXEC,\n            Mode::from_bits_truncate(mode),\n        )\n        .into_os_result(\"openat\", Some(name), None)\n    }\n\n    fn path_at(&self, name: &Utf8CStr, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {\n        self.resolve_path(buf)?;\n        buf.append_path(name);\n        Ok(())\n    }\n}\n\n// Low-level methods, we should track the caller when error occurs, so return OsResult.\nimpl Directory {\n    pub fn open(path: &Utf8CStr) -> OsResult<'_, Directory> {\n        let dirp = unsafe { libc::opendir(path.as_ptr()) };\n        let dirp = dirp.into_os_result(\"opendir\", Some(path), None)?;\n        Ok(Directory { inner: dirp })\n    }\n\n    pub fn read(&mut self) -> OsResult<'static, Option<DirEntry<'_>>> {\n        *errno() = 0;\n        let e = unsafe { libc::readdir(self.inner.as_ptr()) };\n        if e.is_null() {\n            return if *errno() != 0 {\n                Err(OsError::last_os_error(\"readdir\", None, None))\n            } else {\n                Ok(None)\n            };\n        }\n        // Skip non UTF-8 entries, \".\", and \"..\"\n        unsafe {\n            let entry = &*e;\n\n            let Ok(name) = Utf8CStr::from_ptr(entry.d_name.as_ptr()) else {\n                return self.read();\n            };\n\n            if name == \".\" || name == \"..\" {\n                self.read()\n            } else {\n                let e = DirEntry {\n                    dir: self,\n                    entry: NonNull::from(entry),\n                    d_name_len: name.as_bytes_with_nul().len(),\n                };\n                Ok(Some(e))\n            }\n        }\n    }\n\n    pub fn rewind(&mut self) {\n        unsafe { libc::rewinddir(self.inner.as_ptr()) };\n    }\n\n    pub fn open_as_dir_at<'a>(&self, name: &'a Utf8CStr) -> OsResult<'a, Directory> {\n        let fd = self.open_at(name, OFlag::O_RDONLY, 0)?;\n        Directory::try_from(fd).map_err(|e| e.set_args(Some(name), None))\n    }\n\n    pub fn open_as_file_at<'a>(\n        &self,\n        name: &'a Utf8CStr,\n        flags: OFlag,\n        mode: mode_t,\n    ) -> OsResult<'a, File> {\n        let fd = self.open_at(name, flags, mode)?;\n        Ok(File::from(fd))\n    }\n\n    pub fn read_link_at<'a>(\n        &self,\n        name: &'a Utf8CStr,\n        buf: &mut dyn Utf8CStrBuf,\n    ) -> OsResult<'a, ()> {\n        buf.clear();\n        unsafe {\n            readlinkat(\n                self.as_raw_fd(),\n                name.as_ptr(),\n                buf.as_mut_ptr().cast(),\n                buf.capacity(),\n            )\n            .check_os_err(\"readlinkat\", Some(name), None)?;\n        }\n        buf.rebuild().ok();\n        Ok(())\n    }\n\n    pub fn mkdir_at<'a>(&self, name: &'a Utf8CStr, mode: mode_t) -> OsResult<'a, ()> {\n        match nix::sys::stat::mkdirat(self, name, Mode::from_bits_truncate(mode)) {\n            Ok(_) | Err(Errno::EEXIST) => Ok(()),\n            Err(e) => Err(OsError::new(e, \"mkdirat\", Some(name), None)),\n        }\n    }\n\n    // ln -s target self/name\n    pub fn create_symlink_at<'a>(\n        &self,\n        name: &'a Utf8CStr,\n        target: &'a Utf8CStr,\n    ) -> OsResult<'a, ()> {\n        nix::unistd::symlinkat(target, self, name).check_os_err(\n            \"symlinkat\",\n            Some(target),\n            Some(name),\n        )\n    }\n\n    pub fn unlink_at<'a>(&self, name: &'a Utf8CStr, flag: UnlinkatFlags) -> OsResult<'a, ()> {\n        nix::unistd::unlinkat(self, name, flag).check_os_err(\"unlinkat\", Some(name), None)\n    }\n\n    pub fn contains_path(&self, path: &Utf8CStr) -> bool {\n        // WARNING: Using faccessat is incorrect, because the raw linux kernel syscall\n        // does not support the flag AT_SYMLINK_NOFOLLOW until 5.8 with faccessat2.\n        // Use fstatat to check the existence of a file instead.\n        nix::sys::stat::fstatat(self, path, AtFlags::AT_SYMLINK_NOFOLLOW).is_ok()\n    }\n\n    pub fn resolve_path(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {\n        fd_path(self.as_raw_fd(), buf)\n    }\n\n    pub fn rename_at<'a>(\n        &self,\n        old: &'a Utf8CStr,\n        new_dir: impl AsFd,\n        new: &'a Utf8CStr,\n    ) -> OsResult<'a, ()> {\n        nix::fcntl::renameat(self, old, new_dir, new).check_os_err(\"renameat\", Some(old), Some(new))\n    }\n}\n\n// High-level helper methods, composed of multiple operations.\n// We should treat these as application logic and log ASAP, so return LoggedResult.\nimpl Directory {\n    pub fn post_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(\n        &mut self,\n        mut f: F,\n    ) -> LoggedResult<WalkResult> {\n        self.post_order_walk_impl(&mut f)\n    }\n\n    pub fn pre_order_walk<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(\n        &mut self,\n        mut f: F,\n    ) -> LoggedResult<WalkResult> {\n        self.pre_order_walk_impl(&mut f)\n    }\n\n    pub fn remove_all(mut self) -> LoggedResult<()> {\n        self.post_order_walk(|e| {\n            e.unlink()?;\n            Ok(WalkResult::Continue)\n        })?;\n        Ok(())\n    }\n\n    pub fn copy_into(&mut self, dir: &Directory) -> LoggedResult<()> {\n        let mut buf = cstr::buf::default();\n        self.copy_into_impl(dir, &mut buf)\n    }\n\n    pub fn move_into(&mut self, dir: &Directory) -> LoggedResult<()> {\n        while let Some(ref e) = self.read()? {\n            if e.is_dir() && dir.contains_path(e.name()) {\n                // Destination folder exists, needs recursive move\n                let mut src = e.open_as_dir()?;\n                let dest = dir.open_as_dir_at(e.name())?;\n                src.move_into(&dest)?;\n                return Ok(e.unlink()?);\n            }\n            e.rename_to(dir, e.name())?;\n        }\n        Ok(())\n    }\n\n    pub fn link_into(&mut self, dir: &Directory) -> LoggedResult<()> {\n        let mut buf = cstr::buf::default();\n        self.link_into_impl(dir, &mut buf)\n    }\n}\n\nimpl Directory {\n    fn post_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(\n        &mut self,\n        f: &mut F,\n    ) -> LoggedResult<WalkResult> {\n        use WalkResult::*;\n        loop {\n            match self.read()? {\n                None => return Ok(Continue),\n                Some(ref e) => {\n                    if e.is_dir() {\n                        let mut dir = e.open_as_dir()?;\n                        if let Abort = dir.post_order_walk_impl(f)? {\n                            return Ok(Abort);\n                        }\n                    }\n                    match f(e)? {\n                        Abort => return Ok(Abort),\n                        Skip => return Ok(Continue),\n                        Continue => {}\n                    }\n                }\n            }\n        }\n    }\n\n    fn pre_order_walk_impl<F: FnMut(&DirEntry) -> LoggedResult<WalkResult>>(\n        &mut self,\n        f: &mut F,\n    ) -> LoggedResult<WalkResult> {\n        use WalkResult::*;\n        loop {\n            match self.read()? {\n                None => return Ok(Continue),\n                Some(ref e) => match f(e)? {\n                    Abort => return Ok(Abort),\n                    Skip => continue,\n                    Continue => {\n                        if e.is_dir() {\n                            let mut dir = e.open_as_dir()?;\n                            if let Abort = dir.pre_order_walk_impl(f)? {\n                                return Ok(Abort);\n                            }\n                        }\n                    }\n                },\n            }\n        }\n    }\n\n    fn copy_into_impl(\n        &mut self,\n        dest_dir: &Directory,\n        buf: &mut dyn Utf8CStrBuf,\n    ) -> LoggedResult<()> {\n        while let Some(ref e) = self.read()? {\n            e.resolve_path(buf)?;\n            let attr = buf.get_attr()?;\n            if e.is_dir() {\n                dest_dir.mkdir_at(e.name(), 0o777)?;\n                let mut src = e.open_as_dir()?;\n                let dest = dest_dir.open_as_dir_at(e.name())?;\n                src.copy_into_impl(&dest, buf)?;\n                fd_set_attr(dest.as_raw_fd(), &attr)?;\n            } else if e.is_file() {\n                let mut src = e.open_as_file(OFlag::O_RDONLY)?;\n                let mut dest = dest_dir.open_as_file_at(\n                    e.name(),\n                    OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC,\n                    0o777,\n                )?;\n                std::io::copy(&mut src, &mut dest)?;\n                fd_set_attr(dest.as_raw_fd(), &attr)?;\n            } else if e.is_symlink() {\n                e.read_link(buf)?;\n                dest_dir.create_symlink_at(e.name(), buf)?;\n                dest_dir.path_at(e.name(), buf)?;\n                buf.set_attr(&attr)?;\n            }\n        }\n        Ok(())\n    }\n\n    fn link_into_impl(\n        &mut self,\n        dest_dir: &Directory,\n        buf: &mut dyn Utf8CStrBuf,\n    ) -> LoggedResult<()> {\n        while let Some(ref e) = self.read()? {\n            if e.is_dir() {\n                dest_dir.mkdir_at(e.name(), 0o777)?;\n                e.resolve_path(buf)?;\n                let attr = buf.get_attr()?;\n                let mut src = e.open_as_dir()?;\n                let dest = dest_dir.open_as_dir_at(e.name())?;\n                src.link_into_impl(&dest, buf)?;\n                fd_set_attr(dest.as_raw_fd(), &attr)?;\n            } else {\n                nix::unistd::linkat(e.dir, e.name(), dest_dir, e.name(), AtFlags::empty())\n                    .check_os_err(\"linkat\", Some(e.name()), None)?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl TryFrom<OwnedFd> for Directory {\n    type Error = OsError<'static>;\n\n    fn try_from(fd: OwnedFd) -> OsResult<'static, Self> {\n        let dirp = unsafe { libc::fdopendir(fd.into_raw_fd()) };\n        let dirp = dirp.into_os_result(\"fdopendir\", None, None)?;\n        Ok(Directory { inner: dirp })\n    }\n}\n\nimpl AsRawFd for Directory {\n    fn as_raw_fd(&self) -> RawFd {\n        unsafe { libc::dirfd(self.inner.as_ptr()) }\n    }\n}\n\nimpl AsFd for Directory {\n    fn as_fd(&self) -> BorrowedFd<'_> {\n        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }\n    }\n}\n\nimpl Drop for Directory {\n    fn drop(&mut self) {\n        unsafe {\n            libc::closedir(self.inner.as_ptr());\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/base/files.rs",
    "content": "use crate::{\n    Directory, FsPathFollow, LibcReturn, LoggedResult, OsError, OsResult, Utf8CStr, Utf8CStrBuf,\n    cstr, errno, error,\n};\nuse bytemuck::{Pod, bytes_of, bytes_of_mut};\nuse libc::{c_uint, makedev, mode_t};\nuse nix::errno::Errno;\nuse nix::fcntl::{AT_FDCWD, OFlag};\nuse nix::sys::stat::{FchmodatFlags, Mode};\nuse nix::unistd::{AccessFlags, Gid, Uid};\nuse num_traits::AsPrimitive;\nuse std::cmp::min;\nuse std::ffi::CStr;\nuse std::fmt::Display;\nuse std::fs::File;\nuse std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};\nuse std::mem::MaybeUninit;\nuse std::os::fd::{AsFd, BorrowedFd};\nuse std::os::unix::ffi::OsStrExt;\nuse std::os::unix::io::{AsRawFd, OwnedFd, RawFd};\nuse std::path::Path;\nuse std::{io, mem, ptr, slice};\n\npub trait ReadExt {\n    fn skip(&mut self, len: usize) -> io::Result<()>;\n    fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()>;\n}\n\nimpl<T: Read> ReadExt for T {\n    fn skip(&mut self, mut len: usize) -> io::Result<()> {\n        let mut buf = MaybeUninit::<[u8; 4096]>::uninit();\n        let buf = unsafe { buf.assume_init_mut() };\n        while len > 0 {\n            let l = min(buf.len(), len);\n            self.read_exact(&mut buf[..l])?;\n            len -= l;\n        }\n        Ok(())\n    }\n\n    fn read_pod<F: Pod>(&mut self, data: &mut F) -> io::Result<()> {\n        self.read_exact(bytes_of_mut(data))\n    }\n}\n\npub trait ReadSeekExt {\n    fn skip(&mut self, len: usize) -> io::Result<()>;\n}\n\nimpl<T: Read + Seek> ReadSeekExt for T {\n    fn skip(&mut self, len: usize) -> io::Result<()> {\n        if self.seek(SeekFrom::Current(len as i64)).is_err() {\n            // If the file is not actually seekable, fallback to read\n            ReadExt::skip(self, len)?;\n        }\n        Ok(())\n    }\n}\n\npub trait BufReadExt {\n    fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, f: F);\n    fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, f: F);\n}\n\nimpl<T: BufRead> BufReadExt for T {\n    fn for_each_line<F: FnMut(&mut String) -> bool>(&mut self, mut f: F) {\n        let mut buf = String::new();\n        loop {\n            match self.read_line(&mut buf) {\n                Ok(0) => break,\n                Ok(_) => {\n                    if !f(&mut buf) {\n                        break;\n                    }\n                }\n                Err(e) => {\n                    error!(\"{}\", e);\n                    break;\n                }\n            };\n            buf.clear();\n        }\n    }\n\n    fn for_each_prop<F: FnMut(&str, &str) -> bool>(&mut self, mut f: F) {\n        self.for_each_line(|line| {\n            // Reserve an additional byte, because this string will be manually\n            // null terminated on the C++ side, and it may need more space.\n            line.reserve(1);\n            let line = line.trim();\n            if line.starts_with('#') {\n                return true;\n            }\n            if let Some((key, value)) = line.split_once('=') {\n                return f(key.trim(), value.trim());\n            }\n            true\n        });\n    }\n}\n\npub trait WriteExt {\n    fn write_zeros(&mut self, len: usize) -> io::Result<()>;\n    fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()>;\n}\n\nimpl<T: Write> WriteExt for T {\n    fn write_zeros(&mut self, mut len: usize) -> io::Result<()> {\n        let buf = [0_u8; 4096];\n        while len > 0 {\n            let l = min(buf.len(), len);\n            self.write_all(&buf[..l])?;\n            len -= l;\n        }\n        Ok(())\n    }\n\n    fn write_pod<F: Pod>(&mut self, data: &F) -> io::Result<()> {\n        self.write_all(bytes_of(data))\n    }\n}\n\npub enum FileOrStd {\n    StdIn,\n    StdOut,\n    StdErr,\n    File(File),\n}\n\nimpl FileOrStd {\n    pub fn as_file(&self) -> &File {\n        let raw_fd_ref: &'static RawFd = match self {\n            FileOrStd::StdIn => &0,\n            FileOrStd::StdOut => &1,\n            FileOrStd::StdErr => &2,\n            FileOrStd::File(file) => return file,\n        };\n        // SAFETY: File is guaranteed to have the same ABI as RawFd\n        unsafe { mem::transmute(raw_fd_ref) }\n    }\n}\n\nfn open_fd(path: &Utf8CStr, flags: OFlag, mode: mode_t) -> OsResult<'_, OwnedFd> {\n    nix::fcntl::open(path, flags, Mode::from_bits_truncate(mode)).into_os_result(\n        \"open\",\n        Some(path),\n        None,\n    )\n}\n\npub fn fd_path(fd: RawFd, buf: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {\n    let path = cstr::buf::new::<64>()\n        .join_path(\"/proc/self/fd\")\n        .join_path_fmt(fd);\n    path.read_link(buf).map_err(|e| e.set_args(None, None))\n}\n\npub struct FileAttr {\n    pub st: libc::stat,\n    #[cfg(feature = \"selinux\")]\n    pub con: crate::Utf8CStrBufArr<128>,\n}\n\nimpl Default for FileAttr {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl FileAttr {\n    pub fn new() -> Self {\n        FileAttr {\n            st: unsafe { mem::zeroed() },\n            #[cfg(feature = \"selinux\")]\n            con: crate::Utf8CStrBufArr::new(),\n        }\n    }\n\n    #[inline(always)]\n    #[allow(clippy::unnecessary_cast)]\n    fn is(&self, mode: mode_t) -> bool {\n        (self.st.st_mode & libc::S_IFMT as c_uint) as mode_t == mode\n    }\n\n    pub fn is_dir(&self) -> bool {\n        self.is(libc::S_IFDIR)\n    }\n\n    pub fn is_file(&self) -> bool {\n        self.is(libc::S_IFREG)\n    }\n\n    pub fn is_symlink(&self) -> bool {\n        self.is(libc::S_IFLNK)\n    }\n\n    pub fn is_block_device(&self) -> bool {\n        self.is(libc::S_IFBLK)\n    }\n\n    pub fn is_char_device(&self) -> bool {\n        self.is(libc::S_IFCHR)\n    }\n\n    pub fn is_fifo(&self) -> bool {\n        self.is(libc::S_IFIFO)\n    }\n\n    pub fn is_socket(&self) -> bool {\n        self.is(libc::S_IFSOCK)\n    }\n\n    pub fn is_whiteout(&self) -> bool {\n        self.is_char_device() && self.st.st_rdev == 0\n    }\n}\n\nconst XATTR_NAME_SELINUX: &CStr = c\"security.selinux\";\n\n// Low-level methods, we should track the caller when error occurs, so return OsResult.\nimpl Utf8CStr {\n    pub fn follow_link(&self) -> &FsPathFollow {\n        unsafe { mem::transmute(self) }\n    }\n\n    pub fn open(&self, flags: OFlag) -> OsResult<'_, File> {\n        Ok(File::from(open_fd(self, flags, 0)?))\n    }\n\n    pub fn create(&self, flags: OFlag, mode: mode_t) -> OsResult<'_, File> {\n        Ok(File::from(open_fd(self, OFlag::O_CREAT | flags, mode)?))\n    }\n\n    pub fn exists(&self) -> bool {\n        nix::sys::stat::lstat(self).is_ok()\n    }\n\n    pub fn rename_to<'a>(&'a self, name: &'a Utf8CStr) -> OsResult<'a, ()> {\n        nix::fcntl::renameat(AT_FDCWD, self, AT_FDCWD, name).check_os_err(\n            \"rename\",\n            Some(self),\n            Some(name),\n        )\n    }\n\n    pub fn remove(&self) -> OsResult<'_, ()> {\n        unsafe { libc::remove(self.as_ptr()).check_os_err(\"remove\", Some(self), None) }\n    }\n\n    #[allow(clippy::unnecessary_cast)]\n    pub fn read_link(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {\n        buf.clear();\n        unsafe {\n            let r = libc::readlink(self.as_ptr(), buf.as_mut_ptr(), buf.capacity() - 1)\n                .into_os_result(\"readlink\", Some(self), None)? as isize;\n            *(buf.as_mut_ptr().offset(r) as *mut u8) = b'\\0';\n        }\n        buf.rebuild().ok();\n        Ok(())\n    }\n\n    pub fn mkdir(&self, mode: mode_t) -> OsResult<'_, ()> {\n        match nix::unistd::mkdir(self, Mode::from_bits_truncate(mode)) {\n            Ok(_) | Err(Errno::EEXIST) => Ok(()),\n            Err(e) => Err(OsError::new(e, \"mkdir\", Some(self), None)),\n        }\n    }\n\n    // Inspired by https://android.googlesource.com/platform/bionic/+/master/libc/bionic/realpath.cpp\n    pub fn realpath(&self, buf: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {\n        let fd = self.open(OFlag::O_PATH | OFlag::O_CLOEXEC)?;\n        let mut skip_check = false;\n\n        let st1 = match nix::sys::stat::fstat(&fd) {\n            Ok(st) => st,\n            Err(_) => {\n                // This will only fail on Linux < 3.6\n                skip_check = true;\n                unsafe { mem::zeroed() }\n            }\n        };\n\n        fd_path(fd.as_raw_fd(), buf)?;\n\n        let st2 = nix::sys::stat::stat(buf.as_cstr()).into_os_result(\"stat\", Some(self), None)?;\n        if !skip_check && (st2.st_dev != st1.st_dev || st2.st_ino != st1.st_ino) {\n            return Err(OsError::new(Errno::ENOENT, \"realpath\", Some(self), None));\n        }\n        Ok(())\n    }\n\n    pub fn get_attr(&self) -> OsResult<'_, FileAttr> {\n        #[allow(unused_mut)]\n        let mut attr = FileAttr {\n            st: nix::sys::stat::lstat(self).into_os_result(\"lstat\", Some(self), None)?,\n            #[cfg(feature = \"selinux\")]\n            con: cstr::buf::new(),\n        };\n        #[cfg(feature = \"selinux\")]\n        self.get_secontext(&mut attr.con)?;\n        Ok(attr)\n    }\n\n    pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {\n        if !attr.is_symlink()\n            && let Err(e) = self.follow_link().chmod((attr.st.st_mode & 0o777).as_())\n        {\n            // Double check if self is symlink before reporting error\n            let self_attr = self.get_attr()?;\n            if !self_attr.is_symlink() {\n                return Err(e);\n            }\n        }\n\n        unsafe {\n            libc::lchown(self.as_ptr(), attr.st.st_uid, attr.st.st_gid).check_os_err(\n                \"lchown\",\n                Some(self),\n                None,\n            )?;\n        }\n\n        #[cfg(feature = \"selinux\")]\n        if !attr.con.is_empty() {\n            self.set_secontext(&attr.con)?;\n        }\n        Ok(())\n    }\n\n    pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {\n        con.clear();\n        let result = unsafe {\n            libc::lgetxattr(\n                self.as_ptr(),\n                XATTR_NAME_SELINUX.as_ptr(),\n                con.as_mut_ptr().cast(),\n                con.capacity(),\n            )\n            .check_err()\n        };\n\n        match result {\n            Ok(_) => {\n                con.rebuild().ok();\n                Ok(())\n            }\n            Err(Errno::ENODATA) => Ok(()),\n            Err(e) => Err(OsError::new(e, \"lgetxattr\", Some(self), None)),\n        }\n    }\n\n    pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {\n        unsafe {\n            libc::lsetxattr(\n                self.as_ptr(),\n                XATTR_NAME_SELINUX.as_ptr(),\n                con.as_ptr().cast(),\n                con.len() + 1,\n                0,\n            )\n            .check_os_err(\"lsetxattr\", Some(self), Some(con))\n        }\n    }\n\n    pub fn parent_dir(&self) -> Option<&str> {\n        Path::new(self.as_str())\n            .parent()\n            .map(Path::as_os_str)\n            // SAFETY: all substring of self is valid UTF-8\n            .map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })\n    }\n\n    pub fn file_name(&self) -> Option<&str> {\n        Path::new(self.as_str())\n            .file_name()\n            // SAFETY: all substring of self is valid UTF-8\n            .map(|s| unsafe { std::str::from_utf8_unchecked(s.as_bytes()) })\n    }\n\n    // ln -s target self\n    pub fn create_symlink_to<'a>(&'a self, target: &'a Utf8CStr) -> OsResult<'a, ()> {\n        nix::unistd::symlinkat(target, AT_FDCWD, self).check_os_err(\n            \"symlink\",\n            Some(target),\n            Some(self),\n        )\n    }\n\n    pub fn mkfifo(&self, mode: mode_t) -> OsResult<'_, ()> {\n        nix::unistd::mkfifo(self, Mode::from_bits_truncate(mode)).check_os_err(\n            \"mkfifo\",\n            Some(self),\n            None,\n        )\n    }\n}\n\n// High-level helper methods, composed of multiple operations.\n// We should treat these as application logic and log ASAP, so return LoggedResult.\nimpl Utf8CStr {\n    pub fn remove_all(&self) -> LoggedResult<()> {\n        let attr = match self.get_attr() {\n            Ok(attr) => attr,\n            Err(e) => {\n                return match e.errno {\n                    // Allow calling remove_all on non-existence file\n                    Errno::ENOENT => Ok(()),\n                    _ => Err(e)?,\n                };\n            }\n        };\n        if attr.is_dir() {\n            let dir = Directory::open(self)?;\n            dir.remove_all()?;\n        }\n        Ok(self.remove()?)\n    }\n\n    pub fn mkdirs(&self, mode: mode_t) -> LoggedResult<()> {\n        if self.is_empty() {\n            return Ok(());\n        }\n\n        let mut path = cstr::buf::default();\n        let mut components = self.split('/').filter(|s| !s.is_empty());\n\n        if self.starts_with('/') {\n            path.append_path(\"/\");\n        }\n\n        loop {\n            let Some(s) = components.next() else {\n                break;\n            };\n            path.append_path(s);\n            path.mkdir(mode)?;\n        }\n\n        *errno() = 0;\n        Ok(())\n    }\n\n    pub fn copy_to(&self, path: &Utf8CStr) -> LoggedResult<()> {\n        let attr = self.get_attr()?;\n        if attr.is_dir() {\n            path.mkdir(0o777)?;\n            let mut src = Directory::open(self)?;\n            let dest = Directory::open(path)?;\n            src.copy_into(&dest)?;\n        } else {\n            // It's OK if remove failed\n            path.remove().ok();\n            if attr.is_file() {\n                let mut src = self.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;\n                let mut dest = path.create(\n                    OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,\n                    0o777,\n                )?;\n                std::io::copy(&mut src, &mut dest)?;\n            } else if attr.is_symlink() {\n                let mut buf = cstr::buf::default();\n                self.read_link(&mut buf)?;\n                unsafe {\n                    libc::symlink(buf.as_ptr(), path.as_ptr()).check_os_err(\n                        \"symlink\",\n                        Some(&buf),\n                        Some(path),\n                    )?;\n                }\n            }\n        }\n        path.set_attr(&attr)?;\n        Ok(())\n    }\n\n    pub fn move_to(&self, path: &Utf8CStr) -> LoggedResult<()> {\n        if path.exists() {\n            let attr = path.get_attr()?;\n            if attr.is_dir() {\n                let mut src = Directory::open(self)?;\n                let dest = Directory::open(path)?;\n                return src.move_into(&dest);\n            } else {\n                path.remove()?;\n            }\n        }\n        self.rename_to(path)?;\n        Ok(())\n    }\n\n    // ln self path\n    pub fn link_to(&self, path: &Utf8CStr) -> LoggedResult<()> {\n        let attr = self.get_attr()?;\n        if attr.is_dir() {\n            path.mkdir(0o777)?;\n            path.set_attr(&attr)?;\n            let mut src = Directory::open(self)?;\n            let dest = Directory::open(path)?;\n            Ok(src.link_into(&dest)?)\n        } else {\n            unsafe {\n                libc::link(self.as_ptr(), path.as_ptr()).check_os_err(\n                    \"link\",\n                    Some(self),\n                    Some(path),\n                )?;\n            }\n            Ok(())\n        }\n    }\n}\n\nimpl FsPathFollow {\n    pub fn exists(&self) -> bool {\n        nix::unistd::access(self.as_utf8_cstr(), AccessFlags::F_OK).is_ok()\n    }\n\n    pub fn chmod(&self, mode: mode_t) -> OsResult<'_, ()> {\n        nix::sys::stat::fchmodat(\n            AT_FDCWD,\n            self.as_utf8_cstr(),\n            Mode::from_bits_truncate(mode),\n            FchmodatFlags::FollowSymlink,\n        )\n        .check_os_err(\"chmod\", Some(self), None)\n    }\n\n    pub fn get_attr(&self) -> OsResult<'_, FileAttr> {\n        #[allow(unused_mut)]\n        let mut attr = FileAttr {\n            st: nix::sys::stat::stat(self.as_utf8_cstr()).into_os_result(\n                \"lstat\",\n                Some(self),\n                None,\n            )?,\n            #[cfg(feature = \"selinux\")]\n            con: cstr::buf::new(),\n        };\n        #[cfg(feature = \"selinux\")]\n        self.get_secontext(&mut attr.con)?;\n        Ok(attr)\n    }\n\n    pub fn set_attr<'a>(&'a self, attr: &'a FileAttr) -> OsResult<'a, ()> {\n        self.chmod((attr.st.st_mode & 0o777).as_())?;\n\n        nix::unistd::chown(\n            self.as_utf8_cstr(),\n            Some(Uid::from(attr.st.st_uid)),\n            Some(Gid::from(attr.st.st_gid)),\n        )\n        .check_os_err(\"chown\", Some(self), None)?;\n\n        #[cfg(feature = \"selinux\")]\n        if !attr.con.is_empty() {\n            self.set_secontext(&attr.con)?;\n        }\n        Ok(())\n    }\n\n    pub fn get_secontext(&self, con: &mut dyn Utf8CStrBuf) -> OsResult<'_, ()> {\n        con.clear();\n        let result = unsafe {\n            libc::getxattr(\n                self.as_ptr(),\n                XATTR_NAME_SELINUX.as_ptr(),\n                con.as_mut_ptr().cast(),\n                con.capacity(),\n            )\n            .check_err()\n        };\n\n        match result {\n            Ok(_) => {\n                con.rebuild().ok();\n                Ok(())\n            }\n            Err(Errno::ENODATA) => Ok(()),\n            Err(e) => Err(OsError::new(e, \"getxattr\", Some(self), None)),\n        }\n    }\n\n    pub fn set_secontext<'a>(&'a self, con: &'a Utf8CStr) -> OsResult<'a, ()> {\n        unsafe {\n            libc::setxattr(\n                self.as_ptr(),\n                XATTR_NAME_SELINUX.as_ptr(),\n                con.as_ptr().cast(),\n                con.len() + 1,\n                0,\n            )\n            .check_os_err(\"setxattr\", Some(self), Some(con))\n        }\n    }\n}\n\npub trait FsPathBuilder {\n    fn join_path<T: AsRef<str>>(mut self, path: T) -> Self\n    where\n        Self: Sized,\n    {\n        self.append_path(path);\n        self\n    }\n    fn join_path_fmt<T: Display>(mut self, name: T) -> Self\n    where\n        Self: Sized,\n    {\n        self.append_path_fmt(name);\n        self\n    }\n    fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self;\n    fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self;\n}\n\nfn append_path_impl(buf: &mut dyn Utf8CStrBuf, path: &str) {\n    if path.starts_with('/') {\n        buf.clear();\n    }\n    if !buf.is_empty() && !buf.ends_with('/') {\n        buf.push_str(\"/\");\n    }\n    buf.push_str(path);\n}\n\nimpl<S: Utf8CStrBuf + Sized> FsPathBuilder for S {\n    fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {\n        append_path_impl(self, path.as_ref());\n        self\n    }\n\n    fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {\n        self.write_fmt(format_args!(\"/{name}\")).ok();\n        self\n    }\n}\n\nimpl FsPathBuilder for dyn Utf8CStrBuf + '_ {\n    fn append_path<T: AsRef<str>>(&mut self, path: T) -> &mut Self {\n        append_path_impl(self, path.as_ref());\n        self\n    }\n\n    fn append_path_fmt<T: Display>(&mut self, name: T) -> &mut Self {\n        self.write_fmt(format_args!(\"/{name}\")).ok();\n        self\n    }\n}\n\npub fn fd_get_attr(fd: RawFd) -> OsResult<'static, FileAttr> {\n    let mut attr = FileAttr::new();\n    unsafe {\n        libc::fstat(fd, &mut attr.st).check_os_err(\"fstat\", None, None)?;\n\n        #[cfg(feature = \"selinux\")]\n        fd_get_secontext(fd, &mut attr.con)?;\n    }\n    Ok(attr)\n}\n\npub fn fd_set_attr(fd: RawFd, attr: &FileAttr) -> OsResult<'_, ()> {\n    unsafe {\n        libc::fchmod(fd, (attr.st.st_mode & 0o777).as_()).check_os_err(\"fchmod\", None, None)?;\n        libc::fchown(fd, attr.st.st_uid, attr.st.st_gid).check_os_err(\"fchown\", None, None)?;\n\n        #[cfg(feature = \"selinux\")]\n        if !attr.con.is_empty() {\n            fd_set_secontext(fd, &attr.con)?;\n        }\n    }\n    Ok(())\n}\n\npub fn fd_get_secontext(fd: RawFd, con: &mut dyn Utf8CStrBuf) -> OsResult<'static, ()> {\n    con.clear();\n    let result = unsafe {\n        libc::fgetxattr(\n            fd,\n            XATTR_NAME_SELINUX.as_ptr(),\n            con.as_mut_ptr().cast(),\n            con.capacity(),\n        )\n        .check_err()\n    };\n\n    match result {\n        Ok(_) => {\n            con.rebuild().ok();\n            Ok(())\n        }\n        Err(Errno::ENODATA) => Ok(()),\n        Err(e) => Err(OsError::new(e, \"fgetxattr\", None, None)),\n    }\n}\n\npub fn fd_set_secontext(fd: RawFd, con: &Utf8CStr) -> OsResult<'_, ()> {\n    unsafe {\n        libc::fsetxattr(\n            fd,\n            XATTR_NAME_SELINUX.as_ptr(),\n            con.as_ptr().cast(),\n            con.len() + 1,\n            0,\n        )\n        .check_os_err(\"fsetxattr\", Some(con), None)\n    }\n}\n\npub fn clone_attr<'a>(a: &'a Utf8CStr, b: &'a Utf8CStr) -> OsResult<'a, ()> {\n    let attr = a.get_attr().map_err(|e| e.set_args(Some(a), None))?;\n    b.set_attr(&attr).map_err(|e| e.set_args(Some(b), None))\n}\n\npub fn fclone_attr(a: RawFd, b: RawFd) -> OsResult<'static, ()> {\n    let attr = fd_get_attr(a)?;\n    fd_set_attr(b, &attr).map_err(|e| e.set_args(None, None))\n}\n\npub struct MappedFile(&'static mut [u8]);\n\nimpl MappedFile {\n    pub fn open(path: &Utf8CStr) -> OsResult<'_, MappedFile> {\n        Ok(MappedFile(map_file(path, false)?))\n    }\n\n    pub fn open_rw(path: &Utf8CStr) -> OsResult<'_, MappedFile> {\n        Ok(MappedFile(map_file(path, true)?))\n    }\n\n    pub fn openat<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {\n        Ok(MappedFile(map_file_at(dir.as_fd(), path, false)?))\n    }\n\n    pub fn openat_rw<'a, T: AsFd>(dir: &T, path: &'a Utf8CStr) -> OsResult<'a, MappedFile> {\n        Ok(MappedFile(map_file_at(dir.as_fd(), path, true)?))\n    }\n\n    pub fn create(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<MappedFile> {\n        Ok(MappedFile(map_fd(fd, sz, rw)?))\n    }\n}\n\nimpl AsRef<[u8]> for MappedFile {\n    fn as_ref(&self) -> &[u8] {\n        self.0\n    }\n}\n\nimpl AsMut<[u8]> for MappedFile {\n    fn as_mut(&mut self) -> &mut [u8] {\n        self.0\n    }\n}\n\nimpl Drop for MappedFile {\n    fn drop(&mut self) {\n        unsafe {\n            libc::munmap(self.0.as_mut_ptr().cast(), self.0.len());\n        }\n    }\n}\n\nunsafe extern \"C\" {\n    // Don't use the declaration from the libc crate as request should be u32 not i32\n    fn ioctl(fd: RawFd, request: u32, ...) -> i32;\n}\n\n// We mark the returned slice static because it is valid until explicitly unmapped\npub(crate) fn map_file(path: &Utf8CStr, rw: bool) -> OsResult<'_, &'static mut [u8]> {\n    map_file_at(AT_FDCWD, path, rw)\n}\n\npub(crate) fn map_file_at<'a>(\n    dirfd: BorrowedFd,\n    path: &'a Utf8CStr,\n    rw: bool,\n) -> OsResult<'a, &'static mut [u8]> {\n    #[cfg(target_pointer_width = \"64\")]\n    const BLKGETSIZE64: u32 = 0x80081272;\n\n    #[cfg(target_pointer_width = \"32\")]\n    const BLKGETSIZE64: u32 = 0x80041272;\n\n    let flag = if rw { OFlag::O_RDWR } else { OFlag::O_RDONLY };\n    let fd = nix::fcntl::openat(dirfd, path, flag | OFlag::O_CLOEXEC, Mode::empty())\n        .into_os_result(\"openat\", Some(path), None)?;\n    let attr = fd_get_attr(fd.as_raw_fd())?;\n    let sz = if attr.is_block_device() {\n        let mut sz = 0_u64;\n        unsafe {\n            ioctl(fd.as_raw_fd(), BLKGETSIZE64, &mut sz).check_os_err(\"ioctl\", Some(path), None)?;\n        }\n        sz\n    } else {\n        attr.st.st_size as u64\n    };\n\n    map_fd(fd.as_fd(), sz as usize, rw).map_err(|e| e.set_args(Some(path), None))\n}\n\npub(crate) fn map_fd(fd: BorrowedFd, sz: usize, rw: bool) -> OsResult<'static, &'static mut [u8]> {\n    let flag = if rw {\n        libc::MAP_SHARED\n    } else {\n        libc::MAP_PRIVATE\n    };\n    unsafe {\n        let ptr = libc::mmap(\n            ptr::null_mut(),\n            sz,\n            libc::PROT_READ | libc::PROT_WRITE,\n            flag,\n            fd.as_raw_fd(),\n            0,\n        );\n        if ptr == libc::MAP_FAILED {\n            return Err(OsError::last_os_error(\"mmap\", None, None));\n        }\n        Ok(slice::from_raw_parts_mut(ptr.cast(), sz))\n    }\n}\n\n#[allow(dead_code)]\npub struct MountInfo {\n    pub id: u32,\n    pub parent: u32,\n    pub device: u64,\n    pub root: String,\n    pub target: String,\n    pub vfs_option: String,\n    pub shared: u32,\n    pub master: u32,\n    pub propagation_from: u32,\n    pub unbindable: bool,\n    pub fs_type: String,\n    pub source: String,\n    pub fs_option: String,\n}\n\n#[allow(clippy::useless_conversion)]\nfn parse_mount_info_line(line: &str) -> Option<MountInfo> {\n    let mut iter = line.split_whitespace();\n    let id = iter.next()?.parse().ok()?;\n    let parent = iter.next()?.parse().ok()?;\n    let (maj, min) = iter.next()?.split_once(':')?;\n    let maj = maj.parse().ok()?;\n    let min = min.parse().ok()?;\n    let device = makedev(maj, min).into();\n    let root = iter.next()?.to_string();\n    let target = iter.next()?.to_string();\n    let vfs_option = iter.next()?.to_string();\n    let mut optional = iter.next()?;\n    let mut shared = 0;\n    let mut master = 0;\n    let mut propagation_from = 0;\n    let mut unbindable = false;\n    while optional != \"-\" {\n        if let Some(peer) = optional.strip_prefix(\"master:\") {\n            master = peer.parse().ok()?;\n        } else if let Some(peer) = optional.strip_prefix(\"shared:\") {\n            shared = peer.parse().ok()?;\n        } else if let Some(peer) = optional.strip_prefix(\"propagate_from:\") {\n            propagation_from = peer.parse().ok()?;\n        } else if optional == \"unbindable\" {\n            unbindable = true;\n        }\n        optional = iter.next()?;\n    }\n    let fs_type = iter.next()?.to_string();\n    let source = iter.next()?.to_string();\n    let fs_option = iter.next()?.to_string();\n    Some(MountInfo {\n        id,\n        parent,\n        device,\n        root,\n        target,\n        vfs_option,\n        shared,\n        master,\n        propagation_from,\n        unbindable,\n        fs_type,\n        source,\n        fs_option,\n    })\n}\n\npub fn parse_mount_info(pid: &str) -> Vec<MountInfo> {\n    let mut res = vec![];\n    let mut path = format!(\"/proc/{pid}/mountinfo\");\n    if let Ok(file) = Utf8CStr::from_string(&mut path).open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n        BufReader::new(file).for_each_line(|line| {\n            parse_mount_info_line(line)\n                .map(|info| res.push(info))\n                .is_some()\n        });\n    }\n    res\n}\n"
  },
  {
    "path": "native/src/base/include/base.hpp",
    "content": "#pragma once\n\n#include <sys/stat.h>\n#include <unistd.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <functional>\n\n#include <rust/cxx.h>\n\nvoid LOGD(const char *fmt, ...) __printflike(1, 2);\nvoid LOGI(const char *fmt, ...) __printflike(1, 2);\nvoid LOGW(const char *fmt, ...) __printflike(1, 2);\nvoid LOGE(const char *fmt, ...) __printflike(1, 2);\n#define PLOGE(fmt, args...) LOGE(fmt \" failed with %d: %s\\n\", ##args, errno, std::strerror(errno))\n\nextern \"C\" {\n\n// xwraps\n\nFILE *xfopen(const char *pathname, const char *mode);\nFILE *xfdopen(int fd, const char *mode);\nint xopen(const char *pathname, int flags, mode_t mode = 0);\nint xopenat(int dirfd, const char *pathname, int flags, mode_t mode = 0);\nssize_t xwrite(int fd, const void *buf, size_t count);\nssize_t xread(int fd, void *buf, size_t count);\nssize_t xxread(int fd, void *buf, size_t count);\nint xsetns(int fd, int nstype);\nint xunshare(int flags);\nDIR *xopendir(const char *name);\nDIR *xfdopendir(int fd);\ndirent *xreaddir(DIR *dirp);\npid_t xsetsid();\nint xfstat(int fd, struct stat *buf);\nint xdup2(int oldfd, int newfd);\nssize_t xreadlinkat(\n        int dirfd, const char * __restrict__ pathname, char * __restrict__ buf, size_t bufsiz);\nint xsymlink(const char *target, const char *linkpath);\nint xmount(const char *source, const char *target,\n           const char *filesystemtype, unsigned long mountflags,\n           const void *data);\nint xumount2(const char *target, int flags);\nint xrename(const char *oldpath, const char *newpath);\nint xmkdir(const char *pathname, mode_t mode);\nint xmkdirs(const char *pathname, mode_t mode);\nssize_t xsendfile(int out_fd, int in_fd, off_t *offset, size_t count);\npid_t xfork();\nssize_t xrealpath(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz);\nint xmknod(const char * pathname, mode_t mode, dev_t dev);\n\n// Utils\n\nint mkdirs(const char *path, mode_t mode);\nssize_t canonical_path(const char * __restrict__ path, char * __restrict__ buf, size_t bufsiz);\nbool rm_rf(const char *path);\nbool cp_afc(const char *src, const char *dest);\nbool mv_path(const char *src, const char *dest);\nbool link_path(const char *src, const char *dest);\nbool clone_attr(const char *src, const char *dest);\nbool fclone_attr(int src, int dest);\n\n} // extern \"C\"\n\n#define DISALLOW_COPY_AND_MOVE(clazz) \\\nclazz(const clazz&) = delete;        \\\nclazz(clazz &&) = delete;\n\n#define ALLOW_MOVE_ONLY(clazz) \\\nclazz(const clazz&) = delete;  \\\nclazz(clazz &&o) : clazz() { swap(o); }  \\\nclazz& operator=(clazz &&o) { swap(o); return *this; }\n\nstruct Utf8CStr;\n\nclass mutex_guard {\n    DISALLOW_COPY_AND_MOVE(mutex_guard)\npublic:\n    explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {\n        pthread_mutex_lock(mutex);\n    }\n    void unlock() {\n        pthread_mutex_unlock(mutex);\n        mutex = nullptr;\n    }\n    ~mutex_guard() {\n        if (mutex) pthread_mutex_unlock(mutex);\n    }\nprivate:\n    pthread_mutex_t *mutex;\n};\n\ntemplate <class Func>\nclass run_finally {\n    DISALLOW_COPY_AND_MOVE(run_finally)\npublic:\n    explicit run_finally(Func &&fn) : fn(std::move(fn)) {}\n    ~run_finally() { fn(); }\nprivate:\n    Func fn;\n};\n\ntemplate<class T>\nstatic void default_new(T *&p) { p = new T(); }\n\ntemplate<class T>\nstatic void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }\n\nstruct StringCmp {\n    using is_transparent = void;\n    bool operator()(std::string_view a, std::string_view b) const { return a < b; }\n};\n\nusing ByteSlice = rust::Slice<const uint8_t>;\nusing MutByteSlice = rust::Slice<uint8_t>;\n\n// Interchangeable as `&[u8]` in Rust\nstruct byte_view {\n    byte_view() : ptr(nullptr), sz(0) {}\n    byte_view(const void *buf, size_t sz) : ptr((uint8_t *) buf), sz(sz) {}\n\n    // byte_view, or any of its subclasses, can be copied as byte_view\n    byte_view(const byte_view &o) : ptr(o.ptr), sz(o.sz) {}\n\n    // Transparent conversion to Rust slice\n    byte_view(const ByteSlice o) : byte_view(o.data(), o.size()) {}\n    operator ByteSlice() const { return {ptr, sz}; }\n\n    // String as bytes, including null terminator\n    byte_view(const char *s) : byte_view(s, strlen(s) + 1) {}\n\n    const uint8_t *data() const { return ptr; }\n    size_t size() const { return sz; }\n\nprotected:\n    uint8_t *ptr;\n    size_t sz;\n};\n\n// Interchangeable as `&mut [u8]` in Rust\nstruct byte_data : public byte_view {\n    byte_data() = default;\n    byte_data(void *buf, size_t sz) : byte_view(buf, sz) {}\n\n    // byte_data, or any of its subclasses, can be copied as byte_data\n    byte_data(const byte_data &o) : byte_data(o.ptr, o.sz) {}\n\n    // Transparent conversion to Rust slice\n    byte_data(const MutByteSlice o) : byte_data(o.data(), o.size()) {}\n    operator MutByteSlice() const { return {ptr, sz}; }\n\n    using byte_view::data;\n    uint8_t *data() const { return ptr; }\n\n    rust::Vec<size_t> patch(byte_view from, byte_view to) const;\n};\n\nstruct mmap_data : public byte_data {\n    ALLOW_MOVE_ONLY(mmap_data)\n\n    mmap_data() = default;\n    explicit mmap_data(const char *name, bool rw = false);\n    mmap_data(int dirfd, const char *name, bool rw = false);\n    mmap_data(int fd, size_t sz, bool rw = false);\n    ~mmap_data();\nprivate:\n    void swap(mmap_data &o);\n};\n\n\nstruct owned_fd {\n    ALLOW_MOVE_ONLY(owned_fd)\n\n    owned_fd() : fd(-1) {}\n    owned_fd(int fd) : fd(fd) {}\n    ~owned_fd() { close(fd); fd = -1; }\n\n    operator int() { return fd; }\n    int release() { int f = fd; fd = -1; return f; }\n    void swap(owned_fd &owned) { std::swap(fd, owned.fd); }\n\nprivate:\n    int fd;\n};\n\nrust::Vec<size_t> mut_u8_patch(MutByteSlice buf, ByteSlice from, ByteSlice to);\n\nuint32_t parse_uint32_hex(std::string_view s);\nint parse_int(std::string_view s);\n\nusing thread_entry = void *(*)(void *);\nextern \"C\" int new_daemon_thread(thread_entry entry, void *arg = nullptr);\n\nstatic inline std::string rtrim(std::string &&s) {\n    s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {\n        return !std::isspace(ch) && ch != '\\0';\n    }).base(), s.end());\n    return std::move(s);\n}\n\nint fork_dont_care();\nint fork_no_orphan();\nvoid init_argv0(int argc, char **argv);\nvoid set_nice_name(Utf8CStr name);\nint switch_mnt_ns(int pid);\nstd::string &replace_all(std::string &str, std::string_view from, std::string_view to);\nstd::vector<std::string> split(std::string_view s, std::string_view delims);\n\n// Similar to vsnprintf, but the return value is the written number of bytes\n__printflike(3, 0) int vssprintf(char *dest, size_t size, const char *fmt, va_list ap);\n// Similar to snprintf, but the return value is the written number of bytes\n__printflike(3, 4) int ssprintf(char *dest, size_t size, const char *fmt, ...);\n// This is not actually the strscpy from the Linux kernel.\n// Silently truncates, and returns the number of bytes written.\nextern \"C\" size_t strscpy(char *dest, const char *src, size_t size);\n\n// Ban usage of unsafe cstring functions\n#define vsnprintf  __use_vssprintf_instead__\n#define snprintf   __use_ssprintf_instead__\n#define strlcpy    __use_strscpy_instead__\n\nstruct exec_t {\n    bool err = false;\n    int fd = -2;\n    void (*pre_exec)() = nullptr;\n    int (*fork)() = xfork;\n    const char **argv = nullptr;\n};\n\nint exec_command(exec_t &exec);\ntemplate <class ...Args>\nint exec_command(exec_t &exec, Args &&...args) {\n    const char *argv[] = {args..., nullptr};\n    exec.argv = argv;\n    return exec_command(exec);\n}\nint exec_command_sync(exec_t &exec);\ntemplate <class ...Args>\nint exec_command_sync(exec_t &exec, Args &&...args) {\n    const char *argv[] = {args..., nullptr};\n    exec.argv = argv;\n    return exec_command_sync(exec);\n}\ntemplate <class ...Args>\nint exec_command_sync(Args &&...args) {\n    exec_t exec;\n    return exec_command_sync(exec, args...);\n}\ntemplate <class ...Args>\nvoid exec_command_async(Args &&...args) {\n    const char *argv[] = {args..., nullptr};\n    exec_t exec {\n        .fork = fork_dont_care,\n        .argv = argv,\n    };\n    exec_command(exec);\n}\n\ntemplate <typename T>\nconstexpr auto operator+(T e) noexcept ->\n    std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>> {\n    return static_cast<std::underlying_type_t<T>>(e);\n}\n\nstd::string full_read(int fd);\nstd::string full_read(const char *filename);\nvoid write_zero(int fd, size_t size);\nstd::string resolve_preinit_dir(const char *base_dir);\n\nusing sFILE = std::unique_ptr<FILE, decltype(&fclose)>;\nusing sDIR = std::unique_ptr<DIR, decltype(&closedir)>;\nsDIR make_dir(DIR *dp);\nsFILE make_file(FILE *fp);\n\nstatic inline sDIR open_dir(const char *path) {\n    return make_dir(opendir(path));\n}\n\nstatic inline sDIR xopen_dir(const char *path) {\n    return make_dir(xopendir(path));\n}\n\nstatic inline sDIR xopen_dir(int dirfd) {\n    return make_dir(xfdopendir(dirfd));\n}\n\nstatic inline sFILE open_file(const char *path, const char *mode) {\n    return make_file(fopen(path, mode));\n}\n\nstatic inline sFILE xopen_file(const char *path, const char *mode) {\n    return make_file(xfopen(path, mode));\n}\n\nstatic inline sFILE xopen_file(int fd, const char *mode) {\n    return make_file(xfdopen(fd, mode));\n}\n\n// Bindings to &Utf8CStr in Rust\nstruct Utf8CStr {\n    const char *data() const;\n    size_t length() const;\n    Utf8CStr(const char *s, size_t len);\n\n    Utf8CStr() : Utf8CStr(\"\", 1) {};\n    Utf8CStr(const Utf8CStr &o) = default;\n    Utf8CStr(const char *s) : Utf8CStr(s, strlen(s) + 1) {};\n    Utf8CStr(std::string s) : Utf8CStr(s.data(), s.length() + 1) {};\n    const char *c_str() const { return this->data(); }\n    size_t size() const { return this->length(); }\n    bool empty() const { return this->length() == 0 ; }\n    std::string_view sv() const { return {data(), length()}; }\n    operator std::string_view() const { return sv(); }\n    bool operator==(std::string_view rhs) const { return sv() == rhs; }\n\nprivate:\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wunused-private-field\"\n    std::array<std::uintptr_t, 2> repr;\n#pragma clang diagnostic pop\n};\n\n// Bindings for std::function to be callable from Rust\nusing CxxFnBoolStrStr = std::function<bool(rust::Str, rust::Str)>;\nstruct FnBoolStrStr : public CxxFnBoolStrStr {\n    using CxxFnBoolStrStr::function;\n    bool call(rust::Str a, rust::Str b) const {\n        return operator()(a, b);\n    }\n};\nusing CxxFnBoolStr = std::function<bool(Utf8CStr)>;\nstruct FnBoolStr : public CxxFnBoolStr {\n    using CxxFnBoolStr::function;\n    bool call(Utf8CStr s) const {\n        return operator()(s);\n    }\n};\n\n#include \"../base-rs.hpp\"\n\n// Functor = function<bool(Utf8CStr, Utf8CStr)>\ntemplate <typename Functor>\nvoid parse_prop_file(const char *file, Functor &&fn) {\n    parse_prop_file_rs(file, [&](rust::Str key, rust::Str val) -> bool {\n        // We perform the null termination here in C++ because it's very difficult to do it\n        // right in Rust due to pointer provenance. Trying to dereference a pointer without\n        // the correct provenance in Rust, even in unsafe code, is undefined behavior.\n        // However on the C++ side, there are fewer restrictions on pointers, so the const_cast here\n        // will not trigger UB in the compiler.\n        *(const_cast<char *>(key.data()) + key.size()) = '\\0';\n        *(const_cast<char *>(val.data()) + val.size()) = '\\0';\n        return fn(Utf8CStr(key.data(), key.size() + 1), Utf8CStr(val.data(), val.size() + 1));\n    });\n}\n"
  },
  {
    "path": "native/src/base/lib.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n\npub use {const_format, libc, nix};\n\npub use cstr::{\n    FsPathFollow, StrErr, Utf8CStr, Utf8CStrBuf, Utf8CStrBufArr, Utf8CStrBufRef, Utf8CString,\n};\nuse cxx_extern::*;\npub use derive;\npub use dir::*;\npub use ffi::{Utf8CStrRef, fork_dont_care, set_nice_name};\npub use files::*;\npub use logging::*;\npub use misc::*;\npub use result::*;\n\npub mod argh;\npub mod cstr;\nmod cxx_extern;\nmod dir;\nmod files;\nmod logging;\nmod misc;\nmod mount;\nmod result;\nmod xwrap;\n\n#[cxx::bridge]\nmod ffi {\n    #[derive(Copy, Clone)]\n    #[repr(i32)]\n    #[cxx_name = \"LogLevel\"]\n    pub(crate) enum LogLevelCxx {\n        Error,\n        Warn,\n        Info,\n        Debug,\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"base.hpp\");\n\n        #[cxx_name = \"Utf8CStr\"]\n        type Utf8CStrRef<'a> = &'a crate::cstr::Utf8CStr;\n\n        fn mut_u8_patch(buf: &mut [u8], from: &[u8], to: &[u8]) -> Vec<usize>;\n        fn fork_dont_care() -> i32;\n        fn set_nice_name(name: Utf8CStrRef);\n\n        type FnBoolStrStr;\n        fn call(self: &FnBoolStrStr, key: &str, value: &str) -> bool;\n\n        type FnBoolStr;\n        fn call(self: &FnBoolStr, key: Utf8CStrRef) -> bool;\n    }\n\n    extern \"Rust\" {\n        #[cxx_name = \"log_with_rs\"]\n        fn log_from_cxx(level: LogLevelCxx, msg: Utf8CStrRef);\n        fn cmdline_logging();\n        fn parse_prop_file_rs(name: Utf8CStrRef, f: &FnBoolStrStr);\n        #[cxx_name = \"file_readline\"]\n        fn file_readline_for_cxx(fd: i32, f: &FnBoolStr);\n        fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32;\n    }\n\n    #[namespace = \"rust\"]\n    extern \"Rust\" {\n        #[cxx_name = \"map_file\"]\n        fn map_file_for_cxx(path: Utf8CStrRef, rw: bool) -> &'static mut [u8];\n        #[cxx_name = \"map_file_at\"]\n        fn map_file_at_for_cxx(fd: i32, path: Utf8CStrRef, rw: bool) -> &'static mut [u8];\n        #[cxx_name = \"map_fd\"]\n        fn map_fd_for_cxx(fd: i32, sz: usize, rw: bool) -> &'static mut [u8];\n    }\n}\n\n// In Rust, we do not want to deal with raw pointers, so we change the\n// signature of all *mut c_void to usize for new_daemon_thread.\npub type ThreadEntry = extern \"C\" fn(usize) -> usize;\nunsafe extern \"C\" {\n    pub fn new_daemon_thread(entry: ThreadEntry, arg: usize);\n}\n"
  },
  {
    "path": "native/src/base/logging.rs",
    "content": "use crate::ffi::LogLevelCxx;\nuse crate::{Utf8CStr, cstr};\nuse bitflags::bitflags;\nuse num_derive::{FromPrimitive, ToPrimitive};\nuse num_traits::FromPrimitive;\nuse std::fmt;\nuse std::io::{Write, stderr, stdout};\nuse std::process::exit;\n\nbitflags! {\n    #[derive(Copy, Clone)]\n    struct LogFlag : u32 {\n        const DISABLE_ERROR = 1 << 0;\n        const DISABLE_WARN = 1 << 1;\n        const DISABLE_INFO = 1 << 2;\n        const DISABLE_DEBUG = 1 << 3;\n        const EXIT_ON_ERROR = 1 << 4;\n    }\n}\n\n#[derive(Copy, Clone, FromPrimitive, ToPrimitive)]\n#[repr(i32)]\npub enum LogLevel {\n    Error = LogLevelCxx::Error.repr,\n    Warn = LogLevelCxx::Warn.repr,\n    Info = LogLevelCxx::Info.repr,\n    Debug = LogLevelCxx::Debug.repr,\n}\n\n// We don't need to care about thread safety, because all\n// logger changes will only happen on the main thread.\npub static mut LOGGER: Logger = Logger {\n    write: |_, _| {},\n    flags: LogFlag::empty(),\n};\n\ntype LogWriter = fn(level: LogLevel, msg: &Utf8CStr);\npub(crate) type Formatter<'a> = &'a mut dyn fmt::Write;\n\n#[derive(Copy, Clone)]\npub struct Logger {\n    pub write: LogWriter,\n    flags: LogFlag,\n}\n\npub fn update_logger(f: impl FnOnce(&mut Logger)) {\n    let mut logger = unsafe { LOGGER };\n    f(&mut logger);\n    unsafe {\n        LOGGER = logger;\n    }\n}\n\npub fn exit_on_error(b: bool) {\n    update_logger(|logger| logger.flags.set(LogFlag::EXIT_ON_ERROR, b));\n}\n\nimpl LogLevel {\n    fn as_disable_flag(&self) -> LogFlag {\n        match *self {\n            LogLevel::Error => LogFlag::DISABLE_ERROR,\n            LogLevel::Warn => LogFlag::DISABLE_WARN,\n            LogLevel::Info => LogFlag::DISABLE_INFO,\n            LogLevel::Debug => LogFlag::DISABLE_DEBUG,\n        }\n    }\n}\n\npub fn set_log_level_state(level: LogLevel, enabled: bool) {\n    update_logger(|logger| logger.flags.set(level.as_disable_flag(), enabled));\n}\n\nfn log_with_writer<F: FnOnce(LogWriter)>(level: LogLevel, f: F) {\n    let logger = unsafe { LOGGER };\n    if logger.flags.contains(level.as_disable_flag()) {\n        return;\n    }\n    f(logger.write);\n    if matches!(level, LogLevel::Error) && logger.flags.contains(LogFlag::EXIT_ON_ERROR) {\n        exit(-1);\n    }\n}\n\npub fn log_from_cxx(level: LogLevelCxx, msg: &Utf8CStr) {\n    if let Some(level) = LogLevel::from_i32(level.repr) {\n        log_with_writer(level, |write| write(level, msg));\n    }\n}\n\npub fn log_with_formatter<F: FnOnce(Formatter) -> fmt::Result>(level: LogLevel, f: F) {\n    log_with_writer(level, |write| {\n        let mut buf = cstr::buf::default();\n        f(&mut buf).ok();\n        write(level, &buf);\n    });\n}\n\npub fn cmdline_logging() {\n    fn cmdline_write(level: LogLevel, msg: &Utf8CStr) {\n        if matches!(level, LogLevel::Info) {\n            stdout().write_all(msg.as_bytes()).ok();\n        } else {\n            stderr().write_all(msg.as_bytes()).ok();\n        }\n    }\n    update_logger(|logger| logger.write = cmdline_write);\n}\n\n#[macro_export]\nmacro_rules! log_with_args {\n    ($level:expr, $($args:tt)+) => {\n        $crate::log_with_formatter($level, |w| writeln!(w, $($args)+))\n    }\n}\n\n#[macro_export]\nmacro_rules! error {\n    ($($args:tt)+) => {\n        $crate::log_with_formatter($crate::LogLevel::Error, |w| writeln!(w, $($args)+))\n    }\n}\n\n#[macro_export]\nmacro_rules! warn {\n    ($($args:tt)+) => {\n        $crate::log_with_formatter($crate::LogLevel::Warn, |w| writeln!(w, $($args)+))\n    }\n}\n\n#[macro_export]\nmacro_rules! info {\n    ($($args:tt)+) => {\n        $crate::log_with_formatter($crate::LogLevel::Info, |w| writeln!(w, $($args)+))\n    }\n}\n\n#[cfg(debug_assertions)]\n#[macro_export]\nmacro_rules! debug {\n    ($($args:tt)+) => {\n        $crate::log_with_formatter($crate::LogLevel::Debug, |w| writeln!(w, $($args)+))\n    }\n}\n\n#[cfg(not(debug_assertions))]\n#[macro_export]\nmacro_rules! debug {\n    ($($args:tt)+) => {};\n}\n"
  },
  {
    "path": "native/src/base/misc.rs",
    "content": "use super::argh::{EarlyExit, MissingRequirements};\nuse crate::{Utf8CStr, Utf8CString, cstr, ffi};\nuse libc::c_char;\nuse std::fmt::Arguments;\nuse std::io::Write;\nuse std::mem::ManuallyDrop;\nuse std::process::exit;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicPtr, Ordering};\nuse std::{fmt, slice, str};\n\npub fn errno() -> &'static mut i32 {\n    unsafe { &mut *libc::__errno() }\n}\n\n// When len is 0, don't care whether buf is null or not\n#[inline]\npub unsafe fn slice_from_ptr<'a, T>(buf: *const T, len: usize) -> &'a [T] {\n    unsafe {\n        if len == 0 {\n            &[]\n        } else {\n            slice::from_raw_parts(buf, len)\n        }\n    }\n}\n\n// When len is 0, don't care whether buf is null or not\n#[inline]\npub unsafe fn slice_from_ptr_mut<'a, T>(buf: *mut T, len: usize) -> &'a mut [T] {\n    unsafe {\n        if len == 0 {\n            &mut []\n        } else {\n            slice::from_raw_parts_mut(buf, len)\n        }\n    }\n}\n\npub trait BytesExt {\n    fn find(&self, needle: &[u8]) -> Option<usize>;\n    fn contains(&self, needle: &[u8]) -> bool {\n        self.find(needle).is_some()\n    }\n}\n\nimpl<T: AsRef<[u8]> + ?Sized> BytesExt for T {\n    fn find(&self, needle: &[u8]) -> Option<usize> {\n        fn inner(haystack: &[u8], needle: &[u8]) -> Option<usize> {\n            unsafe {\n                let ptr: *const u8 = libc::memmem(\n                    haystack.as_ptr().cast(),\n                    haystack.len(),\n                    needle.as_ptr().cast(),\n                    needle.len(),\n                )\n                .cast();\n                if ptr.is_null() {\n                    None\n                } else {\n                    Some(ptr.offset_from(haystack.as_ptr()) as usize)\n                }\n            }\n        }\n        inner(self.as_ref(), needle)\n    }\n}\n\npub trait MutBytesExt {\n    fn patch(&mut self, from: &[u8], to: &[u8]) -> Vec<usize>;\n}\n\nimpl<T: AsMut<[u8]> + ?Sized> MutBytesExt for T {\n    fn patch(&mut self, from: &[u8], to: &[u8]) -> Vec<usize> {\n        ffi::mut_u8_patch(self.as_mut(), from, to)\n    }\n}\n\npub trait EarlyExitExt<T> {\n    fn on_early_exit<F: FnOnce()>(self, print_help_msg: F) -> T;\n}\n\nimpl<T> EarlyExitExt<T> for Result<T, EarlyExit> {\n    fn on_early_exit<F: FnOnce()>(self, print_help_msg: F) -> T {\n        match self {\n            Ok(t) => t,\n            Err(EarlyExit { output, is_help }) => {\n                if is_help {\n                    print_help_msg();\n                    exit(0)\n                } else {\n                    eprintln!(\"{output}\");\n                    print_help_msg();\n                    exit(1)\n                }\n            }\n        }\n    }\n}\n\npub struct PositionalArgParser<'a>(pub slice::Iter<'a, &'a str>);\n\nimpl PositionalArgParser<'_> {\n    pub fn required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {\n        if let Some(next) = self.0.next() {\n            Ok((*next).into())\n        } else {\n            let mut missing = MissingRequirements::default();\n            missing.missing_positional_arg(field_name);\n            missing.err_on_any()?;\n            unreachable!()\n        }\n    }\n\n    pub fn optional(&mut self) -> Option<Utf8CString> {\n        self.0.next().map(|s| (*s).into())\n    }\n\n    pub fn last_required(&mut self, field_name: &'static str) -> Result<Utf8CString, EarlyExit> {\n        let r = self.required(field_name)?;\n        self.ensure_end()?;\n        Ok(r)\n    }\n\n    pub fn last_optional(&mut self) -> Result<Option<Utf8CString>, EarlyExit> {\n        let r = self.optional();\n        if r.is_none() {\n            return Ok(r);\n        }\n        self.ensure_end()?;\n        Ok(r)\n    }\n\n    fn ensure_end(&mut self) -> Result<(), EarlyExit> {\n        match self.0.next() {\n            None => Ok(()),\n            Some(s) => Err(EarlyExit::from(format!(\"Unrecognized argument: {s}\\n\"))),\n        }\n    }\n}\n\npub struct FmtAdaptor<'a, T>(pub &'a mut T)\nwhere\n    T: Write;\n\nimpl<T: Write> fmt::Write for FmtAdaptor<'_, T> {\n    fn write_str(&mut self, s: &str) -> fmt::Result {\n        self.0.write_all(s.as_bytes()).map_err(|_| fmt::Error)\n    }\n    fn write_fmt(&mut self, args: Arguments<'_>) -> fmt::Result {\n        self.0.write_fmt(args).map_err(|_| fmt::Error)\n    }\n}\n\npub struct AtomicArc<T> {\n    ptr: AtomicPtr<T>,\n}\n\nimpl<T> AtomicArc<T> {\n    pub fn new(arc: Arc<T>) -> AtomicArc<T> {\n        let raw = Arc::into_raw(arc);\n        Self {\n            ptr: AtomicPtr::new(raw as *mut _),\n        }\n    }\n\n    pub fn load(&self) -> Arc<T> {\n        let raw = self.ptr.load(Ordering::Acquire);\n        // SAFETY: the raw pointer is always created from Arc::into_raw\n        let arc = ManuallyDrop::new(unsafe { Arc::from_raw(raw) });\n        ManuallyDrop::into_inner(arc.clone())\n    }\n\n    fn swap_ptr(&self, raw: *const T) -> Arc<T> {\n        let prev = self.ptr.swap(raw as *mut _, Ordering::AcqRel);\n        // SAFETY: the raw pointer is always created from Arc::into_raw\n        unsafe { Arc::from_raw(prev) }\n    }\n\n    pub fn swap(&self, arc: Arc<T>) -> Arc<T> {\n        let raw = Arc::into_raw(arc);\n        self.swap_ptr(raw)\n    }\n\n    pub fn store(&self, arc: Arc<T>) {\n        // Drop the previous value\n        let _ = self.swap(arc);\n    }\n}\n\nimpl<T> Drop for AtomicArc<T> {\n    fn drop(&mut self) {\n        // Drop the internal value\n        let _ = self.swap_ptr(std::ptr::null());\n    }\n}\n\nimpl<T: Default> Default for AtomicArc<T> {\n    fn default() -> Self {\n        Self::new(Default::default())\n    }\n}\n\npub struct Chunker {\n    chunk: Box<[u8]>,\n    chunk_size: usize,\n    pos: usize,\n}\n\nimpl Chunker {\n    pub fn new(chunk_size: usize) -> Self {\n        Chunker {\n            // SAFETY: all bytes will be initialized before it is used, tracked by self.pos\n            chunk: unsafe { Box::new_uninit_slice(chunk_size).assume_init() },\n            chunk_size,\n            pos: 0,\n        }\n    }\n\n    pub fn set_chunk_size(&mut self, chunk_size: usize) {\n        self.chunk_size = chunk_size;\n        self.pos = 0;\n        if self.chunk.len() < chunk_size {\n            self.chunk = unsafe { Box::new_uninit_slice(chunk_size).assume_init() };\n        }\n    }\n\n    // Returns (remaining buf, Option<Chunk>)\n    pub fn add_data<'a, 'b: 'a>(&'a mut self, mut buf: &'b [u8]) -> (&'b [u8], Option<&'a [u8]>) {\n        let mut chunk = None;\n        if self.pos > 0 {\n            // Try to fill the chunk\n            let len = std::cmp::min(self.chunk_size - self.pos, buf.len());\n            self.chunk[self.pos..self.pos + len].copy_from_slice(&buf[..len]);\n            self.pos += len;\n            // If the chunk is filled, consume it\n            if self.pos == self.chunk_size {\n                chunk = Some(&self.chunk[..self.chunk_size]);\n                self.pos = 0;\n            }\n            buf = &buf[len..];\n        } else if buf.len() >= self.chunk_size {\n            // Directly consume a chunk from buf\n            chunk = Some(&buf[..self.chunk_size]);\n            buf = &buf[self.chunk_size..];\n        } else {\n            // Copy buf into chunk\n            self.chunk[self.pos..self.pos + buf.len()].copy_from_slice(buf);\n            self.pos += buf.len();\n            return (&[], None);\n        }\n        (buf, chunk)\n    }\n\n    pub fn get_available(&mut self) -> &[u8] {\n        let chunk = &self.chunk[..self.pos];\n        self.pos = 0;\n        chunk\n    }\n}\n\npub struct CmdArgs(pub Vec<&'static str>);\n\nimpl CmdArgs {\n    #[allow(clippy::not_unsafe_ptr_arg_deref)]\n    pub fn new(argc: i32, argv: *const *const c_char) -> CmdArgs {\n        CmdArgs(\n            // SAFETY: libc guarantees argc and argv are properly setup and are static\n            unsafe { slice::from_raw_parts(argv, argc as usize) }\n                .iter()\n                .map(|s| unsafe { Utf8CStr::from_ptr(*s) })\n                .map(|r| r.unwrap_or(cstr!(\"<invalid>\")))\n                .map(Utf8CStr::as_str)\n                .collect(),\n        )\n    }\n\n    pub fn as_slice(&self) -> &[&'static str] {\n        self.0.as_slice()\n    }\n\n    pub fn iter(&self) -> slice::Iter<'_, &'static str> {\n        self.0.iter()\n    }\n\n    pub fn cstr_iter(&self) -> impl Iterator<Item = &'static Utf8CStr> {\n        // SAFETY: libc guarantees null terminated strings\n        self.0\n            .iter()\n            .map(|s| unsafe { Utf8CStr::from_raw_parts(s.as_ptr().cast(), s.len() + 1) })\n    }\n}\n"
  },
  {
    "path": "native/src/base/mount.rs",
    "content": "use crate::{LibcReturn, OsResult, Utf8CStr};\nuse nix::mount::{MntFlags, MsFlags, mount, umount2};\n\nimpl Utf8CStr {\n    pub fn bind_mount_to<'a>(&'a self, path: &'a Utf8CStr, rec: bool) -> OsResult<'a, ()> {\n        let flag = if rec {\n            MsFlags::MS_REC\n        } else {\n            MsFlags::empty()\n        };\n        mount(\n            Some(self),\n            path,\n            None::<&Utf8CStr>,\n            flag | MsFlags::MS_BIND,\n            None::<&Utf8CStr>,\n        )\n        .check_os_err(\"bind_mount\", Some(self), Some(path))\n    }\n\n    pub fn remount_mount_point_flags(&self, flags: MsFlags) -> OsResult<'_, ()> {\n        mount(\n            None::<&Utf8CStr>,\n            self,\n            None::<&Utf8CStr>,\n            MsFlags::MS_BIND | MsFlags::MS_REMOUNT | flags,\n            None::<&Utf8CStr>,\n        )\n        .check_os_err(\"remount\", Some(self), None)\n    }\n\n    pub fn remount_mount_flags(&self, flags: MsFlags) -> OsResult<'_, ()> {\n        mount(\n            None::<&Utf8CStr>,\n            self,\n            None::<&Utf8CStr>,\n            MsFlags::MS_REMOUNT | flags,\n            None::<&Utf8CStr>,\n        )\n        .check_os_err(\"remount\", Some(self), None)\n    }\n\n    pub fn remount_with_data(&self, data: &Utf8CStr) -> OsResult<'_, ()> {\n        mount(\n            None::<&Utf8CStr>,\n            self,\n            None::<&Utf8CStr>,\n            MsFlags::MS_REMOUNT,\n            Some(data),\n        )\n        .check_os_err(\"remount\", Some(self), None)\n    }\n\n    pub fn move_mount_to<'a>(&'a self, path: &'a Utf8CStr) -> OsResult<'a, ()> {\n        mount(\n            Some(self),\n            path,\n            None::<&Utf8CStr>,\n            MsFlags::MS_MOVE,\n            None::<&Utf8CStr>,\n        )\n        .check_os_err(\"move_mount\", Some(self), Some(path))\n    }\n\n    pub fn unmount(&self) -> OsResult<'_, ()> {\n        umount2(self, MntFlags::MNT_DETACH).check_os_err(\"unmount\", Some(self), None)\n    }\n\n    pub fn set_mount_private(&self, rec: bool) -> OsResult<'_, ()> {\n        let flag = if rec {\n            MsFlags::MS_REC\n        } else {\n            MsFlags::empty()\n        };\n        mount(\n            None::<&Utf8CStr>,\n            self,\n            None::<&Utf8CStr>,\n            flag | MsFlags::MS_PRIVATE,\n            None::<&Utf8CStr>,\n        )\n        .check_os_err(\"set_mount_private\", Some(self), None)\n    }\n}\n"
  },
  {
    "path": "native/src/base/result.rs",
    "content": "use crate::logging::Formatter;\nuse crate::{LogLevel, log_with_args, log_with_formatter};\nuse nix::errno::Errno;\nuse std::fmt;\nuse std::fmt::Display;\nuse std::panic::Location;\nuse std::ptr::NonNull;\n\n// Error handling throughout the Rust codebase in Magisk:\n//\n// All errors should be logged and consumed as soon as possible and converted into LoggedError.\n// For `Result` with errors that implement the `Display` trait, use the `?` operator to\n// log and convert to LoggedResult.\n//\n// To log an error with more information, use `ResultExt::log_with_msg()`.\n\n#[derive(Default)]\npub struct LoggedError {}\npub type LoggedResult<T> = Result<T, LoggedError>;\n\n#[macro_export]\nmacro_rules! log_err {\n    () => {{\n        Err($crate::LoggedError::default())\n    }};\n    ($($args:tt)+) => {{\n        $crate::error!($($args)+);\n        Err($crate::LoggedError::default())\n    }};\n}\n\n// Any result or option can be silenced\npub trait SilentLogExt<T> {\n    fn silent(self) -> LoggedResult<T>;\n}\n\nimpl<T, E> SilentLogExt<T> for Result<T, E> {\n    fn silent(self) -> LoggedResult<T> {\n        self.map_err(|_| LoggedError::default())\n    }\n}\n\nimpl<T> SilentLogExt<T> for Option<T> {\n    fn silent(self) -> LoggedResult<T> {\n        self.ok_or_else(LoggedError::default)\n    }\n}\n\n// Public API for logging results\npub trait ResultExt<T> {\n    fn log(self) -> LoggedResult<T>;\n    fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;\n    fn log_ok(self);\n}\n\n// Public API for converting Option to LoggedResult\npub trait OptionExt<T> {\n    fn ok_or_log(self) -> LoggedResult<T>;\n    fn ok_or_log_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T>;\n}\n\nimpl<T> OptionExt<T> for Option<T> {\n    #[inline(always)]\n    fn ok_or_log(self) -> LoggedResult<T> {\n        self.ok_or_else(LoggedError::default)\n    }\n\n    #[cfg(not(debug_assertions))]\n    fn ok_or_log_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        self.ok_or_else(|| {\n            do_log_msg(LogLevel::Error, None, f);\n            LoggedError::default()\n        })\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn ok_or_log_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        let caller = Some(Location::caller());\n        self.ok_or_else(|| {\n            do_log_msg(LogLevel::Error, caller, f);\n            LoggedError::default()\n        })\n    }\n}\n\ntrait Loggable {\n    fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedError;\n    fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(\n        self,\n        level: LogLevel,\n        caller: Option<&'static Location>,\n        f: F,\n    ) -> LoggedError;\n}\n\nimpl<T, E: Loggable> ResultExt<T> for Result<T, E> {\n    #[cfg(not(debug_assertions))]\n    fn log(self) -> LoggedResult<T> {\n        self.map_err(|e| e.do_log(LogLevel::Error, None))\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn log(self) -> LoggedResult<T> {\n        let caller = Some(Location::caller());\n        self.map_err(|e| e.do_log(LogLevel::Error, caller))\n    }\n\n    #[cfg(not(debug_assertions))]\n    fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        self.map_err(|e| e.do_log_msg(LogLevel::Error, None, f))\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        let caller = Some(Location::caller());\n        self.map_err(|e| e.do_log_msg(LogLevel::Error, caller, f))\n    }\n\n    #[cfg(not(debug_assertions))]\n    fn log_ok(self) {\n        self.map_err(|e| e.do_log(LogLevel::Error, None)).ok();\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn log_ok(self) {\n        let caller = Some(Location::caller());\n        self.map_err(|e| e.do_log(LogLevel::Error, caller)).ok();\n    }\n}\n\nimpl<T> ResultExt<T> for LoggedResult<T> {\n    fn log(self) -> LoggedResult<T> {\n        self\n    }\n\n    #[cfg(not(debug_assertions))]\n    fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        self.inspect_err(|_| do_log_msg(LogLevel::Error, None, f))\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn log_with_msg<F: FnOnce(Formatter) -> fmt::Result>(self, f: F) -> LoggedResult<T> {\n        let caller = Some(Location::caller());\n        self.inspect_err(|_| do_log_msg(LogLevel::Error, caller, f))\n    }\n\n    fn log_ok(self) {}\n}\n\n// Allow converting Loggable errors to LoggedError to support `?` operator\nimpl<T: Loggable> From<T> for LoggedError {\n    #[cfg(not(debug_assertions))]\n    fn from(e: T) -> Self {\n        e.do_log(LogLevel::Error, None)\n    }\n\n    #[track_caller]\n    #[cfg(debug_assertions)]\n    fn from(e: T) -> Self {\n        let caller = Some(Location::caller());\n        e.do_log(LogLevel::Error, caller)\n    }\n}\n\n// Actual logging implementation\n\n// Make all printable objects Loggable\nimpl<T: Display> Loggable for T {\n    fn do_log(self, level: LogLevel, caller: Option<&'static Location>) -> LoggedError {\n        if let Some(caller) = caller {\n            log_with_args!(level, \"[{}:{}] {:#}\", caller.file(), caller.line(), self);\n        } else {\n            log_with_args!(level, \"{:#}\", self);\n        }\n        LoggedError::default()\n    }\n\n    fn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(\n        self,\n        level: LogLevel,\n        caller: Option<&'static Location>,\n        f: F,\n    ) -> LoggedError {\n        log_with_formatter(level, |w| {\n            if let Some(caller) = caller {\n                write!(w, \"[{}:{}] \", caller.file(), caller.line())?;\n            }\n            f(w)?;\n            writeln!(w, \": {self:#}\")\n        });\n        LoggedError::default()\n    }\n}\n\nfn do_log_msg<F: FnOnce(Formatter) -> fmt::Result>(\n    level: LogLevel,\n    caller: Option<&'static Location>,\n    f: F,\n) {\n    log_with_formatter(level, |w| {\n        if let Some(caller) = caller {\n            write!(w, \"[{}:{}] \", caller.file(), caller.line())?;\n        }\n        f(w)?;\n        w.write_char('\\n')\n    });\n}\n\n// Check libc return value and map to Result\npub trait LibcReturn\nwhere\n    Self: Sized,\n{\n    type Value;\n\n    fn check_err(self) -> nix::Result<Self::Value>;\n\n    fn into_os_result<'a>(\n        self,\n        name: &'static str,\n        arg1: Option<&'a str>,\n        arg2: Option<&'a str>,\n    ) -> OsResult<'a, Self::Value> {\n        self.check_err()\n            .map_err(|e| OsError::new(e, name, arg1, arg2))\n    }\n\n    fn check_os_err<'a>(\n        self,\n        name: &'static str,\n        arg1: Option<&'a str>,\n        arg2: Option<&'a str>,\n    ) -> OsResult<'a, ()> {\n        self.check_err()\n            .map(|_| ())\n            .map_err(|e| OsError::new(e, name, arg1, arg2))\n    }\n}\n\nmacro_rules! impl_libc_return {\n    ($($t:ty)*) => ($(\n        impl LibcReturn for $t {\n            type Value = Self;\n\n            #[inline(always)]\n            fn check_err(self) -> nix::Result<Self::Value> {\n                if self < 0 {\n                    Err(Errno::last())\n                } else {\n                    Ok(self)\n                }\n            }\n        }\n    )*)\n}\n\nimpl_libc_return! { i8 i16 i32 i64 isize }\n\nimpl<T> LibcReturn for *mut T {\n    type Value = NonNull<T>;\n\n    #[inline(always)]\n    fn check_err(self) -> nix::Result<Self::Value> {\n        NonNull::new(self).ok_or_else(Errno::last)\n    }\n}\n\nimpl<T> LibcReturn for nix::Result<T> {\n    type Value = T;\n\n    #[inline(always)]\n    fn check_err(self) -> Self {\n        self\n    }\n}\n\n#[derive(Debug)]\npub struct OsError<'a> {\n    pub errno: Errno,\n    name: &'static str,\n    arg1: Option<&'a str>,\n    arg2: Option<&'a str>,\n}\n\nimpl OsError<'_> {\n    pub fn new<'a>(\n        errno: Errno,\n        name: &'static str,\n        arg1: Option<&'a str>,\n        arg2: Option<&'a str>,\n    ) -> OsError<'a> {\n        OsError {\n            errno,\n            name,\n            arg1,\n            arg2,\n        }\n    }\n\n    pub fn last_os_error<'a>(\n        name: &'static str,\n        arg1: Option<&'a str>,\n        arg2: Option<&'a str>,\n    ) -> OsError<'a> {\n        Self::new(Errno::last(), name, arg1, arg2)\n    }\n\n    pub fn set_args<'a>(self, arg1: Option<&'a str>, arg2: Option<&'a str>) -> OsError<'a> {\n        Self::new(self.errno, self.name, arg1, arg2)\n    }\n}\n\nimpl Display for OsError<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        if self.name.is_empty() {\n            write!(f, \"{}\", self.errno)\n        } else {\n            match (self.arg1, self.arg2) {\n                (Some(arg1), Some(arg2)) => {\n                    write!(f, \"{} '{arg1}' '{arg2}': {}\", self.name, self.errno)\n                }\n                (Some(arg1), None) => {\n                    write!(f, \"{} '{arg1}': {}\", self.name, self.errno)\n                }\n                _ => {\n                    write!(f, \"{}: {}\", self.name, self.errno)\n                }\n            }\n        }\n    }\n}\n\nimpl std::error::Error for OsError<'_> {}\n\npub type OsResult<'a, T> = Result<T, OsError<'a>>;\n"
  },
  {
    "path": "native/src/base/xwrap.rs",
    "content": "// Functions in this file are only for exporting to C++, DO NOT USE IN RUST\n\nuse crate::cxx_extern::readlinkat;\nuse crate::{Directory, LibcReturn, ResultExt, Utf8CStr, cstr, slice_from_ptr, slice_from_ptr_mut};\nuse libc::{c_char, c_uint, c_ulong, c_void, dev_t, mode_t, off_t};\nuse std::ffi::CStr;\nuse std::fs::File;\nuse std::io::{Read, Write};\nuse std::mem::ManuallyDrop;\nuse std::os::fd::FromRawFd;\nuse std::os::unix::io::RawFd;\nuse std::ptr;\nuse std::ptr::NonNull;\n\nfn ptr_to_str<'a>(ptr: *const c_char) -> Option<&'a str> {\n    if ptr.is_null() {\n        None\n    } else {\n        unsafe { CStr::from_ptr(ptr) }.to_str().ok()\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xrealpath(path: *const c_char, buf: *mut u8, bufsz: usize) -> isize {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => {\n                let mut buf = cstr::buf::wrap_ptr(buf, bufsz);\n                path.realpath(&mut buf)\n                    .log()\n                    .map_or(-1, |_| buf.len() as isize)\n            }\n            Err(_) => -1,\n        }\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xreadlinkat(\n    dirfd: RawFd,\n    path: *const c_char,\n    buf: *mut u8,\n    bufsz: usize,\n) -> isize {\n    unsafe {\n        readlinkat(dirfd, path, buf, bufsz)\n            .into_os_result(\"readlinkat\", ptr_to_str(path), None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xfopen(path: *const c_char, mode: *const c_char) -> *mut libc::FILE {\n    unsafe {\n        libc::fopen(path, mode)\n            .into_os_result(\"fopen\", ptr_to_str(path), None)\n            .log()\n            .map_or(ptr::null_mut(), NonNull::as_ptr)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xfdopen(fd: RawFd, mode: *const c_char) -> *mut libc::FILE {\n    unsafe {\n        libc::fdopen(fd, mode)\n            .into_os_result(\"fdopen\", None, None)\n            .log()\n            .map_or(ptr::null_mut(), NonNull::as_ptr)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xopen(path: *const c_char, flags: i32, mode: mode_t) -> RawFd {\n    unsafe {\n        libc::open(path, flags, mode as c_uint)\n            .into_os_result(\"open\", ptr_to_str(path), None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xopenat(dirfd: RawFd, path: *const c_char, flags: i32, mode: mode_t) -> RawFd {\n    unsafe {\n        libc::openat(dirfd, path, flags, mode as c_uint)\n            .into_os_result(\"openat\", ptr_to_str(path), None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xwrite(fd: RawFd, buf: *const u8, bufsz: usize) -> isize {\n    let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };\n    let data = unsafe { slice_from_ptr(buf, bufsz) };\n    file.write_all(data)\n        .log()\n        .map_or(-1, |_| data.len() as isize)\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xread(fd: RawFd, buf: *mut c_void, bufsz: usize) -> isize {\n    unsafe {\n        libc::read(fd, buf, bufsz)\n            .into_os_result(\"read\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xxread(fd: RawFd, buf: *mut u8, bufsz: usize) -> isize {\n    let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };\n    let data = unsafe { slice_from_ptr_mut(buf, bufsz) };\n    file.read_exact(data)\n        .log()\n        .map_or(-1, |_| data.len() as isize)\n}\n\npub(crate) fn xpipe2(fds: &mut [i32; 2], flags: i32) -> i32 {\n    unsafe {\n        libc::pipe2(fds.as_mut_ptr(), flags)\n            .into_os_result(\"pipe2\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xsetns(fd: RawFd, nstype: i32) -> i32 {\n    unsafe {\n        libc::setns(fd, nstype)\n            .into_os_result(\"setns\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xunshare(flags: i32) -> i32 {\n    unsafe {\n        libc::unshare(flags)\n            .into_os_result(\"unshare\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xopendir(path: *const c_char) -> *mut libc::DIR {\n    unsafe {\n        libc::opendir(path)\n            .into_os_result(\"opendir\", ptr_to_str(path), None)\n            .log()\n            .map_or(ptr::null_mut(), NonNull::as_ptr)\n    }\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xfdopendir(fd: RawFd) -> *mut libc::DIR {\n    unsafe {\n        libc::fdopendir(fd)\n            .into_os_result(\"fdopendir\", None, None)\n            .log()\n            .map_or(ptr::null_mut(), NonNull::as_ptr)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xreaddir(mut dir: ManuallyDrop<Directory>) -> *mut libc::dirent {\n    dir.read()\n        .log()\n        .ok()\n        .flatten()\n        .map_or(ptr::null_mut(), |entry| entry.as_ptr())\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xsetsid() -> i32 {\n    unsafe {\n        libc::setsid()\n            .into_os_result(\"setsid\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xfstat(fd: RawFd, buf: *mut libc::stat) -> i32 {\n    unsafe {\n        libc::fstat(fd, buf)\n            .into_os_result(\"fstat\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xdup2(oldfd: RawFd, newfd: RawFd) -> RawFd {\n    unsafe {\n        libc::dup2(oldfd, newfd)\n            .into_os_result(\"dup2\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xsymlink(target: *const c_char, linkpath: *const c_char) -> i32 {\n    unsafe {\n        libc::symlink(target, linkpath)\n            .into_os_result(\"symlink\", ptr_to_str(target), ptr_to_str(linkpath))\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xmount(\n    src: *const c_char,\n    target: *const c_char,\n    fstype: *const c_char,\n    flags: c_ulong,\n    data: *const c_void,\n) -> i32 {\n    unsafe {\n        libc::mount(src, target, fstype, flags, data)\n            .into_os_result(\"mount\", ptr_to_str(src), ptr_to_str(target))\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xumount2(target: *const c_char, flags: i32) -> i32 {\n    unsafe {\n        libc::umount2(target, flags)\n            .into_os_result(\"umount2\", ptr_to_str(target), None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xrename(oldname: *const c_char, newname: *const c_char) -> i32 {\n    unsafe {\n        libc::rename(oldname, newname)\n            .into_os_result(\"rename\", ptr_to_str(oldname), ptr_to_str(newname))\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xmkdir(path: *const c_char, mode: mode_t) -> i32 {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => path.mkdir(mode).log().map_or(-1, |_| 0),\n            Err(_) => -1,\n        }\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xmkdirs(path: *const c_char, mode: mode_t) -> i32 {\n    unsafe {\n        match Utf8CStr::from_ptr(path) {\n            Ok(path) => path.mkdirs(mode).log().map_or(-1, |_| 0),\n            Err(_) => -1,\n        }\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xsendfile(\n    out_fd: RawFd,\n    in_fd: RawFd,\n    offset: *mut off_t,\n    count: usize,\n) -> isize {\n    unsafe {\n        libc::sendfile(out_fd, in_fd, offset, count)\n            .into_os_result(\"sendfile\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nextern \"C\" fn xfork() -> i32 {\n    unsafe {\n        libc::fork()\n            .into_os_result(\"fork\", None, None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n\n#[unsafe(no_mangle)]\nunsafe extern \"C\" fn xmknod(pathname: *const c_char, mode: mode_t, dev: dev_t) -> i32 {\n    unsafe {\n        libc::mknod(pathname, mode, dev)\n            .into_os_result(\"mknod\", ptr_to_str(pathname), None)\n            .log()\n            .unwrap_or(-1)\n    }\n}\n"
  },
  {
    "path": "native/src/boot/.gitignore",
    "content": "proto/update_metadata.rs\nproto/mod.rs\n"
  },
  {
    "path": "native/src/boot/Cargo.toml",
    "content": "[package]\nname = \"magiskboot\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\ncrate-type = [\"staticlib\"]\npath = \"lib.rs\"\n\n[lints]\nworkspace = true\n\n[build-dependencies]\ncxx-gen = { workspace = true }\npb-rs = { workspace = true }\n\n[dependencies]\nbase = { workspace = true }\ncxx = { workspace = true }\nbyteorder = { workspace = true }\nsize = { workspace = true }\nquick-protobuf = { workspace = true }\nsha1 = { workspace = true }\nsha2 = { workspace = true }\ndigest = { workspace = true }\np256 = { workspace = true }\np384 = { workspace = true }\np521 = { workspace = true }\nrsa = { workspace = true, features = [\"sha2\"] }\nx509-cert = { workspace = true }\nder = { workspace = true, features = [\"derive\", \"pem\"] }\nfdt = { workspace = true }\nbytemuck = { workspace = true, features = [\"derive\", \"min_const_generics\"] }\nnum-traits = { workspace = true }\nflate2 = { workspace = true, features = [\"zlib-rs\"] }\nbzip2 = { workspace = true }\nlz4 = { workspace = true }\nlzma-rust2 = { workspace = true, features = [\"xz\", \"std\", \"encoder\", \"optimization\"] }\nzopfli = { workspace = true, features = [\"gzip\"] }\n"
  },
  {
    "path": "native/src/boot/bootimg.cpp",
    "content": "#include <bit>\n#include <functional>\n#include <memory>\n#include <span>\n\n#include <base.hpp>\n\n#include \"boot-rs.hpp\"\n#include \"bootimg.hpp\"\n#include \"magiskboot.hpp\"\n\nusing namespace std;\n\n#define PADDING 15\n#define SHA256_DIGEST_SIZE 32\n#define SHA_DIGEST_SIZE 20\n\n#define RETURN_OK       0\n#define RETURN_ERROR    1\n#define RETURN_CHROMEOS 2\n#define RETURN_VENDOR   3\n\nstatic void decompress(FileFormat type, int fd, const void *in, size_t size) {\n    decompress_bytes(type, byte_view { in, size }, fd);\n}\n\nstatic off_t compress_len(FileFormat type, byte_view in, int fd) {\n    auto prev = lseek(fd, 0, SEEK_CUR);\n    compress_bytes(type, in, fd);\n    auto now = lseek(fd, 0, SEEK_CUR);\n    return now - prev;\n}\n\nstatic void dump(const void *buf, size_t size, const char *filename) {\n    if (size == 0)\n        return;\n    int fd = creat(filename, 0644);\n    xwrite(fd, buf, size);\n    close(fd);\n}\n\nstatic size_t restore(int fd, const char *filename) {\n    int ifd = xopen(filename, O_RDONLY);\n    size_t size = lseek(ifd, 0, SEEK_END);\n    lseek(ifd, 0, SEEK_SET);\n    xsendfile(fd, ifd, nullptr, size);\n    close(ifd);\n    return size;\n}\n\nstatic bool check_env(const char *name) {\n    const char *val = getenv(name);\n    return val != nullptr && val == \"true\"sv;\n}\n\nstatic bool guess_lzma(const uint8_t *buf, size_t len) {\n    // 0     : (pb * 5 + lp) * 9 + lc\n    // 1 - 4 : dict size, must be 2^n\n    // 5 - 12: all 0xFF\n    if (len <= 13) return false;\n    if (memcmp(buf, \"\\x5d\", 1) != 0) return false;\n    uint32_t dict_sz = 0;\n    memcpy(&dict_sz, buf + 1, sizeof(dict_sz));\n    if (dict_sz == 0 || (dict_sz & (dict_sz - 1)) != 0) return false;\n    if (memcmp(buf + 5, \"\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\", 8) != 0) return false;\n    return true;\n}\n\nFileFormat check_fmt(const void *buf, size_t len) {\n    if (CHECKED_MATCH(CHROMEOS_MAGIC)) {\n        return FileFormat::CHROMEOS;\n    } else if (CHECKED_MATCH(BOOT_MAGIC)) {\n        return FileFormat::AOSP;\n    } else if (CHECKED_MATCH(VENDOR_BOOT_MAGIC)) {\n        return FileFormat::AOSP_VENDOR;\n    } else if (CHECKED_MATCH(GZIP1_MAGIC) || CHECKED_MATCH(GZIP2_MAGIC)) {\n        return FileFormat::GZIP;\n    } else if (CHECKED_MATCH(LZOP_MAGIC)) {\n        return FileFormat::LZOP;\n    } else if (CHECKED_MATCH(XZ_MAGIC)) {\n        return FileFormat::XZ;\n    } else if (guess_lzma(static_cast<const uint8_t *>(buf), len)) {\n        return FileFormat::LZMA;\n    } else if (CHECKED_MATCH(BZIP_MAGIC)) {\n        return FileFormat::BZIP2;\n    } else if (CHECKED_MATCH(LZ41_MAGIC) || CHECKED_MATCH(LZ42_MAGIC)) {\n        return FileFormat::LZ4;\n    } else if (CHECKED_MATCH(LZ4_LEG_MAGIC)) {\n        return FileFormat::LZ4_LEGACY;\n    } else if (CHECKED_MATCH(MTK_MAGIC)) {\n        return FileFormat::MTK;\n    } else if (CHECKED_MATCH(DTB_MAGIC)) {\n        return FileFormat::DTB;\n    } else if (CHECKED_MATCH(DHTB_MAGIC)) {\n        return FileFormat::DHTB;\n    } else if (CHECKED_MATCH(TEGRABLOB_MAGIC)) {\n        return FileFormat::BLOB;\n    } else if (len >= 0x28 && memcmp(&((char *)buf)[0x24], ZIMAGE_MAGIC, 4) == 0) {\n        return FileFormat::ZIMAGE;\n    } else {\n        return FileFormat::UNKNOWN;\n    }\n}\n\nvoid dyn_img_hdr::print() const {\n    uint32_t ver = header_version();\n    fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"HEADER_VER\", ver);\n    if (!is_vendor())\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"KERNEL_SZ\", kernel_size());\n    fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"RAMDISK_SZ\", ramdisk_size());\n    if (ver < 3)\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"SECOND_SZ\", second_size());\n    if (ver == 0)\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"EXTRA_SZ\", extra_size());\n    if (ver == 1 || ver == 2)\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"RECOV_DTBO_SZ\", recovery_dtbo_size());\n    if (ver == 2 || is_vendor())\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"DTB_SZ\", dtb_size());\n    if (ver == 4 && is_vendor())\n        fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"BOOTCONFIG_SZ\", bootconfig_size());\n\n    if (uint32_t os_ver = os_version()) {\n        int a,b,c,y,m = 0;\n        int version = os_ver >> 11;\n        int patch_level = os_ver & 0x7ff;\n\n        a = (version >> 14) & 0x7f;\n        b = (version >> 7) & 0x7f;\n        c = version & 0x7f;\n        fprintf(stderr, \"%-*s [%d.%d.%d]\\n\", PADDING, \"OS_VERSION\", a, b, c);\n\n        y = (patch_level >> 4) + 2000;\n        m = patch_level & 0xf;\n        fprintf(stderr, \"%-*s [%d-%02d]\\n\", PADDING, \"OS_PATCH_LEVEL\", y, m);\n    }\n\n    fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"PAGESIZE\", page_size());\n    if (const char *n = name()) {\n        fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"NAME\", n);\n    }\n    fprintf(stderr, \"%-*s [%.*s%.*s]\\n\", PADDING, \"CMDLINE\",\n            BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());\n    if (const char *checksum = id()) {\n        fprintf(stderr, \"%-*s [\", PADDING, \"CHECKSUM\");\n        for (int i = 0; i < SHA256_DIGEST_SIZE; ++i)\n            fprintf(stderr, \"%02hhx\", checksum[i]);\n        fprintf(stderr, \"]\\n\");\n    }\n}\n\nvoid dyn_img_hdr::dump_hdr_file() const {\n    FILE *fp = xfopen(HEADER_FILE, \"w\");\n    if (name())\n        fprintf(fp, \"name=%s\\n\", name());\n    fprintf(fp, \"cmdline=%.*s%.*s\\n\", BOOT_ARGS_SIZE, cmdline(), BOOT_EXTRA_ARGS_SIZE, extra_cmdline());\n    uint32_t ver = os_version();\n    if (ver) {\n        int a, b, c, y, m;\n        int version, patch_level;\n        version = ver >> 11;\n        patch_level = ver & 0x7ff;\n\n        a = (version >> 14) & 0x7f;\n        b = (version >> 7) & 0x7f;\n        c = version & 0x7f;\n        fprintf(fp, \"os_version=%d.%d.%d\\n\", a, b, c);\n\n        y = (patch_level >> 4) + 2000;\n        m = patch_level & 0xf;\n        fprintf(fp, \"os_patch_level=%d-%02d\\n\", y, m);\n    }\n    fclose(fp);\n}\n\nvoid dyn_img_hdr::load_hdr_file() {\n    parse_prop_file(HEADER_FILE, [=, this](Utf8CStr key, Utf8CStr value) -> bool {\n        if (key == \"name\" && name()) {\n            memset(name(), 0, 16);\n            memcpy(name(), value.data(), value.length() > 15 ? 15 : value.length());\n        } else if (key == \"cmdline\") {\n            memset(cmdline(), 0, BOOT_ARGS_SIZE);\n            memset(extra_cmdline(), 0, BOOT_EXTRA_ARGS_SIZE);\n            if (value.length() > BOOT_ARGS_SIZE) {\n                memcpy(cmdline(), value.data(), BOOT_ARGS_SIZE);\n                auto len = std::min(value.length() - BOOT_ARGS_SIZE, (size_t) BOOT_EXTRA_ARGS_SIZE);\n                memcpy(extra_cmdline(), value.data() + BOOT_ARGS_SIZE, len);\n            } else {\n                memcpy(cmdline(), value.data(), value.length());\n            }\n        } else if (key == \"os_version\") {\n            int patch_level = os_version() & 0x7ff;\n            int a, b, c;\n            sscanf(value.data(), \"%d.%d.%d\", &a, &b, &c);\n            os_version() = (((a << 14) | (b << 7) | c) << 11) | patch_level;\n        } else if (key == \"os_patch_level\") {\n            int os_ver = os_version() >> 11;\n            int y, m;\n            sscanf(value.data(), \"%d-%d\", &y, &m);\n            y -= 2000;\n            os_version() = (os_ver << 11) | (y << 4) | m;\n        }\n        return true;\n    });\n}\n\nboot_img::boot_img(const char *image) :\nmap(image), k_fmt(FileFormat::UNKNOWN), r_fmt(FileFormat::UNKNOWN), e_fmt(FileFormat::UNKNOWN) {\n    fprintf(stderr, \"Parsing boot image: [%s]\\n\", image);\n    for (const uint8_t *addr = map.data(); addr < map.data() + map.size(); ++addr) {\n        FileFormat fmt = check_fmt(addr, map.size());\n        switch (fmt) {\n        case FileFormat::CHROMEOS:\n            // chromeos require external signing\n            flags[CHROMEOS_FLAG] = true;\n            addr += 65535;\n            break;\n        case FileFormat::DHTB:\n            flags[DHTB_FLAG] = true;\n            flags[SEANDROID_FLAG] = true;\n            fprintf(stderr, \"DHTB_HDR\\n\");\n            addr += sizeof(dhtb_hdr) - 1;\n            break;\n        case FileFormat::BLOB:\n            flags[BLOB_FLAG] = true;\n            fprintf(stderr, \"TEGRA_BLOB\\n\");\n            addr += sizeof(blob_hdr) - 1;\n            break;\n        case FileFormat::AOSP:\n        case FileFormat::AOSP_VENDOR:\n            if (parse_image(addr, fmt))\n                return;\n            // fallthrough\n        default:\n            break;\n        }\n    }\n    exit(RETURN_ERROR);\n}\n\nboot_img::~boot_img() {\n    delete hdr;\n}\n\nstruct [[gnu::packed]] fdt_header {\n    struct fdt32_t {\n        uint32_t byte0: 8;\n        uint32_t byte1: 8;\n        uint32_t byte2: 8;\n        uint32_t byte3: 8;\n\n        constexpr operator uint32_t() const {\n            return bit_cast<uint32_t>(fdt32_t {\n                .byte0 = byte3,\n                .byte1 = byte2,\n                .byte2 = byte1,\n                .byte3 = byte0\n            });\n        }\n    };\n\n    struct node_header {\n        fdt32_t tag;\n        char name[0];\n    };\n\n    fdt32_t magic;\t\t\t /* magic word FDT_MAGIC */\n    fdt32_t totalsize;\t\t /* total size of DT block */\n    fdt32_t off_dt_struct;\t\t /* offset to structure */\n    fdt32_t off_dt_strings;\t\t /* offset to strings */\n    fdt32_t off_mem_rsvmap;\t\t /* offset to memory reserve map */\n    fdt32_t version;\t\t /* format version */\n    fdt32_t last_comp_version;\t /* last compatible version */\n\n    /* version 2 fields below */\n    fdt32_t boot_cpuid_phys;\t /* Which physical CPU id we're\n\t\t\t\t\t    booting on */\n    /* version 3 fields below */\n    fdt32_t size_dt_strings;\t /* size of the strings block */\n\n    /* version 17 fields below */\n    fdt32_t size_dt_struct;\t\t /* size of the structure block */\n};\n\nstatic int find_dtb_offset(const uint8_t *buf, unsigned sz) {\n    const uint8_t * const end = buf + sz;\n\n    for (auto curr = buf; curr < end; curr += sizeof(fdt_header)) {\n        curr = static_cast<uint8_t*>(memmem(curr, end - curr, DTB_MAGIC, sizeof(fdt_header::fdt32_t)));\n        if (curr == nullptr)\n            return -1;\n\n        auto fdt_hdr = reinterpret_cast<const fdt_header *>(curr);\n\n        // Check that fdt_header.totalsize does not overflow kernel image size or is empty dtb\n        // https://github.com/torvalds/linux/commit/7b937cc243e5b1df8780a0aa743ce800df6c68d1\n        uint32_t totalsize = fdt_hdr->totalsize;\n        if (totalsize > end - curr || totalsize <= 0x48)\n            continue;\n\n        // Check that fdt_header.off_dt_struct does not overflow kernel image size\n        uint32_t off_dt_struct = fdt_hdr->off_dt_struct;\n        if (off_dt_struct > end - curr)\n            continue;\n\n        // Check that fdt_node_header.tag of first node is FDT_BEGIN_NODE\n        auto fdt_node_hdr = reinterpret_cast<const fdt_header::node_header *>(curr + off_dt_struct);\n        if (fdt_node_hdr->tag != 0x1u)\n            continue;\n\n        return curr - buf;\n    }\n    return -1;\n}\n\nstatic FileFormat check_fmt_lg(const uint8_t *buf, unsigned sz) {\n    FileFormat fmt = check_fmt(buf, sz);\n    if (fmt == FileFormat::LZ4_LEGACY) {\n        // We need to check if it is LZ4_LG\n        uint32_t off = 4;\n        uint32_t block_sz;\n        while (off + sizeof(block_sz) <= sz) {\n            memcpy(&block_sz, buf + off, sizeof(block_sz));\n            off += sizeof(block_sz);\n            if (off + block_sz > sz)\n                return FileFormat::LZ4_LG;\n            off += block_sz;\n        }\n    }\n    return fmt;\n}\n\n#define CMD_MATCH(s) BUFFER_MATCH(h->cmdline, s)\n\nconst uint8_t *boot_img::parse_hdr(const uint8_t *addr, FileFormat type) {\n    if (type == FileFormat::AOSP_VENDOR) {\n        fprintf(stderr, \"VENDOR_BOOT_HDR\\n\");\n        auto h = reinterpret_cast<const boot_img_hdr_vnd_v3*>(addr);\n        switch (h->header_version) {\n        case 4:\n            hdr = new dyn_img_vnd_v4(addr);\n            break;\n        default:\n            hdr = new dyn_img_vnd_v3(addr);\n            break;\n        }\n        return addr;\n    }\n\n    auto h = reinterpret_cast<const boot_img_hdr_v0*>(addr);\n\n    if (h->page_size >= 0x02000000) {\n        fprintf(stderr, \"PXA_BOOT_HDR\\n\");\n        hdr = new dyn_img_pxa(addr);\n        return addr;\n    }\n\n    auto make_aosp_hdr = [](const uint8_t *ptr, ssize_t size = -1) -> dyn_img_hdr * {\n        auto h = reinterpret_cast<const boot_img_hdr_v0*>(ptr);\n        if (memcmp(h->magic, BOOT_MAGIC, BOOT_MAGIC_SIZE) != 0)\n            return nullptr;\n\n        switch (h->header_version) {\n        case 1:\n            return new dyn_img_v1(ptr, size);\n        case 2:\n            return new dyn_img_v2(ptr, size);\n        case 3:\n            return new dyn_img_v3(ptr, size);\n        case 4:\n            return new dyn_img_v4(ptr, size);\n        default:\n            return new dyn_img_v0(ptr, size);\n        }\n    };\n\n    // For NOOKHD and ACCLAIM, the entire boot image is shifted by a fixed offset.\n    // For AMONET, the header itself is internally shifted by a fixed offset.\n\n    if (BUFFER_CONTAIN(addr, AMONET_MICROLOADER_SZ, AMONET_MICROLOADER_MAGIC) &&\n        BUFFER_MATCH(addr + AMONET_MICROLOADER_SZ, BOOT_MAGIC)) {\n        flags[AMONET_FLAG] = true;\n        fprintf(stderr, \"AMONET_MICROLOADER\\n\");\n\n        // The real header is shifted\n        h = reinterpret_cast<const boot_img_hdr_v0*>(addr + AMONET_MICROLOADER_SZ);\n        auto real_hdr_sz = h->page_size - AMONET_MICROLOADER_SZ;\n        hdr = make_aosp_hdr(addr + AMONET_MICROLOADER_SZ, real_hdr_sz);\n        return addr;\n    }\n\n    if (CMD_MATCH(NOOKHD_RL_MAGIC) ||\n        CMD_MATCH(NOOKHD_GL_MAGIC) ||\n        CMD_MATCH(NOOKHD_GR_MAGIC) ||\n        CMD_MATCH(NOOKHD_EB_MAGIC) ||\n        CMD_MATCH(NOOKHD_ER_MAGIC)) {\n        flags[NOOKHD_FLAG] = true;\n        fprintf(stderr, \"NOOKHD_LOADER\\n\");\n        addr += NOOKHD_PRE_HEADER_SZ;\n    } else if (BUFFER_MATCH(h->name, ACCLAIM_MAGIC)) {\n        flags[ACCLAIM_FLAG] = true;\n        fprintf(stderr, \"ACCLAIM_LOADER\\n\");\n        addr += ACCLAIM_PRE_HEADER_SZ;\n    }\n\n    hdr = make_aosp_hdr(addr);\n    return addr;\n}\n\nvoid boot_img::parse_zimage() {\n    z_info.hdr = reinterpret_cast<const zimage_hdr *>(kernel);\n\n    const uint8_t* piggy = nullptr;\n    // Skip 0x28, which includes zimage header\n    for (const uint8_t* curr = kernel + 0x28; curr < kernel + hdr->kernel_size(); curr++) {\n        if (check_fmt_lg(curr, hdr->kernel_size() - (curr - kernel)) != FileFormat::UNKNOWN) {\n            piggy = curr;\n            break;\n        }\n    }\n\n    if (piggy != nullptr) {\n        fprintf(stderr, \"ZIMAGE_KERNEL\\n\");\n        z_info.hdr_sz = piggy - kernel;\n\n        // Find end of piggy\n        uint32_t piggy_size = z_info.hdr->end - z_info.hdr->start;\n        uint32_t piggy_end = piggy_size;\n        uint32_t offsets[16];\n        memcpy(offsets, kernel + piggy_size - sizeof(offsets), sizeof(offsets));\n        for (int i = 15; i >= 0; --i) {\n            if (offsets[i] > (piggy_size - 0xFF) && offsets[i] < piggy_size) {\n                piggy_end = offsets[i];\n                break;\n            }\n        }\n\n        if (piggy_end == piggy_size) {\n            fprintf(stderr, \"! Could not find end of zImage piggy, keeping raw kernel\\n\");\n        } else {\n            flags[ZIMAGE_KERNEL] = true;\n            z_info.tail = byte_view(kernel + piggy_end, hdr->kernel_size() - piggy_end);\n            // Shift the kernel pointer and resize\n            kernel += z_info.hdr_sz;\n            hdr->kernel_size() = piggy_end - z_info.hdr_sz;\n            k_fmt = check_fmt_lg(kernel, hdr->kernel_size());\n        }\n    } else {\n        fprintf(stderr, \"! Could not find zImage piggy, keeping raw kernel\\n\");\n    }\n}\n\nstatic const char *vendor_ramdisk_type(int type) {\n    switch (type) {\n    case VENDOR_RAMDISK_TYPE_PLATFORM:\n        return \"platform\";\n    case VENDOR_RAMDISK_TYPE_RECOVERY:\n        return \"recovery\";\n    case VENDOR_RAMDISK_TYPE_DLKM:\n        return \"dlkm\";\n    case VENDOR_RAMDISK_TYPE_NONE:\n    default:\n        return \"none\";\n    }\n}\n\nstd::span<const vendor_ramdisk_table_entry_v4> boot_img::vendor_ramdisk_tbl() const {\n    if (hdr->vendor_ramdisk_table_size() == 0) {\n        return {};\n    }\n\n    // v4 vendor boot contains multiple ramdisks\n    using table_entry = const vendor_ramdisk_table_entry_v4;\n    if (hdr->vendor_ramdisk_table_entry_size() != sizeof(table_entry)) {\n        fprintf(stderr,\n                \"! Invalid vendor image: vendor_ramdisk_table_entry_size != %zu\\n\",\n                sizeof(table_entry));\n        exit(RETURN_ERROR);\n    }\n    return span(reinterpret_cast<table_entry *>(vendor_ramdisk_table), hdr->vendor_ramdisk_table_entry_num());\n}\n\n\n#define assert_off() \\\nif ((addr + off) > (map.data() + map_end)) {      \\\n    fprintf(stderr, \"Corrupted boot image!\\n\");   \\\n    return false;                                 \\\n}\n\n#define get_block(name)                 \\\nname = addr + off;                      \\\noff += hdr->name##_size();              \\\noff = align_to(off, hdr->page_size());  \\\nassert_off()\n\nbool boot_img::parse_image(const uint8_t *addr, FileFormat type) {\n    addr = parse_hdr(addr, type);\n    if (hdr == nullptr) {\n        fprintf(stderr, \"Invalid boot image header!\\n\");\n        return false;\n    }\n\n    if (const char *id = hdr->id()) {\n        for (int i = SHA_DIGEST_SIZE + 4; i < SHA256_DIGEST_SIZE; ++i) {\n            if (id[i]) {\n                flags[SHA256_FLAG] = true;\n                break;\n            }\n        }\n    }\n\n    hdr->print();\n\n    size_t map_end = align_to(map.size(), getpagesize());\n    size_t off = hdr->hdr_space();\n    get_block(kernel);\n    get_block(ramdisk);\n    get_block(second);\n    get_block(extra);\n    get_block(recovery_dtbo);\n    get_block(dtb);\n    get_block(signature);\n    get_block(vendor_ramdisk_table);\n    get_block(bootconfig);\n\n    payload = byte_view(addr, off);\n    auto tail_addr = addr + off;\n    tail = byte_view(tail_addr, map.data() + map_end - tail_addr);\n\n    if (auto size = hdr->kernel_size()) {\n        if (int dtb_off = find_dtb_offset(kernel, size); dtb_off > 0) {\n            kernel_dtb = byte_view(kernel + dtb_off, size - dtb_off);\n            hdr->kernel_size() = dtb_off;\n            fprintf(stderr, \"%-*s [%zu]\\n\", PADDING, \"KERNEL_DTB_SZ\", kernel_dtb.size());\n        }\n\n        k_fmt = check_fmt_lg(kernel, hdr->kernel_size());\n        if (k_fmt == FileFormat::MTK) {\n            fprintf(stderr, \"MTK_KERNEL_HDR\\n\");\n            flags[MTK_KERNEL] = true;\n            k_hdr = reinterpret_cast<const mtk_hdr *>(kernel);\n            fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"SIZE\", k_hdr->size);\n            fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"NAME\", k_hdr->name);\n            kernel += sizeof(mtk_hdr);\n            hdr->kernel_size() -= sizeof(mtk_hdr);\n            k_fmt = check_fmt_lg(kernel, hdr->kernel_size());\n        }\n        if (k_fmt == FileFormat::ZIMAGE) {\n            parse_zimage();\n        }\n        fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"KERNEL_FMT\", fmt2name(k_fmt));\n    }\n    if (auto size = hdr->ramdisk_size()) {\n        if (hdr->vendor_ramdisk_table_size()) {\n            for (auto &it : vendor_ramdisk_tbl()) {\n                FileFormat fmt = check_fmt_lg(ramdisk + it.ramdisk_offset, it.ramdisk_size);\n                fprintf(stderr,\n                        \"%-*s name=[%s] type=[%s] size=[%u] fmt=[%s]\\n\", PADDING, \"VND_RAMDISK\",\n                        it.ramdisk_name, vendor_ramdisk_type(it.ramdisk_type),\n                        it.ramdisk_size, fmt2name(fmt));\n            }\n        } else {\n            r_fmt = check_fmt_lg(ramdisk, size);\n            if (r_fmt == FileFormat::MTK) {\n                fprintf(stderr, \"MTK_RAMDISK_HDR\\n\");\n                flags[MTK_RAMDISK] = true;\n                r_hdr = reinterpret_cast<const mtk_hdr *>(ramdisk);\n                fprintf(stderr, \"%-*s [%u]\\n\", PADDING, \"SIZE\", r_hdr->size);\n                fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"NAME\", r_hdr->name);\n                ramdisk += sizeof(mtk_hdr);\n                hdr->ramdisk_size() -= sizeof(mtk_hdr);\n                r_fmt = check_fmt_lg(ramdisk, hdr->ramdisk_size());\n            }\n            fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"RAMDISK_FMT\", fmt2name(r_fmt));\n        }\n    }\n    if (auto size = hdr->extra_size()) {\n        e_fmt = check_fmt_lg(extra, size);\n        fprintf(stderr, \"%-*s [%s]\\n\", PADDING, \"EXTRA_FMT\", fmt2name(e_fmt));\n    }\n\n    if (tail.size()) {\n        // Check special flags\n        if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), SEANDROID_MAGIC)) {\n            fprintf(stderr, \"SAMSUNG_SEANDROID\\n\");\n            flags[SEANDROID_FLAG] = true;\n        } else if (tail.size() >= 16 && BUFFER_MATCH(tail.data(), LG_BUMP_MAGIC)) {\n            fprintf(stderr, \"LG_BUMP_IMAGE\\n\");\n            flags[LG_BUMP_FLAG] = true;\n        } else if (verify()) {\n            fprintf(stderr, \"AVB1_SIGNED\\n\");\n            flags[AVB1_SIGNED_FLAG] = true;\n        }\n\n        // Find AVB footer\n        const void *footer = tail.data() + tail.size() - sizeof(AvbFooter);\n        if (BUFFER_MATCH(footer, AVB_FOOTER_MAGIC)) {\n            avb_footer = static_cast<const AvbFooter*>(footer);\n            // Double check if meta header exists\n            const void *meta = payload.data() + __builtin_bswap64(avb_footer->vbmeta_offset);\n            if (BUFFER_MATCH(meta, AVB_MAGIC)) {\n                fprintf(stderr, \"VBMETA\\n\");\n                flags[AVB_FLAG] = true;\n                vbmeta = static_cast<const AvbVBMetaImageHeader*>(meta);\n            }\n        }\n    }\n\n    return true;\n}\n\nint split_image_dtb(Utf8CStr filename, bool skip_decomp) {\n    mmap_data img(filename.c_str());\n\n    if (int offset = find_dtb_offset(img.data(), img.size()); offset > 0) {\n        size_t off = (size_t) offset;\n\n        FileFormat fmt = check_fmt_lg(img.data(), img.size());\n        if (!skip_decomp && fmt_compressed(fmt)) {\n            int fd = creat(KERNEL_FILE, 0644);\n            decompress(fmt, fd, img.data(), off);\n            close(fd);\n        } else {\n            dump(img.data(), off, KERNEL_FILE);\n        }\n        dump(img.data() + off, img.size() - off, KER_DTB_FILE);\n        return 0;\n    } else {\n        fprintf(stderr, \"Cannot find DTB in %s\\n\", filename.c_str());\n        return 1;\n    }\n}\n\nint unpack(Utf8CStr image, bool skip_decomp, bool hdr) {\n    const boot_img boot(image.c_str());\n\n    if (hdr)\n        boot.hdr->dump_hdr_file();\n\n    // Dump kernel\n    if (!skip_decomp && fmt_compressed(boot.k_fmt)) {\n        if (boot.hdr->kernel_size() != 0) {\n            int fd = creat(KERNEL_FILE, 0644);\n            decompress(boot.k_fmt, fd, boot.kernel, boot.hdr->kernel_size());\n            close(fd);\n        }\n    } else {\n        dump(boot.kernel, boot.hdr->kernel_size(), KERNEL_FILE);\n    }\n\n    // Dump kernel_dtb\n    dump(boot.kernel_dtb.data(), boot.kernel_dtb.size(), KER_DTB_FILE);\n\n    // Dump ramdisk\n    if (boot.hdr->vendor_ramdisk_table_size()) {\n        xmkdir(VND_RAMDISK_DIR, 0755);\n        owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);\n        for (auto &it : boot.vendor_ramdisk_tbl()) {\n            char file_name[40];\n            if (it.ramdisk_name[0] == '\\0') {\n                strscpy(file_name, RAMDISK_FILE, sizeof(file_name));\n            } else {\n                ssprintf(file_name, sizeof(file_name), \"%s.cpio\", it.ramdisk_name);\n            }\n            owned_fd fd = xopenat(dirfd, file_name, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644);\n            FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);\n            if (!skip_decomp && fmt_compressed(fmt)) {\n                decompress(fmt, fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);\n            } else {\n                xwrite(fd, boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);\n            }\n        }\n    } else if (!skip_decomp && fmt_compressed(boot.r_fmt)) {\n        if (boot.hdr->ramdisk_size() != 0) {\n            int fd = creat(RAMDISK_FILE, 0644);\n            decompress(boot.r_fmt, fd, boot.ramdisk, boot.hdr->ramdisk_size());\n            close(fd);\n        }\n    } else {\n        dump(boot.ramdisk, boot.hdr->ramdisk_size(), RAMDISK_FILE);\n    }\n\n    // Dump second\n    dump(boot.second, boot.hdr->second_size(), SECOND_FILE);\n\n    // Dump extra\n    if (!skip_decomp && fmt_compressed(boot.e_fmt)) {\n        if (boot.hdr->extra_size() != 0) {\n            int fd = creat(EXTRA_FILE, 0644);\n            decompress(boot.e_fmt, fd, boot.extra, boot.hdr->extra_size());\n            close(fd);\n        }\n    } else {\n        dump(boot.extra, boot.hdr->extra_size(), EXTRA_FILE);\n    }\n\n    // Dump recovery_dtbo\n    dump(boot.recovery_dtbo, boot.hdr->recovery_dtbo_size(), RECV_DTBO_FILE);\n\n    // Dump dtb\n    dump(boot.dtb, boot.hdr->dtb_size(), DTB_FILE);\n\n    // Dump bootconfig\n    dump(boot.bootconfig, boot.hdr->bootconfig_size(), BOOTCONFIG_FILE);\n\n    if (boot.flags[CHROMEOS_FLAG]) return RETURN_CHROMEOS;\n    if (boot.hdr->is_vendor()) return RETURN_VENDOR;\n    return RETURN_OK;\n}\n\n#define file_align_with(page_size) \\\nwrite_zero(fd, align_padding(lseek(fd, 0, SEEK_CUR) - off.header, page_size))\n\n#define file_align() file_align_with(boot.hdr->page_size())\n\nvoid repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp) {\n    const boot_img boot(src_img.c_str());\n    fprintf(stderr, \"Repack to boot image: [%s]\\n\", out_img.c_str());\n\n    struct {\n        uint32_t header;\n        uint32_t kernel;\n        uint32_t ramdisk;\n        uint32_t second;\n        uint32_t extra;\n        uint32_t dtb;\n        uint32_t tail;\n        uint32_t vbmeta;\n    } off{};\n\n    // Create a new boot header and reset sizes\n    auto hdr = boot.hdr->clone();\n    hdr->kernel_size() = 0;\n    hdr->ramdisk_size() = 0;\n    hdr->second_size() = 0;\n    hdr->dtb_size() = 0;\n    hdr->bootconfig_size() = 0;\n\n    if (access(HEADER_FILE, R_OK) == 0)\n        hdr->load_hdr_file();\n\n    /***************\n     * Write blocks\n     ***************/\n\n    // Create new image\n    int fd = open(out_img.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);\n\n    // Copy non-standard headers\n    if (boot.flags[DHTB_FLAG]) {\n        xwrite(fd, boot.map.data(), sizeof(dhtb_hdr));\n    } else if (boot.flags[BLOB_FLAG]) {\n        xwrite(fd, boot.map.data(), sizeof(blob_hdr));\n    } else if (boot.flags[NOOKHD_FLAG]) {\n        xwrite(fd, boot.map.data(), NOOKHD_PRE_HEADER_SZ);\n    } else if (boot.flags[ACCLAIM_FLAG]) {\n        xwrite(fd, boot.map.data(), ACCLAIM_PRE_HEADER_SZ);\n    }\n\n    // Copy raw header\n    off.header = lseek(fd, 0, SEEK_CUR);\n    xwrite(fd, boot.payload.data(), hdr->hdr_space());\n\n    // kernel\n    off.kernel = lseek(fd, 0, SEEK_CUR);\n    if (boot.flags[MTK_KERNEL]) {\n        // Copy MTK headers\n        xwrite(fd, boot.k_hdr, sizeof(mtk_hdr));\n    }\n    if (boot.flags[ZIMAGE_KERNEL]) {\n        // Copy zImage headers\n        xwrite(fd, boot.z_info.hdr, boot.z_info.hdr_sz);\n    }\n    if (access(KERNEL_FILE, R_OK) == 0) {\n        mmap_data m(KERNEL_FILE);\n        if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.k_fmt)) {\n            // Always use zopfli for zImage compression\n            auto fmt = (boot.flags[ZIMAGE_KERNEL] && boot.k_fmt == FileFormat::GZIP) ? FileFormat::ZOPFLI : boot.k_fmt;\n            hdr->kernel_size() = compress_len(fmt, m, fd);\n        } else {\n            hdr->kernel_size() = xwrite(fd, m.data(), m.size());\n        }\n\n        if (boot.flags[ZIMAGE_KERNEL]) {\n            if (hdr->kernel_size() > boot.hdr->kernel_size()) {\n                fprintf(stderr, \"! Recompressed kernel is too large, using original kernel\\n\");\n                ftruncate64(fd, lseek64(fd, - (off64_t) hdr->kernel_size(), SEEK_CUR));\n                xwrite(fd, boot.kernel, boot.hdr->kernel_size());\n            } else if (!skip_comp) {\n                // Pad zeros to make sure the zImage file size does not change\n                // Also ensure the last 4 bytes are the uncompressed vmlinux size\n                uint32_t sz = m.size();\n                write_zero(fd, boot.hdr->kernel_size() - hdr->kernel_size() - sizeof(sz));\n                xwrite(fd, &sz, sizeof(sz));\n            }\n\n            // zImage size shall remain the same\n            hdr->kernel_size() = boot.hdr->kernel_size();\n        }\n    } else if (boot.hdr->kernel_size() != 0) {\n        xwrite(fd, boot.kernel, boot.hdr->kernel_size());\n        hdr->kernel_size() = boot.hdr->kernel_size();\n    }\n    if (boot.flags[ZIMAGE_KERNEL]) {\n        // Copy zImage tail and adjust size accordingly\n        hdr->kernel_size() += boot.z_info.hdr_sz;\n        hdr->kernel_size() += xwrite(fd, boot.z_info.tail.data(), boot.z_info.tail.size());\n    }\n\n    // kernel dtb\n    if (access(KER_DTB_FILE, R_OK) == 0)\n        hdr->kernel_size() += restore(fd, KER_DTB_FILE);\n    file_align();\n\n    // ramdisk\n    off.ramdisk = lseek(fd, 0, SEEK_CUR);\n    if (boot.flags[MTK_RAMDISK]) {\n        // Copy MTK headers\n        xwrite(fd, boot.r_hdr, sizeof(mtk_hdr));\n    }\n\n    vector<vendor_ramdisk_table_entry_v4> ramdisk_table;\n\n    if (boot.hdr->vendor_ramdisk_table_size()) {\n        // Create a copy so we can modify it\n        ramdisk_table.assign_range(boot.vendor_ramdisk_tbl());\n\n        owned_fd dirfd = xopen(VND_RAMDISK_DIR, O_RDONLY | O_CLOEXEC);\n        uint32_t ramdisk_offset = 0;\n        for (auto &it : ramdisk_table) {\n            char file_name[64];\n            if (it.ramdisk_name[0] == '\\0') {\n                strscpy(file_name, RAMDISK_FILE, sizeof(file_name));\n            } else {\n                ssprintf(file_name, sizeof(file_name), \"%s.cpio\", it.ramdisk_name);\n            }\n            mmap_data m(dirfd, file_name);\n            FileFormat fmt = check_fmt_lg(boot.ramdisk + it.ramdisk_offset, it.ramdisk_size);\n            it.ramdisk_offset = ramdisk_offset;\n            if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(fmt)) {\n                it.ramdisk_size = compress_len(fmt, m, fd);\n            } else {\n                it.ramdisk_size = xwrite(fd, m.data(), m.size());\n            }\n            ramdisk_offset += it.ramdisk_size;\n        }\n\n        hdr->ramdisk_size() = ramdisk_offset;\n        file_align();\n    } else if (access(RAMDISK_FILE, R_OK) == 0) {\n        mmap_data m(RAMDISK_FILE);\n        auto r_fmt = boot.r_fmt;\n        if (!skip_comp && !hdr->is_vendor() && hdr->header_version() == 4 && r_fmt != FileFormat::LZ4_LEGACY) {\n            // A v4 boot image ramdisk will have to be merged with other vendor ramdisks,\n            // and they have to use the exact same compression method. v4 GKIs are required to\n            // use lz4 (legacy), so hardcode the format here.\n            fprintf(stderr, \"RAMDISK_FMT: [%s] -> [%s]\\n\", fmt2name(r_fmt), fmt2name(FileFormat::LZ4_LEGACY));\n            r_fmt = FileFormat::LZ4_LEGACY;\n        }\n        if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(r_fmt)) {\n            hdr->ramdisk_size() = compress_len(r_fmt, m, fd);\n        } else {\n            hdr->ramdisk_size() = xwrite(fd, m.data(), m.size());\n        }\n        file_align();\n    }\n\n    // second\n    off.second = lseek(fd, 0, SEEK_CUR);\n    if (access(SECOND_FILE, R_OK) == 0) {\n        hdr->second_size() = restore(fd, SECOND_FILE);\n        file_align();\n    }\n\n    // extra\n    off.extra = lseek(fd, 0, SEEK_CUR);\n    if (access(EXTRA_FILE, R_OK) == 0) {\n        mmap_data m(EXTRA_FILE);\n        if (!skip_comp && !fmt_compressed_any(check_fmt(m.data(), m.size())) && fmt_compressed(boot.e_fmt)) {\n            hdr->extra_size() = compress_len(boot.e_fmt, m, fd);\n        } else {\n            hdr->extra_size() = xwrite(fd, m.data(), m.size());\n        }\n        file_align();\n    }\n\n    // recovery_dtbo\n    if (access(RECV_DTBO_FILE, R_OK) == 0) {\n        hdr->recovery_dtbo_offset() = lseek(fd, 0, SEEK_CUR);\n        hdr->recovery_dtbo_size() = restore(fd, RECV_DTBO_FILE);\n        file_align();\n    }\n\n    // dtb\n    off.dtb = lseek(fd, 0, SEEK_CUR);\n    if (access(DTB_FILE, R_OK) == 0) {\n        hdr->dtb_size() = restore(fd, DTB_FILE);\n        file_align();\n    }\n\n    // Copy boot signature\n    if (boot.hdr->signature_size()) {\n        xwrite(fd, boot.signature, boot.hdr->signature_size());\n        file_align();\n    }\n\n    // vendor ramdisk table\n    if (!ramdisk_table.empty()) {\n        xwrite(fd, ramdisk_table.data(), sizeof(*ramdisk_table.data()) * ramdisk_table.size());\n        file_align();\n    }\n\n    // bootconfig\n    if (access(BOOTCONFIG_FILE, R_OK) == 0) {\n        hdr->bootconfig_size() = restore(fd, BOOTCONFIG_FILE);\n        file_align();\n    }\n\n    // Proprietary stuffs\n    if (boot.flags[SEANDROID_FLAG]) {\n        xwrite(fd, SEANDROID_MAGIC, 16);\n        if (boot.flags[DHTB_FLAG]) {\n            xwrite(fd, \"\\xFF\\xFF\\xFF\\xFF\", 4);\n        }\n    } else if (boot.flags[LG_BUMP_FLAG]) {\n        xwrite(fd, LG_BUMP_MAGIC, 16);\n    }\n\n    off.tail = lseek(fd, 0, SEEK_CUR);\n    file_align();\n\n    // vbmeta\n    if (boot.flags[AVB_FLAG]) {\n        // According to avbtool.py, if the input is not an Android sparse image\n        // (which boot images are not), the default block size is 4096\n        file_align_with(4096);\n        off.vbmeta = lseek(fd, 0, SEEK_CUR);\n        uint64_t vbmeta_size = __builtin_bswap64(boot.avb_footer->vbmeta_size);\n        xwrite(fd, boot.vbmeta, vbmeta_size);\n    }\n\n    // Pad image to original size if not chromeos (as it requires post processing)\n    if (!boot.flags[CHROMEOS_FLAG]) {\n        off_t current = lseek(fd, 0, SEEK_CUR);\n        if (current < boot.map.size()) {\n            write_zero(fd, boot.map.size() - current);\n        }\n    }\n\n    /******************\n     * Patch the image\n     ******************/\n\n    uint32_t aosp_img_size = off.tail - off.header;\n\n    // Map output image as rw\n    mmap_data out(fd, lseek(fd, 0, SEEK_END), true);\n\n    // MTK headers\n    if (boot.flags[MTK_KERNEL]) {\n        auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.kernel);\n        m_hdr->size = hdr->kernel_size();\n        hdr->kernel_size() += sizeof(mtk_hdr);\n    }\n    if (boot.flags[MTK_RAMDISK]) {\n        auto m_hdr = reinterpret_cast<mtk_hdr *>(out.data() + off.ramdisk);\n        m_hdr->size = hdr->ramdisk_size();\n        hdr->ramdisk_size() += sizeof(mtk_hdr);\n    }\n\n    // Make sure header size matches\n    hdr->header_size() = hdr->hdr_size();\n\n    // Update checksum\n    if (char *id = hdr->id()) {\n        auto ctx = get_sha(!boot.flags[SHA256_FLAG]);\n        uint32_t size = hdr->kernel_size();\n        ctx->update(byte_view(out.data() + off.kernel, size));\n        ctx->update(byte_view(&size, sizeof(size)));\n        size = hdr->ramdisk_size();\n        ctx->update(byte_view(out.data() + off.ramdisk, size));\n        ctx->update(byte_view(&size, sizeof(size)));\n        size = hdr->second_size();\n        ctx->update(byte_view(out.data() + off.second, size));\n        ctx->update(byte_view(&size, sizeof(size)));\n        size = hdr->extra_size();\n        if (size) {\n            ctx->update(byte_view(out.data() + off.extra, size));\n            ctx->update(byte_view(&size, sizeof(size)));\n        }\n        uint32_t ver = hdr->header_version();\n        if (ver == 1 || ver == 2) {\n            size = hdr->recovery_dtbo_size();\n            ctx->update(byte_view(out.data() + hdr->recovery_dtbo_offset(), size));\n            ctx->update(byte_view(&size, sizeof(size)));\n        }\n        if (ver == 2) {\n            size = hdr->dtb_size();\n            ctx->update(byte_view(out.data() + off.dtb, size));\n            ctx->update(byte_view(&size, sizeof(size)));\n        }\n        memset(id, 0, BOOT_ID_SIZE);\n        ctx->finalize_into(byte_data(id, ctx->output_size()));\n    }\n\n    // Print new header info\n    hdr->print();\n\n    // Copy main header\n    if (boot.flags[AMONET_FLAG]) {\n        auto real_hdr_sz = std::min(hdr->hdr_space() - AMONET_MICROLOADER_SZ, hdr->hdr_size());\n        memcpy(out.data() + off.header + AMONET_MICROLOADER_SZ, hdr->raw_hdr(), real_hdr_sz);\n    } else {\n        memcpy(out.data() + off.header, hdr->raw_hdr(), hdr->hdr_size());\n    }\n\n    if (boot.flags[AVB_FLAG]) {\n        // Copy and patch AVB structures\n        auto footer = reinterpret_cast<AvbFooter*>(out.data() + out.size() - sizeof(AvbFooter));\n        memcpy(footer, boot.avb_footer, sizeof(AvbFooter));\n        footer->original_image_size = __builtin_bswap64(aosp_img_size);\n        footer->vbmeta_offset = __builtin_bswap64(off.vbmeta);\n        if (check_env(\"PATCHVBMETAFLAG\")) {\n            auto vbmeta = reinterpret_cast<AvbVBMetaImageHeader*>(out.data() + off.vbmeta);\n            vbmeta->flags = __builtin_bswap32(3);\n        }\n    }\n\n    if (boot.flags[DHTB_FLAG]) {\n        // DHTB header\n        auto d_hdr = reinterpret_cast<dhtb_hdr *>(out.data());\n        d_hdr->size = aosp_img_size + 16 /* SEANDROID_MAGIC */ + 4 /* DHTB trailer */;\n        sha256_hash(byte_view(out.data() + sizeof(dhtb_hdr), d_hdr->size),\n                    byte_data(d_hdr->checksum, SHA256_DIGEST_SIZE));\n    } else if (boot.flags[BLOB_FLAG]) {\n        // Blob header\n        auto b_hdr = reinterpret_cast<blob_hdr *>(out.data());\n        b_hdr->size = aosp_img_size;\n    }\n\n    // Sign the image after we finish patching the boot image\n    if (boot.flags[AVB1_SIGNED_FLAG]) {\n        byte_view payload(out.data() + off.header, aosp_img_size);\n        auto sig = sign_payload(payload);\n        if (!sig.empty()) {\n            lseek(fd, off.tail, SEEK_SET);\n            xwrite(fd, sig.data(), sig.size());\n        }\n    }\n\n    close(fd);\n}\n\nvoid cleanup() {\n    unlink(HEADER_FILE);\n    unlink(KERNEL_FILE);\n    unlink(RAMDISK_FILE);\n    unlink(SECOND_FILE);\n    unlink(KER_DTB_FILE);\n    unlink(EXTRA_FILE);\n    unlink(RECV_DTBO_FILE);\n    unlink(DTB_FILE);\n    unlink(BOOTCONFIG_FILE);\n    rm_rf(VND_RAMDISK_DIR);\n}\n"
  },
  {
    "path": "native/src/boot/bootimg.hpp",
    "content": "#pragma once\n\n#include <cstdint>\n#include <utility>\n#include <bitset>\n#include <rust/cxx.h>\n\n/******************\n * Special Headers\n *****************/\n\nstruct mtk_hdr {\n    uint32_t magic;         /* MTK magic */\n    uint32_t size;          /* Size of the content */\n    char name[32];          /* The type of the header */\n\n    char padding[472];      /* Padding to 512 bytes */\n} __attribute__((packed));\n\nstruct dhtb_hdr {\n    char magic[8];          /* DHTB magic */\n    uint8_t checksum[40];   /* Payload SHA256, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */\n    uint32_t size;          /* Payload size, whole image + SEANDROIDENFORCE + 0xFFFFFFFF */\n\n    char padding[460];      /* Padding to 512 bytes */\n} __attribute__((packed));\n\nstruct blob_hdr {\n    char secure_magic[20];  /* \"-SIGNED-BY-SIGNBLOB-\" */\n    uint32_t datalen;       /* 0x00000000 */\n    uint32_t signature;     /* 0x00000000 */\n    char magic[16];         /* \"MSM-RADIO-UPDATE\" */\n    uint32_t hdr_version;   /* 0x00010000 */\n    uint32_t hdr_size;      /* Size of header */\n    uint32_t part_offset;   /* Same as size */\n    uint32_t num_parts;     /* Number of partitions */\n    uint32_t unknown[7];    /* All 0x00000000 */\n    char name[4];           /* Name of partition */\n    uint32_t offset;        /* offset in blob where this partition starts */\n    uint32_t size;          /* Size of data */\n    uint32_t version;       /* 0x00000001 */\n} __attribute__((packed));\n\nstruct zimage_hdr {\n    uint32_t code[9];\n    uint32_t magic;      /* zImage magic */\n    uint32_t start;      /* absolute load/run zImage address */\n    uint32_t end;        /* zImage end address */\n    uint32_t endian;     /* endianness flag */\n    // There could be more fields, but we don't care\n} __attribute__((packed));\n\n/**************\n * AVB Headers\n **************/\n\n#define AVB_FOOTER_MAGIC_LEN 4\n#define AVB_MAGIC_LEN 4\n#define AVB_RELEASE_STRING_SIZE 48\n\n// https://android.googlesource.com/platform/external/avb/+/refs/heads/android11-release/libavb/avb_footer.h\nstruct AvbFooter {\n    uint8_t magic[AVB_FOOTER_MAGIC_LEN];\n    uint32_t version_major;\n    uint32_t version_minor;\n    uint64_t original_image_size;\n    uint64_t vbmeta_offset;\n    uint64_t vbmeta_size;\n    uint8_t reserved[28];\n} __attribute__((packed));\n\n// https://android.googlesource.com/platform/external/avb/+/refs/heads/android11-release/libavb/avb_vbmeta_image.h\nstruct AvbVBMetaImageHeader {\n    uint8_t magic[AVB_MAGIC_LEN];\n    uint32_t required_libavb_version_major;\n    uint32_t required_libavb_version_minor;\n    uint64_t authentication_data_block_size;\n    uint64_t auxiliary_data_block_size;\n    uint32_t algorithm_type;\n    uint64_t hash_offset;\n    uint64_t hash_size;\n    uint64_t signature_offset;\n    uint64_t signature_size;\n    uint64_t public_key_offset;\n    uint64_t public_key_size;\n    uint64_t public_key_metadata_offset;\n    uint64_t public_key_metadata_size;\n    uint64_t descriptors_offset;\n    uint64_t descriptors_size;\n    uint64_t rollback_index;\n    uint32_t flags;\n    uint32_t rollback_index_location;\n    uint8_t release_string[AVB_RELEASE_STRING_SIZE];\n    uint8_t reserved[80];\n} __attribute__((packed));\n\n/*********************\n * Boot Image Headers\n *********************/\n\n// https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/android12-release/include/bootimg/bootimg.h\n\n#define BOOT_MAGIC_SIZE 8\n#define BOOT_NAME_SIZE 16\n#define BOOT_ID_SIZE 32\n#define BOOT_ARGS_SIZE 512\n#define BOOT_EXTRA_ARGS_SIZE 1024\n#define VENDOR_BOOT_ARGS_SIZE 2048\n#define VENDOR_RAMDISK_NAME_SIZE 32\n#define VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE 16\n\n#define VENDOR_RAMDISK_TYPE_NONE 0\n#define VENDOR_RAMDISK_TYPE_PLATFORM 1\n#define VENDOR_RAMDISK_TYPE_RECOVERY 2\n#define VENDOR_RAMDISK_TYPE_DLKM 3\n\n/*\n * When the boot image header has a version of 0 - 2, the structure of the boot\n * image is as follows:\n *\n * +-----------------+\n * | boot header     | 1 page\n * +-----------------+\n * | kernel          | m pages\n * +-----------------+\n * | ramdisk         | n pages\n * +-----------------+\n * | second stage    | o pages\n * +-----------------+\n * | extra blob      | x pages (non standard)\n * +-----------------+\n * | recovery dtbo   | p pages\n * +-----------------+\n * | dtb             | q pages\n * +-----------------+\n *\n * m = (kernel_size + page_size - 1) / page_size\n * n = (ramdisk_size + page_size - 1) / page_size\n * o = (second_size + page_size - 1) / page_size\n * p = (recovery_dtbo_size + page_size - 1) / page_size\n * q = (dtb_size + page_size - 1) / page_size\n * x = (extra_size + page_size - 1) / page_size\n */\n\nstruct boot_img_hdr_v0_common {\n    char magic[BOOT_MAGIC_SIZE];\n\n    uint32_t kernel_size;  /* size in bytes */\n    uint32_t kernel_addr;  /* physical load addr */\n\n    uint32_t ramdisk_size; /* size in bytes */\n    uint32_t ramdisk_addr; /* physical load addr */\n\n    uint32_t second_size;  /* size in bytes */\n    uint32_t second_addr;  /* physical load addr */\n} __attribute__((packed));\n\nstruct boot_img_hdr_v0 : public boot_img_hdr_v0_common {\n    uint32_t tags_addr;    /* physical addr for kernel tags */\n\n    // In AOSP headers, this field is used for page size.\n    // For Samsung PXA headers, the use of this field is unknown;\n    // however, its value is something unrealistic to be treated as page size.\n    // We use this fact to determine whether this is an AOSP or PXA header.\n    union {\n        uint32_t unknown;\n        uint32_t page_size;    /* flash page size we assume */\n    };\n\n    // In header v1, this field is used for header version\n    // However, on some devices like Samsung, this field is used to store DTB\n    // We treat this field differently based on its value\n    union {\n        uint32_t header_version;  /* the version of the header */\n        uint32_t extra_size;      /* extra blob size in bytes */\n    };\n\n    // Operating system version and security patch level.\n    // For version \"A.B.C\" and patch level \"Y-M-D\":\n    //   (7 bits for each of A, B, C; 7 bits for (Y-2000), 4 bits for M)\n    //   os_version = A[31:25] B[24:18] C[17:11] (Y-2000)[10:4] M[3:0]\n    uint32_t os_version;\n\n    char name[BOOT_NAME_SIZE];  /* asciiz product name */\n    char cmdline[BOOT_ARGS_SIZE];\n    char id[BOOT_ID_SIZE];      /* timestamp / checksum / sha1 / etc */\n\n    // Supplemental command line data; kept here to maintain\n    // binary compatibility with older versions of mkbootimg.\n    char extra_cmdline[BOOT_EXTRA_ARGS_SIZE];\n} __attribute__((packed));\n\nstruct boot_img_hdr_v1 : public boot_img_hdr_v0 {\n    uint32_t recovery_dtbo_size;    /* size in bytes for recovery DTBO/ACPIO image */\n    uint64_t recovery_dtbo_offset;  /* offset to recovery dtbo/acpio in boot image */\n    uint32_t header_size;\n} __attribute__((packed));\n\nstruct boot_img_hdr_v2 : public boot_img_hdr_v1 {\n    uint32_t dtb_size;  /* size in bytes for DTB image */\n    uint64_t dtb_addr;  /* physical load address for DTB image */\n} __attribute__((packed));\n\n// Special Samsung header\nstruct boot_img_hdr_pxa : public boot_img_hdr_v0_common {\n    uint32_t extra_size;   /* extra blob size in bytes */\n    uint32_t unknown;\n    uint32_t tags_addr;    /* physical addr for kernel tags */\n    uint32_t page_size;    /* flash page size we assume */\n\n    char name[24];         /* asciiz product name */\n    char cmdline[BOOT_ARGS_SIZE];\n    char id[BOOT_ID_SIZE]; /* timestamp / checksum / sha1 / etc */\n\n    char extra_cmdline[BOOT_EXTRA_ARGS_SIZE];\n} __attribute__((packed));\n\n/*\n * When the boot image header has a version of 3 - 4, the structure of the boot\n * image is as follows:\n *\n * +---------------------+\n * | boot header         | 4096 bytes\n * +---------------------+\n * | kernel              | m pages\n * +---------------------+\n * | ramdisk             | n pages\n * +---------------------+\n * | boot signature      | g pages\n * +---------------------+\n *\n * m = (kernel_size + 4096 - 1) / 4096\n * n = (ramdisk_size + 4096 - 1) / 4096\n * g = (signature_size + 4096 - 1) / 4096\n *\n * Page size is fixed at 4096 bytes.\n *\n * The structure of the vendor boot image is as follows:\n *\n * +------------------------+\n * | vendor boot header     | o pages\n * +------------------------+\n * | vendor ramdisk section | p pages\n * +------------------------+\n * | dtb                    | q pages\n * +------------------------+\n * | vendor ramdisk table   | r pages\n * +------------------------+\n * | bootconfig             | s pages\n * +------------------------+\n *\n * o = (2128 + page_size - 1) / page_size\n * p = (vendor_ramdisk_size + page_size - 1) / page_size\n * q = (dtb_size + page_size - 1) / page_size\n * r = (vendor_ramdisk_table_size + page_size - 1) / page_size\n * s = (vendor_bootconfig_size + page_size - 1) / page_size\n *\n * Note that in version 4 of the vendor boot image, multiple vendor ramdisks can\n * be included in the vendor boot image. The bootloader can select a subset of\n * ramdisks to load at runtime. To help the bootloader select the ramdisks, each\n * ramdisk is tagged with a type tag and a set of hardware identifiers\n * describing the board, soc or platform that this ramdisk is intended for.\n *\n * The vendor ramdisk section is consist of multiple ramdisk images concatenated\n * one after another, and vendor_ramdisk_size is the size of the section, which\n * is the total size of all the ramdisks included in the vendor boot image.\n *\n * The vendor ramdisk table holds the size, offset, type, name and hardware\n * identifiers of each ramdisk. The type field denotes the type of its content.\n * The vendor ramdisk names are unique. The hardware identifiers are specified\n * in the board_id field in each table entry. The board_id field is consist of a\n * vector of unsigned integer words, and the encoding scheme is defined by the\n * hardware vendor.\n *\n * For the different type of ramdisks, there are:\n *    - VENDOR_RAMDISK_TYPE_NONE indicates the value is unspecified.\n *    - VENDOR_RAMDISK_TYPE_PLATFORM ramdisks contain platform specific bits, so\n *      the bootloader should always load these into memory.\n *    - VENDOR_RAMDISK_TYPE_RECOVERY ramdisks contain recovery resources, so\n *      the bootloader should load these when booting into recovery.\n *    - VENDOR_RAMDISK_TYPE_DLKM ramdisks contain dynamic loadable kernel\n *      modules.\n *\n * Version 4 of the vendor boot image also adds a bootconfig section to the end\n * of the image. This section contains Boot Configuration parameters known at\n * build time. The bootloader is responsible for placing this section directly\n * after the generic ramdisk, followed by the bootconfig trailer, before\n * entering the kernel.\n */\n\nstruct boot_img_hdr_v3 {\n    uint8_t magic[BOOT_MAGIC_SIZE];\n\n    uint32_t kernel_size;  /* size in bytes */\n    uint32_t ramdisk_size; /* size in bytes */\n    uint32_t os_version;\n    uint32_t header_size;\n    uint32_t reserved[4];\n\n    uint32_t header_version;\n\n    char cmdline[BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE];\n} __attribute__((packed));\n\nstruct boot_img_hdr_vnd_v3 {\n    // Must be VENDOR_BOOT_MAGIC.\n    uint8_t magic[BOOT_MAGIC_SIZE];\n    // Version of the vendor boot image header.\n    uint32_t header_version;\n    uint32_t page_size;     /* flash page size we assume */\n    uint32_t kernel_addr;   /* physical load addr */\n    uint32_t ramdisk_addr;  /* physical load addr */\n    uint32_t ramdisk_size;  /* size in bytes */\n    char cmdline[VENDOR_BOOT_ARGS_SIZE];\n    uint32_t tags_addr;     /* physical addr for kernel tags (if required) */\n    char name[BOOT_NAME_SIZE]; /* asciiz product name */\n    uint32_t header_size;\n    uint32_t dtb_size;      /* size in bytes for DTB image */\n    uint64_t dtb_addr;      /* physical load address for DTB image */\n} __attribute__((packed));\n\nstruct boot_img_hdr_v4 : public boot_img_hdr_v3 {\n    uint32_t signature_size; /* size in bytes */\n} __attribute__((packed));\n\nstruct boot_img_hdr_vnd_v4 : public boot_img_hdr_vnd_v3 {\n    uint32_t vendor_ramdisk_table_size;       /* size in bytes for the vendor ramdisk table */\n    uint32_t vendor_ramdisk_table_entry_num;  /* number of entries in the vendor ramdisk table */\n    uint32_t vendor_ramdisk_table_entry_size; /* size in bytes for a vendor ramdisk table entry */\n    uint32_t bootconfig_size; /* size in bytes for the bootconfig section */\n} __attribute__((packed));\n\nstruct vendor_ramdisk_table_entry_v4 {\n    uint32_t ramdisk_size;   /* size in bytes for the ramdisk image */\n    uint32_t ramdisk_offset; /* offset to the ramdisk image in vendor ramdisk section */\n    uint32_t ramdisk_type;   /* type of the ramdisk */\n    char ramdisk_name[VENDOR_RAMDISK_NAME_SIZE]; /* asciiz ramdisk name */\n\n    // Hardware identifiers describing the board, soc or platform which this\n    // ramdisk is intended to be loaded on.\n    uint32_t board_id[VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE];\n} __attribute__((packed));\n\n/*******************************\n * Polymorphic Universal Header\n *******************************/\n\ntemplate <typename T>\nstatic T align_to(T v, int a) {\n    static_assert(std::is_integral_v<T>);\n    return (v + a - 1) / a * a;\n}\n\ntemplate <typename T>\nstatic T align_padding(T v, int a) {\n    return align_to(v, a) - v;\n}\n\n#define decl_val(name, len) \\\nvirtual uint##len##_t name() const { return 0; }\n\n#define decl_var(name, len) \\\nvirtual uint##len##_t &name() { return j##len(); } \\\ndecl_val(name, len)\n\n#define decl_str(name) \\\nvirtual char *name() { return nullptr; } \\\nvirtual const char *name() const { return nullptr; }\n\nstruct dyn_img_hdr {\n\n    virtual bool is_vendor() const = 0;\n\n    // Standard entries\n    decl_var(kernel_size, 32)\n    decl_var(ramdisk_size, 32)\n    decl_var(second_size, 32)\n    decl_val(page_size, 32)\n    decl_val(header_version, 32)\n    decl_var(extra_size, 32)\n    decl_var(os_version, 32)\n    decl_str(name)\n    decl_str(cmdline)\n    decl_str(id)\n    decl_str(extra_cmdline)\n\n    // v1/v2 specific\n    decl_var(recovery_dtbo_size, 32)\n    decl_var(recovery_dtbo_offset, 64)\n    decl_var(header_size, 32)\n    decl_var(dtb_size, 32)\n\n    // v4 specific\n    decl_val(signature_size, 32)\n\n    // v4 vendor specific\n    decl_val(vendor_ramdisk_table_size, 32)\n    decl_val(vendor_ramdisk_table_entry_num, 32)\n    decl_val(vendor_ramdisk_table_entry_size, 32)\n    decl_var(bootconfig_size, 32)\n\n    virtual ~dyn_img_hdr() {\n        free(raw);\n    }\n\n    virtual size_t hdr_size() const = 0;\n    virtual size_t hdr_space() const { return page_size(); }\n    virtual dyn_img_hdr *clone() const = 0;\n\n    const void *raw_hdr() const { return raw; }\n    void print() const;\n    void dump_hdr_file() const;\n    void load_hdr_file();\n\nprotected:\n    union {\n        boot_img_hdr_v2 *v2_hdr;     /* AOSP v2 header */\n        boot_img_hdr_v4 *v4_hdr;     /* AOSP v4 header */\n        boot_img_hdr_vnd_v4 *v4_vnd; /* AOSP vendor v4 header */\n        boot_img_hdr_pxa *hdr_pxa;   /* Samsung PXA header */\n        void *raw;                   /* Raw pointer */\n    };\n\n    static uint32_t &j32() { _j32 = 0; return _j32; }\n    static uint64_t &j64() { _j64 = 0; return _j64; }\n\nprivate:\n    // Junk for references\n    inline static uint32_t _j32 = 0;\n    inline static uint64_t _j64 = 0;\n};\n\n#undef decl_var\n#undef decl_val\n#undef decl_str\n\n#define __impl_cls(name, hdr)           \\\nprotected: name() = default;            \\\npublic:                                 \\\nexplicit                                \\\nname(const void *p, ssize_t sz = -1) {  \\\n    if (sz < 0) sz = sizeof(hdr);       \\\n    raw = calloc(sizeof(hdr), 1);       \\\n    memcpy(raw, p, sz);                 \\\n}                                       \\\nsize_t hdr_size() const override {      \\\n    return sizeof(hdr);                 \\\n}                                       \\\ndyn_img_hdr *clone() const override {   \\\n    auto p = new name(raw);             \\\n    return p;                           \\\n};\n\n#define __impl_val(name, hdr_name) \\\ndecltype(std::declval<const dyn_img_hdr>().name()) name() const override { return hdr_name->name; }\n\n#define __impl_var(name, hdr_name) \\\ndecltype(std::declval<dyn_img_hdr>().name()) name() override { return hdr_name->name; } \\\n__impl_val(name, hdr_name)\n\n#define impl_cls(ver)  __impl_cls(dyn_img_##ver, boot_img_hdr_##ver)\n#define impl_val(name) __impl_val(name, v2_hdr)\n#define impl_var(name) __impl_var(name, v2_hdr)\n\nstruct dyn_img_hdr_boot : public dyn_img_hdr {\n    bool is_vendor() const final { return false; }\n};\n\nstruct dyn_img_common : public dyn_img_hdr_boot {\n    impl_var(kernel_size)\n    impl_var(ramdisk_size)\n    impl_var(second_size)\n};\n\nstruct dyn_img_v0 : public dyn_img_common {\n    impl_cls(v0)\n\n    impl_val(page_size)\n    impl_var(extra_size)\n    impl_var(os_version)\n    impl_var(name)\n    impl_var(cmdline)\n    impl_var(id)\n    impl_var(extra_cmdline)\n};\n\nstruct dyn_img_v1 : public dyn_img_v0 {\n    impl_cls(v1)\n\n    impl_val(header_version)\n    impl_var(recovery_dtbo_size)\n    impl_var(recovery_dtbo_offset)\n    impl_var(header_size)\n\n    uint32_t &extra_size() override { return j32(); }\n    uint32_t extra_size() const override { return 0; }\n};\n\nstruct dyn_img_v2 : public dyn_img_v1 {\n    impl_cls(v2)\n\n    impl_var(dtb_size)\n};\n\n#undef impl_val\n#undef impl_var\n#define impl_val(name) __impl_val(name, hdr_pxa)\n#define impl_var(name) __impl_var(name, hdr_pxa)\n\nstruct dyn_img_pxa : public dyn_img_common {\n    impl_cls(pxa)\n\n    impl_var(extra_size)\n    impl_val(page_size)\n    impl_var(name)\n    impl_var(cmdline)\n    impl_var(id)\n    impl_var(extra_cmdline)\n};\n\n#undef impl_val\n#undef impl_var\n#define impl_val(name) __impl_val(name, v4_hdr)\n#define impl_var(name) __impl_var(name, v4_hdr)\n\nstruct dyn_img_v3 : public dyn_img_hdr_boot {\n    impl_cls(v3)\n\n    impl_var(kernel_size)\n    impl_var(ramdisk_size)\n    impl_var(os_version)\n    impl_var(header_size)\n    impl_val(header_version)\n    impl_var(cmdline)\n\n    // Make API compatible\n    uint32_t page_size() const override { return 4096; }\n    char *extra_cmdline() override { return &v4_hdr->cmdline[BOOT_ARGS_SIZE]; }\n    const char *extra_cmdline() const override { return &v4_hdr->cmdline[BOOT_ARGS_SIZE]; }\n};\n\nstruct dyn_img_v4 : public dyn_img_v3 {\n    impl_cls(v4)\n\n    impl_val(signature_size)\n};\n\nstruct dyn_img_hdr_vendor : public dyn_img_hdr {\n    bool is_vendor() const final { return true; }\n};\n\n#undef impl_val\n#undef impl_var\n#define impl_val(name) __impl_val(name, v4_vnd)\n#define impl_var(name) __impl_var(name, v4_vnd)\n\nstruct dyn_img_vnd_v3 : public dyn_img_hdr_vendor {\n    impl_cls(vnd_v3)\n\n    impl_val(header_version)\n    impl_val(page_size)\n    impl_var(ramdisk_size)\n    impl_var(cmdline)\n    impl_var(name)\n    impl_var(header_size)\n    impl_var(dtb_size)\n\n    size_t hdr_space() const override { return align_to(hdr_size(), page_size()); }\n\n    // Make API compatible\n    char *extra_cmdline() override { return &v4_vnd->cmdline[BOOT_ARGS_SIZE]; }\n    const char *extra_cmdline() const override { return &v4_vnd->cmdline[BOOT_ARGS_SIZE]; }\n};\n\nstruct dyn_img_vnd_v4 : public dyn_img_vnd_v3 {\n    impl_cls(vnd_v4)\n\n    impl_val(vendor_ramdisk_table_size)\n    impl_val(vendor_ramdisk_table_entry_num)\n    impl_val(vendor_ramdisk_table_entry_size)\n    impl_var(bootconfig_size)\n};\n\n#undef __impl_cls\n#undef __impl_val\n#undef __impl_var\n#undef impl_cls\n#undef impl_val\n#undef impl_var\n\n/******************\n * Full Boot Image\n ******************/\n\nenum {\n    MTK_KERNEL,\n    MTK_RAMDISK,\n    CHROMEOS_FLAG,\n    DHTB_FLAG,\n    SEANDROID_FLAG,\n    LG_BUMP_FLAG,\n    SHA256_FLAG,\n    BLOB_FLAG,\n    NOOKHD_FLAG,\n    ACCLAIM_FLAG,\n    AMONET_FLAG,\n    AVB1_SIGNED_FLAG,\n    AVB_FLAG,\n    ZIMAGE_KERNEL,\n    BOOT_FLAGS_MAX\n};\n\nstruct boot_img {\n    // Memory map of the whole image\n    const mmap_data map;\n\n    // Android image header\n    dyn_img_hdr *hdr = nullptr;\n\n    // Flags to indicate the state of current boot image\n    std::bitset<BOOT_FLAGS_MAX> flags;\n\n    // The format of kernel, ramdisk and extra\n    FileFormat k_fmt;\n    FileFormat r_fmt;\n    FileFormat e_fmt;\n\n    /*************************************************************\n     * Following pointers points within the read-only mmap region\n     *************************************************************/\n\n    // Layout of the memory mapped region\n    // +---------+\n    // | head    | Vendor specific. Should not exist for standard AOSP boot images.\n    // +---------+\n    // | payload | The actual entire AOSP boot image, including the boot image header.\n    // +---------+\n    // | tail    | Data after payload. Usually contains signature/AVB information.\n    // +---------+\n\n    byte_view payload;\n    byte_view tail;\n\n    // MTK headers\n    const mtk_hdr *k_hdr = nullptr;\n    const mtk_hdr *r_hdr = nullptr;\n\n    // The pointers/values after parse_image\n    // +---------------+\n    // | z_info.hdr    | z_info.hdr_sz\n    // +---------------+\n    // | kernel        | hdr->kernel_size()\n    // +---------------+\n    // | z_info.tail   |\n    // +---------------+\n    struct {\n        const zimage_hdr *hdr = nullptr;\n        uint32_t hdr_sz = 0;\n        byte_view tail{};\n    } z_info;\n\n    // AVB structs\n    const AvbFooter *avb_footer = nullptr;\n    const AvbVBMetaImageHeader *vbmeta = nullptr;\n\n    // Pointers to blocks defined in header\n    const uint8_t *kernel = nullptr;\n    const uint8_t *ramdisk = nullptr;\n    const uint8_t *second = nullptr;\n    const uint8_t *extra = nullptr;\n    const uint8_t *recovery_dtbo = nullptr;\n    const uint8_t *dtb = nullptr;\n    const uint8_t *signature = nullptr;\n    const uint8_t *vendor_ramdisk_table = nullptr;\n    const uint8_t *bootconfig = nullptr;\n\n    // dtb embedded in kernel\n    byte_view kernel_dtb;\n\n    explicit boot_img(const char *);\n    ~boot_img();\n\n    bool parse_image(const uint8_t *addr, FileFormat type);\n    void parse_zimage();\n    const uint8_t *parse_hdr(const uint8_t *addr, FileFormat type);\n    std::span<const vendor_ramdisk_table_entry_v4> vendor_ramdisk_tbl() const;\n\n    // Rust FFI\n    static std::unique_ptr<boot_img> create(Utf8CStr name) { return std::make_unique<boot_img>(name.c_str()); }\n    rust::Slice<const uint8_t> get_payload() const { return payload; }\n    rust::Slice<const uint8_t> get_tail() const { return tail; }\n    bool is_signed() const { return flags[AVB1_SIGNED_FLAG]; }\n    uint64_t tail_off() const { return tail.data() - map.data(); }\n\n    // Implemented in Rust\n    bool verify() const noexcept;\n};\n"
  },
  {
    "path": "native/src/boot/build.rs",
    "content": "use pb_rs::ConfigBuilder;\nuse pb_rs::types::FileDescriptor;\n\nuse crate::codegen::gen_cxx_binding;\n\n#[path = \"../include/codegen.rs\"]\nmod codegen;\n\n#[allow(clippy::unwrap_used)]\nfn main() {\n    println!(\"cargo:rerun-if-changed=proto/update_metadata.proto\");\n\n    gen_cxx_binding(\"boot-rs\");\n\n    let cb = ConfigBuilder::new(\n        &[\"proto/update_metadata.proto\"],\n        None,\n        Some(&\"proto\"),\n        &[\".\"],\n    )\n    .unwrap();\n    FileDescriptor::run(\n        &cb.single_module(true)\n            .dont_use_cow(true)\n            .generate_getters(true)\n            .build(),\n    )\n    .unwrap();\n}\n"
  },
  {
    "path": "native/src/boot/cli.rs",
    "content": "use crate::compress::{compress_cmd, decompress_cmd};\nuse crate::cpio::{cpio_commands, print_cpio_usage};\nuse crate::dtb::{DtbAction, dtb_commands, print_dtb_usage};\nuse crate::ffi::{BootImage, FileFormat, cleanup, repack, split_image_dtb, unpack};\nuse crate::patch::hexpatch;\nuse crate::payload::extract_boot_from_payload;\nuse crate::sign::{sha1_hash, sign_boot_image};\nuse argh::{CommandInfo, EarlyExit, FromArgs, SubCommand};\nuse base::libc::umask;\nuse base::nix::fcntl::OFlag;\nuse base::{\n    CmdArgs, EarlyExitExt, LoggedResult, MappedFile, PositionalArgParser, ResultExt, Utf8CStr,\n    Utf8CString, WriteExt, argh, cmdline_logging, cstr, log_err,\n};\nuse std::ffi::c_char;\nuse std::io::{Seek, SeekFrom, Write};\nuse std::str::FromStr;\n\n#[derive(FromArgs)]\nstruct Cli {\n    #[argh(subcommand)]\n    action: Action,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand)]\nenum Action {\n    Unpack(Unpack),\n    Repack(Repack),\n    Verify(Verify),\n    Sign(Sign),\n    Extract(Extract),\n    HexPatch(HexPatch),\n    Cpio(Cpio),\n    Dtb(Dtb),\n    Split(Split),\n    Sha1(Sha1),\n    Cleanup(Cleanup),\n    Compress(Compress),\n    Decompress(Decompress),\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"unpack\")]\nstruct Unpack {\n    #[argh(switch, short = 'n', long = none)]\n    no_decompress: bool,\n    #[argh(switch, short = 'h', long = none)]\n    dump_header: bool,\n    #[argh(positional)]\n    img: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"repack\")]\nstruct Repack {\n    #[argh(switch, short = 'n', long = none)]\n    no_compress: bool,\n    #[argh(positional)]\n    img: Utf8CString,\n    #[argh(positional)]\n    out: Option<Utf8CString>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"verify\")]\nstruct Verify {\n    #[argh(positional)]\n    img: Utf8CString,\n    #[argh(positional)]\n    cert: Option<Utf8CString>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"sign\")]\nstruct Sign {\n    #[argh(positional)]\n    img: Utf8CString,\n    #[argh(positional)]\n    name: Option<Utf8CString>,\n    #[argh(positional)]\n    cert: Option<Utf8CString>,\n    #[argh(positional)]\n    key: Option<Utf8CString>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"extract\")]\nstruct Extract {\n    #[argh(positional)]\n    payload: Utf8CString,\n    #[argh(positional)]\n    partition: Option<Utf8CString>,\n    #[argh(positional)]\n    outfile: Option<Utf8CString>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"hexpatch\")]\nstruct HexPatch {\n    #[argh(positional)]\n    file: Utf8CString,\n    #[argh(positional)]\n    src: Utf8CString,\n    #[argh(positional)]\n    dest: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"cpio\")]\nstruct Cpio {\n    #[argh(positional)]\n    file: Utf8CString,\n    #[argh(positional)]\n    cmds: Vec<String>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"dtb\")]\nstruct Dtb {\n    #[argh(positional)]\n    file: Utf8CString,\n    #[argh(subcommand)]\n    action: DtbAction,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"split\")]\nstruct Split {\n    #[argh(switch, short = 'n', long = none)]\n    no_decompress: bool,\n    #[argh(positional)]\n    file: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"sha1\")]\nstruct Sha1 {\n    #[argh(positional)]\n    file: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"cleanup\")]\nstruct Cleanup {}\n\nstruct Compress {\n    format: FileFormat,\n    file: Utf8CString,\n    out: Option<Utf8CString>,\n}\n\nimpl FromArgs for Compress {\n    fn from_args(command_name: &[&str], args: &[&str]) -> Result<Self, EarlyExit> {\n        let cmd = command_name.last().copied().unwrap_or_default();\n        let fmt = cmd.strip_prefix(\"compress=\").unwrap_or(\"gzip\");\n\n        let Ok(fmt) = FileFormat::from_str(fmt) else {\n            return Err(EarlyExit::from(format!(\n                \"Unsupported or unknown compression format: {fmt}\\n\"\n            )));\n        };\n\n        let mut iter = PositionalArgParser(args.iter());\n        Ok(Compress {\n            format: fmt,\n            file: iter.required(\"infile\")?,\n            out: iter.last_optional()?,\n        })\n    }\n}\n\nimpl SubCommand for Compress {\n    const COMMAND: &'static CommandInfo = &CommandInfo {\n        name: \"compress\",\n        description: \"\",\n    };\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"decompress\")]\nstruct Decompress {\n    #[argh(positional)]\n    file: Utf8CString,\n    #[argh(positional)]\n    out: Option<Utf8CString>,\n}\n\nfn print_usage(cmd: &str) {\n    eprintln!(\n        r#\"MagiskBoot - Boot Image Modification Tool\n\nUsage: {0} <action> [args...]\n\nSupported actions:\n  unpack [-n] [-h] <bootimg>\n    Unpack <bootimg> to its individual components, each component to\n    a file with its corresponding file name in the current directory.\n    Supported components: kernel, kernel_dtb, ramdisk.cpio, second,\n    dtb, extra, and recovery_dtbo.\n    By default, each component will be decompressed on-the-fly.\n    If '-n' is provided, all decompression operations will be skipped;\n    each component will remain untouched, dumped in its original format.\n    If '-h' is provided, the boot image header information will be\n    dumped to the file 'header', which can be used to modify header\n    configurations during repacking.\n    Return values:\n    0:valid    1:error    2:chromeos    3:vendor_boot\n\n  repack [-n] <origbootimg> [outbootimg]\n    Repack boot image components using files from the current directory\n    to [outbootimg], or 'new-boot.img' if not specified. Current directory\n    should only contain required files for [outbootimg], or incorrect\n    [outbootimg] may be produced.\n    <origbootimg> is the original boot image used to unpack the components.\n    By default, each component will be automatically compressed using its\n    corresponding format detected in <origbootimg>. If a component file\n    in the current directory is already compressed, then no addition\n    compression will be performed for that specific component.\n    If '-n' is provided, all compression operations will be skipped.\n    If env variable PATCHVBMETAFLAG is set to true, all disable flags in\n    the boot image's vbmeta header will be set.\n\n  verify <bootimg> [x509.pem]\n    Check whether the boot image is signed with AVB 1.0 signature.\n    Optionally provide a certificate to verify whether the image is\n    signed by the public key certificate.\n    Return value:\n    0:valid    1:error\n\n  sign <bootimg> [name] [x509.pem pk8]\n    Sign <bootimg> with AVB 1.0 signature.\n    Optionally provide the name of the image (default: '/boot').\n    Optionally provide the certificate/private key pair for signing.\n    If the certificate/private key pair is not provided, the AOSP\n    verity key bundled in the executable will be used.\n\n  extract <payload.bin> [partition] [outfile]\n    Extract [partition] from <payload.bin> to [outfile].\n    If [outfile] is not specified, then output to '[partition].img'.\n    If [partition] is not specified, then attempt to extract either\n    'init_boot' or 'boot'. Which partition was chosen can be determined\n    by whichever 'init_boot.img' or 'boot.img' exists.\n    <payload.bin> can be '-' to be STDIN.\n\n  hexpatch <file> <hexpattern1> <hexpattern2>\n    Search <hexpattern1> in <file>, and replace it with <hexpattern2>\n\n  cpio <incpio> [commands...]\n    Do cpio commands to <incpio> (modifications are done in-place).\n    Each command is a single argument; add quotes for each command.\n    See \"cpio --help\" for supported commands.\n\n  dtb <file> <action> [args...]\n    Do dtb related actions to <file>.\n    See \"dtb --help\" for supported actions.\n\n  split [-n] <file>\n    Split image.*-dtb into kernel + kernel_dtb.\n    If '-n' is provided, decompression operations will be skipped;\n    the kernel will remain untouched, split in its original format.\n\n  sha1 <file>\n    Print the SHA1 checksum for <file>\n\n  cleanup\n    Cleanup the current working directory\n\n  compress[=format] <infile> [outfile]\n    Compress <infile> with [format] to [outfile].\n    <infile>/[outfile] can be '-' to be STDIN/STDOUT.\n    If [format] is not specified, then gzip will be used.\n    If [outfile] is not specified, then <infile> will be replaced\n    with another file suffixed with a matching file extension.\n    Supported formats:\n    {1}\n\n  decompress <infile> [outfile]\n    Detect format and decompress <infile> to [outfile].\n    <infile>/[outfile] can be '-' to be STDIN/STDOUT.\n    If [outfile] is not specified, then <infile> will be replaced\n    with another file removing its archive format file extension.\n    Supported formats:\n    {1}\n\"#,\n        cmd,\n        FileFormat::formats()\n    );\n}\n\nfn verify_cmd(image: &Utf8CStr, cert: Option<&Utf8CStr>) -> bool {\n    let image = BootImage::new(image);\n    match cert {\n        None => {\n            // Boot image parsing already checks if the image is signed\n            image.is_signed()\n        }\n        Some(_) => {\n            // Provide a custom certificate and re-verify\n            image.verify(cert).is_ok()\n        }\n    }\n}\n\nfn sign_cmd(\n    image: &Utf8CStr,\n    name: Option<&Utf8CStr>,\n    cert: Option<&Utf8CStr>,\n    key: Option<&Utf8CStr>,\n) -> LoggedResult<()> {\n    let img = BootImage::new(image);\n    let name = name.unwrap_or(cstr!(\"/boot\"));\n    let sig = sign_boot_image(img.payload(), name, cert, key)?;\n    let tail_off = img.tail_off();\n    drop(img);\n    let mut fd = image.open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)?;\n    fd.seek(SeekFrom::Start(tail_off))?;\n    fd.write_all(&sig)?;\n    let current = fd.stream_position()?;\n    let eof = fd.seek(SeekFrom::End(0))?;\n    if eof > current {\n        // Zero out rest of the file\n        fd.seek(SeekFrom::Start(current))?;\n        fd.write_zeros((eof - current) as usize)?;\n    }\n    Ok(())\n}\n\nfn boot_main(cmds: CmdArgs) -> LoggedResult<i32> {\n    let mut cmds = cmds.0;\n    if cmds.len() < 2 {\n        print_usage(cmds.first().unwrap_or(&\"magiskboot\"));\n        return log_err!();\n    }\n\n    if cmds[1].starts_with(\"--\") {\n        cmds[1] = &cmds[1][2..];\n    }\n\n    let cli = if cmds[1].starts_with(\"compress=\") {\n        // Skip the main parser, directly parse the subcommand\n        Compress::from_args(&cmds[..2], &cmds[2..]).map(|compress| Cli {\n            action: Action::Compress(compress),\n        })\n    } else {\n        Cli::from_args(&[cmds[0]], &cmds[1..])\n    }\n    .on_early_exit(|| match cmds[1] {\n        \"dtb\" => print_dtb_usage(),\n        \"cpio\" => print_cpio_usage(),\n        _ => print_usage(cmds[0]),\n    });\n\n    match cli.action {\n        Action::Unpack(Unpack {\n            no_decompress,\n            dump_header,\n            img,\n        }) => {\n            return Ok(unpack(&img, no_decompress, dump_header));\n        }\n        Action::Repack(Repack {\n            no_compress,\n            img,\n            out,\n        }) => {\n            repack(\n                &img,\n                out.as_deref().unwrap_or(cstr!(\"new-boot.img\")),\n                no_compress,\n            );\n        }\n        Action::Verify(Verify { img, cert }) => {\n            if !verify_cmd(&img, cert.as_deref()) {\n                return log_err!();\n            }\n        }\n        Action::Sign(Sign {\n            img,\n            name,\n            cert,\n            key,\n        }) => {\n            sign_cmd(&img, name.as_deref(), cert.as_deref(), key.as_deref())?;\n        }\n        Action::Extract(Extract {\n            payload,\n            partition,\n            outfile,\n        }) => {\n            extract_boot_from_payload(\n                &payload,\n                partition.as_ref().map(AsRef::as_ref),\n                outfile.as_ref().map(AsRef::as_ref),\n            )\n            .log_with_msg(|w| w.write_str(\"Failed to extract from payload\"))?;\n        }\n        Action::HexPatch(HexPatch { file, src, dest }) => {\n            if !hexpatch(&file, &src, &dest) {\n                log_err!(\"Failed to patch\")?;\n            }\n        }\n        Action::Cpio(Cpio { file, cmds }) => {\n            cpio_commands(&file, &cmds).log_with_msg(|w| w.write_str(\"Failed to process cpio\"))?;\n        }\n        Action::Dtb(Dtb { file, action }) => {\n            return dtb_commands(&file, &action)\n                .map(|b| if b { 0 } else { 1 })\n                .log_with_msg(|w| w.write_str(\"Failed to process dtb\"));\n        }\n        Action::Split(Split {\n            no_decompress,\n            file,\n        }) => {\n            return Ok(split_image_dtb(&file, no_decompress));\n        }\n        Action::Sha1(Sha1 { file }) => {\n            let file = MappedFile::open(&file)?;\n            let mut sha1 = [0u8; 20];\n            sha1_hash(file.as_ref(), &mut sha1);\n            for byte in &sha1 {\n                print!(\"{byte:02x}\");\n            }\n            println!();\n        }\n        Action::Cleanup(_) => {\n            eprintln!(\"Cleaning up...\");\n            cleanup();\n        }\n        Action::Decompress(Decompress { file, out }) => {\n            decompress_cmd(&file, out.as_deref())?;\n        }\n        Action::Compress(Compress { format, file, out }) => {\n            compress_cmd(format, &file, out.as_deref())?;\n        }\n    }\n    Ok(0)\n}\n\n#[unsafe(no_mangle)]\npub extern \"C\" fn main(argc: i32, argv: *const *const c_char, _envp: *const *const c_char) -> i32 {\n    cmdline_logging();\n    unsafe { umask(0) };\n    let cmds = CmdArgs::new(argc, argv);\n    boot_main(cmds).unwrap_or(1)\n}\n"
  },
  {
    "path": "native/src/boot/compress.rs",
    "content": "use crate::ffi::{FileFormat, check_fmt};\nuse base::nix::fcntl::OFlag;\nuse base::{Chunker, FileOrStd, LoggedResult, ReadExt, Utf8CStr, Utf8CString, WriteExt, log_err};\nuse bzip2::Compression as BzCompression;\nuse bzip2::read::BzDecoder;\nuse bzip2::write::BzEncoder;\nuse flate2::Compression as GzCompression;\nuse flate2::read::MultiGzDecoder;\nuse flate2::write::GzEncoder;\nuse lz4::block::CompressionMode;\nuse lz4::liblz4::BlockChecksum;\nuse lz4::{\n    BlockMode, BlockSize, ContentChecksum, Decoder as LZ4FrameDecoder, Encoder as LZ4FrameEncoder,\n    EncoderBuilder as LZ4FrameEncoderBuilder,\n};\nuse lzma_rust2::{CheckType, LzmaOptions, LzmaReader, LzmaWriter, XzOptions, XzReader, XzWriter};\nuse std::cmp::min;\nuse std::fmt::Write as FmtWrite;\nuse std::fs::File;\nuse std::io::{BufWriter, Cursor, Read, Write};\nuse std::mem::ManuallyDrop;\nuse std::num::NonZeroU64;\nuse std::ops::DerefMut;\nuse std::os::fd::{FromRawFd, RawFd};\nuse zopfli::{BlockType, GzipEncoder as ZopFliEncoder, Options as ZopfliOptions};\n\npub trait WriteFinish<W: Write>: Write {\n    fn finish(self: Box<Self>) -> std::io::Result<W>;\n}\n\n// Boilerplate for existing types\n\nmacro_rules! finish_impl {\n    ($($t:ty),*) => {$(\n        impl<W: Write> WriteFinish<W> for $t {\n            fn finish(self: Box<Self>) -> std::io::Result<W> {\n                Self::finish(*self)\n            }\n        }\n    )*}\n}\n\nfinish_impl!(GzEncoder<W>, BzEncoder<W>, XzWriter<W>, LzmaWriter<W>);\n\nimpl<W: Write> WriteFinish<W> for BufWriter<ZopFliEncoder<W>> {\n    fn finish(self: Box<Self>) -> std::io::Result<W> {\n        let inner = self.into_inner()?;\n        ZopFliEncoder::finish(inner)\n    }\n}\n\nimpl<W: Write> WriteFinish<W> for LZ4FrameEncoder<W> {\n    fn finish(self: Box<Self>) -> std::io::Result<W> {\n        let (w, r) = Self::finish(*self);\n        r?;\n        Ok(w)\n    }\n}\n\n// LZ4BlockArchive format\n//\n// len:  |   4   |          4            |           n           | ... |           4             |\n// data: | magic | compressed block size | compressed block data | ... | total uncompressed size |\n\n// LZ4BlockEncoder\n\nconst LZ4_BLOCK_SIZE: usize = 0x800000;\nconst LZ4HC_CLEVEL_MAX: i32 = 12;\nconst LZ4_MAGIC: u32 = 0x184c2102;\n\nstruct LZ4BlockEncoder<W: Write> {\n    write: W,\n    chunker: Chunker,\n    out_buf: Box<[u8]>,\n    total: u32,\n    is_lg: bool,\n}\n\nimpl<W: Write> LZ4BlockEncoder<W> {\n    fn new(write: W, is_lg: bool) -> Self {\n        let out_sz = lz4::block::compress_bound(LZ4_BLOCK_SIZE).unwrap_or(LZ4_BLOCK_SIZE);\n        LZ4BlockEncoder {\n            write,\n            chunker: Chunker::new(LZ4_BLOCK_SIZE),\n            // SAFETY: all bytes will be initialized before it is used\n            out_buf: unsafe { Box::new_uninit_slice(out_sz).assume_init() },\n            total: 0,\n            is_lg,\n        }\n    }\n\n    fn encode_block(write: &mut W, out_buf: &mut [u8], chunk: &[u8]) -> std::io::Result<()> {\n        let compressed_size = lz4::block::compress_to_buffer(\n            chunk,\n            Some(CompressionMode::HIGHCOMPRESSION(LZ4HC_CLEVEL_MAX)),\n            false,\n            out_buf,\n        )?;\n        let block_size = compressed_size as u32;\n        write.write_pod(&block_size)?;\n        write.write_all(&out_buf[..compressed_size])\n    }\n}\n\nimpl<W: Write> Write for LZ4BlockEncoder<W> {\n    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {\n        self.write_all(buf)?;\n        Ok(buf.len())\n    }\n\n    fn flush(&mut self) -> std::io::Result<()> {\n        Ok(())\n    }\n\n    fn write_all(&mut self, mut buf: &[u8]) -> std::io::Result<()> {\n        if self.total == 0 {\n            // Write header\n            self.write.write_pod(&LZ4_MAGIC)?;\n        }\n\n        self.total += buf.len() as u32;\n        while !buf.is_empty() {\n            let (b, chunk) = self.chunker.add_data(buf);\n            buf = b;\n            if let Some(chunk) = chunk {\n                Self::encode_block(&mut self.write, &mut self.out_buf, chunk)?;\n            }\n        }\n        Ok(())\n    }\n}\n\nimpl<W: Write> WriteFinish<W> for LZ4BlockEncoder<W> {\n    fn finish(mut self: Box<Self>) -> std::io::Result<W> {\n        let chunk = self.chunker.get_available();\n        if !chunk.is_empty() {\n            Self::encode_block(&mut self.write, &mut self.out_buf, chunk)?;\n        }\n        if self.is_lg {\n            self.write.write_pod(&self.total)?;\n        }\n        Ok(self.write)\n    }\n}\n\n// LZ4BlockDecoder\n\nstruct LZ4BlockDecoder<R: Read> {\n    read: R,\n    in_buf: Box<[u8]>,\n    out_buf: Box<[u8]>,\n    out_len: usize,\n    out_pos: usize,\n}\n\nimpl<R: Read> LZ4BlockDecoder<R> {\n    fn new(read: R) -> Self {\n        let compressed_sz = lz4::block::compress_bound(LZ4_BLOCK_SIZE).unwrap_or(LZ4_BLOCK_SIZE);\n        Self {\n            read,\n            in_buf: unsafe { Box::new_uninit_slice(compressed_sz).assume_init() },\n            out_buf: unsafe { Box::new_uninit_slice(LZ4_BLOCK_SIZE).assume_init() },\n            out_len: 0,\n            out_pos: 0,\n        }\n    }\n}\n\nimpl<R: Read> Read for LZ4BlockDecoder<R> {\n    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {\n        if self.out_pos == self.out_len {\n            let mut block_size: u32 = 0;\n            if let Err(e) = self.read.read_pod(&mut block_size) {\n                return if e.kind() == std::io::ErrorKind::UnexpectedEof {\n                    Ok(0)\n                } else {\n                    Err(e)\n                };\n            }\n            if block_size == LZ4_MAGIC {\n                self.read.read_pod(&mut block_size)?;\n            }\n\n            let block_size = block_size as usize;\n\n            if block_size > self.in_buf.len() {\n                // This may be the LG format trailer, EOF\n                return Ok(0);\n            }\n\n            // Read the entire compressed block\n            let compressed_block = &mut self.in_buf[..block_size];\n            if let Ok(len) = self.read.read(compressed_block) {\n                if len == 0 {\n                    // We hit EOF, that's fine\n                    return Ok(0);\n                } else if len != block_size {\n                    let remain = &mut compressed_block[len..];\n                    self.read.read_exact(remain)?;\n                }\n            }\n\n            self.out_len = lz4::block::decompress_to_buffer(\n                compressed_block,\n                Some(LZ4_BLOCK_SIZE as i32),\n                &mut self.out_buf,\n            )?;\n            self.out_pos = 0;\n        }\n        let copy_len = min(buf.len(), self.out_len - self.out_pos);\n        buf[..copy_len].copy_from_slice(&self.out_buf[self.out_pos..self.out_pos + copy_len]);\n        self.out_pos += copy_len;\n        Ok(copy_len)\n    }\n}\n\n// Top-level APIs\n\npub fn get_encoder<'a, W: Write + 'a>(\n    format: FileFormat,\n    w: W,\n) -> std::io::Result<Box<dyn WriteFinish<W> + 'a>> {\n    Ok(match format {\n        FileFormat::XZ => {\n            let mut opt = XzOptions::with_preset(9);\n            opt.set_check_sum_type(CheckType::Crc32);\n            Box::new(XzWriter::new(w, opt)?)\n        }\n        FileFormat::LZMA => Box::new(LzmaWriter::new_use_header(\n            w,\n            &LzmaOptions::with_preset(9),\n            None,\n        )?),\n        FileFormat::BZIP2 => Box::new(BzEncoder::new(w, BzCompression::best())),\n        FileFormat::LZ4 => {\n            let encoder = LZ4FrameEncoderBuilder::new()\n                .block_size(BlockSize::Max4MB)\n                .block_mode(BlockMode::Independent)\n                .checksum(ContentChecksum::ChecksumEnabled)\n                .block_checksum(BlockChecksum::BlockChecksumEnabled)\n                .level(9)\n                .auto_flush(true)\n                .build(w)?;\n            Box::new(encoder)\n        }\n        FileFormat::LZ4_LEGACY => Box::new(LZ4BlockEncoder::new(w, false)),\n        FileFormat::LZ4_LG => Box::new(LZ4BlockEncoder::new(w, true)),\n        FileFormat::ZOPFLI => {\n            // These options are already better than gzip -9\n            let opt = ZopfliOptions {\n                iteration_count: unsafe { NonZeroU64::new_unchecked(1) },\n                maximum_block_splits: 1,\n                ..Default::default()\n            };\n            Box::new(ZopFliEncoder::new_buffered(opt, BlockType::Dynamic, w)?)\n        }\n        FileFormat::GZIP => Box::new(GzEncoder::new(w, GzCompression::best())),\n        _ => unreachable!(),\n    })\n}\n\npub fn get_decoder<'a, R: Read + 'a>(\n    format: FileFormat,\n    r: R,\n) -> std::io::Result<Box<dyn Read + 'a>> {\n    Ok(match format {\n        FileFormat::XZ => Box::new(XzReader::new(r, true)),\n        FileFormat::LZMA => Box::new(LzmaReader::new_mem_limit(r, u32::MAX, None)?),\n        FileFormat::BZIP2 => Box::new(BzDecoder::new(r)),\n        FileFormat::LZ4 => Box::new(LZ4FrameDecoder::new(r)?),\n        FileFormat::LZ4_LG | FileFormat::LZ4_LEGACY => Box::new(LZ4BlockDecoder::new(r)),\n        FileFormat::ZOPFLI | FileFormat::GZIP => Box::new(MultiGzDecoder::new(r)),\n        _ => unreachable!(),\n    })\n}\n\n// C++ FFI\n\npub fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {\n    let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };\n\n    let _ = || -> LoggedResult<()> {\n        let mut encoder = get_encoder(format, out_file.deref_mut())?;\n        std::io::copy(&mut Cursor::new(in_bytes), encoder.deref_mut())?;\n        encoder.finish()?;\n        Ok(())\n    }();\n}\n\npub fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: RawFd) {\n    let mut out_file = unsafe { ManuallyDrop::new(File::from_raw_fd(out_fd)) };\n\n    let _ = || -> LoggedResult<()> {\n        let mut decoder = get_decoder(format, in_bytes)?;\n        std::io::copy(decoder.as_mut(), out_file.deref_mut())?;\n        Ok(())\n    }();\n}\n\n// Command-line entry points\n\npub(crate) fn decompress_cmd(infile: &Utf8CStr, outfile: Option<&Utf8CStr>) -> LoggedResult<()> {\n    let in_std = infile == \"-\";\n    let mut rm_in = false;\n\n    let mut buf = [0u8; 4096];\n\n    let input = if in_std {\n        FileOrStd::StdIn\n    } else {\n        FileOrStd::File(infile.open(OFlag::O_RDONLY)?)\n    };\n\n    // First read some bytes for format detection\n    let len = input.as_file().read(&mut buf)?;\n    let buf = &buf[..len];\n\n    let format = check_fmt(buf);\n\n    eprintln!(\"Detected format: {format}\");\n\n    if !format.is_compressed() {\n        return log_err!(\"Input file is not a supported type!\");\n    }\n\n    // If user did not provide outfile, infile has to be either\n    // <path>.[ext], or \"-\". Outfile will be either <path> or \"-\".\n    // If the input does not have proper format, abort.\n\n    let output = if let Some(outfile) = outfile {\n        if outfile == \"-\" {\n            FileOrStd::StdOut\n        } else {\n            FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)\n        }\n    } else if in_std {\n        FileOrStd::StdOut\n    } else {\n        // Strip out extension and remove input\n        let outfile = if let Some((outfile, ext)) = infile.rsplit_once('.')\n            && ext == format.ext()\n        {\n            Utf8CString::from(outfile)\n        } else {\n            return log_err!(\"Input file is not a supported type!\");\n        };\n\n        rm_in = true;\n        eprintln!(\"Decompressing to [{outfile}]\");\n        FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)\n    };\n\n    let mut decoder = get_decoder(format, Cursor::new(buf).chain(input.as_file()))?;\n    std::io::copy(decoder.as_mut(), &mut output.as_file())?;\n\n    if rm_in {\n        infile.remove()?;\n    }\n\n    Ok(())\n}\n\npub(crate) fn compress_cmd(\n    method: FileFormat,\n    infile: &Utf8CStr,\n    outfile: Option<&Utf8CStr>,\n) -> LoggedResult<()> {\n    let in_std = infile == \"-\";\n    let mut rm_in = false;\n\n    let input = if in_std {\n        FileOrStd::StdIn\n    } else {\n        FileOrStd::File(infile.open(OFlag::O_RDONLY)?)\n    };\n\n    let output = if let Some(outfile) = outfile {\n        if outfile == \"-\" {\n            FileOrStd::StdOut\n        } else {\n            FileOrStd::File(outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?)\n        }\n    } else if in_std {\n        FileOrStd::StdOut\n    } else {\n        let mut outfile = Utf8CString::default();\n        outfile.write_str(infile).ok();\n        outfile.write_char('.').ok();\n        outfile.write_str(method.ext()).ok();\n        eprintln!(\"Compressing to [{outfile}]\");\n        rm_in = true;\n        let outfile = outfile.create(OFlag::O_WRONLY | OFlag::O_TRUNC, 0o644)?;\n        FileOrStd::File(outfile)\n    };\n\n    let mut encoder = get_encoder(method, output.as_file())?;\n    std::io::copy(&mut input.as_file(), encoder.as_mut())?;\n    encoder.finish()?;\n\n    if rm_in {\n        infile.remove()?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "native/src/boot/cpio.rs",
    "content": "#![allow(clippy::useless_conversion)]\n\nuse argh::FromArgs;\nuse base::argh;\nuse bytemuck::{Pod, Zeroable, from_bytes};\nuse num_traits::cast::AsPrimitive;\nuse size::{Base, Size, Style};\nuse std::cmp::Ordering;\nuse std::collections::{BTreeMap, HashMap};\nuse std::fmt::{Display, Formatter};\nuse std::fs::File;\nuse std::io::{Cursor, Read, Write};\nuse std::mem::size_of;\nuse std::process::exit;\nuse std::str;\n\nuse crate::check_env;\nuse crate::compress::{get_decoder, get_encoder};\nuse crate::ffi::FileFormat;\nuse crate::patch::{patch_encryption, patch_verity};\nuse base::libc::{\n    S_IFBLK, S_IFCHR, S_IFDIR, S_IFLNK, S_IFMT, S_IFREG, S_IRGRP, S_IROTH, S_IRUSR, S_IWGRP,\n    S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR, dev_t, gid_t, major, makedev, minor, mknod,\n    mode_t, uid_t,\n};\nuse base::nix::fcntl::OFlag;\nuse base::{\n    BytesExt, EarlyExitExt, LoggedResult, MappedFile, OptionExt, ResultExt, Utf8CStr, Utf8CStrBuf,\n    WriteExt, cstr, log_err,\n};\n\n#[derive(FromArgs)]\nstruct CpioCommand {\n    #[argh(subcommand)]\n    action: CpioAction,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand)]\nenum CpioAction {\n    Test(Test),\n    Restore(Restore),\n    Patch(Patch),\n    Exists(Exists),\n    Backup(Backup),\n    Remove(Remove),\n    Move(Move),\n    Extract(Extract),\n    MakeDir(MakeDir),\n    Link(Link),\n    Add(Add),\n    List(List),\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"test\")]\nstruct Test {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"restore\")]\nstruct Restore {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"patch\")]\nstruct Patch {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"exists\")]\nstruct Exists {\n    #[argh(positional, arg_name = \"entry\")]\n    path: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"backup\")]\nstruct Backup {\n    #[argh(switch, short = 'n')]\n    skip_compress: bool,\n    #[argh(positional, arg_name = \"orig\")]\n    origin: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"rm\")]\nstruct Remove {\n    #[argh(switch, short = 'r')]\n    recursive: bool,\n    #[argh(positional, arg_name = \"entry\")]\n    path: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"mv\")]\nstruct Move {\n    #[argh(positional, arg_name = \"source\")]\n    from: String,\n    #[argh(positional, arg_name = \"dest\")]\n    to: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"extract\")]\nstruct Extract {\n    #[argh(positional, greedy)]\n    paths: Vec<String>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"mkdir\")]\nstruct MakeDir {\n    #[argh(positional, from_str_fn(parse_mode))]\n    mode: mode_t,\n    #[argh(positional, arg_name = \"entry\")]\n    dir: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"ln\")]\nstruct Link {\n    #[argh(positional, arg_name = \"entry\")]\n    src: String,\n    #[argh(positional, arg_name = \"target\")]\n    dst: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"add\")]\nstruct Add {\n    #[argh(positional, from_str_fn(parse_mode))]\n    mode: mode_t,\n    #[argh(positional, arg_name = \"entry\")]\n    path: String,\n    #[argh(positional, arg_name = \"infile\")]\n    file: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"ls\")]\nstruct List {\n    #[argh(switch, short = 'r')]\n    recursive: bool,\n    #[argh(positional, default = r#\"String::from(\"/\")\"#)]\n    path: String,\n}\n\npub(crate) fn print_cpio_usage() {\n    eprintln!(\n        r#\"Usage: magiskboot cpio <incpio> [commands...]\n\nDo cpio commands to <incpio> (modifications are done in-place).\nEach command is a single argument; add quotes for each command.\n\nSupported commands:\n  exists ENTRY\n    Return 0 if ENTRY exists, else return 1\n  ls [-r] [PATH]\n    List PATH (\"/\" by default); specify [-r] to list recursively\n  rm [-r] ENTRY\n    Remove ENTRY, specify [-r] to remove recursively\n  mkdir MODE ENTRY\n    Create directory ENTRY with permissions MODE\n  ln TARGET ENTRY\n    Create a symlink to TARGET with the name ENTRY\n  mv SOURCE DEST\n    Move SOURCE to DEST\n  add MODE ENTRY INFILE\n    Add INFILE as ENTRY with permissions MODE; replaces ENTRY if exists\n  extract [ENTRY OUT]\n    Extract ENTRY to OUT, or extract all entries to current directory\n  test\n    Test the cpio's status. Return values:\n    0:stock    1:Magisk    2:unsupported\n  patch\n    Apply ramdisk patches\n    Configure with env variables: KEEPVERITY KEEPFORCEENCRYPT\n  backup ORIG [-n]\n    Create ramdisk backups from ORIG, specify [-n] to skip compression\n  restore\n    Restore ramdisk from ramdisk backup stored within incpio\n\"#\n    )\n}\n\n#[derive(Copy, Clone, Pod, Zeroable)]\n#[repr(C, packed)]\nstruct CpioHeader {\n    magic: [u8; 6],\n    ino: [u8; 8],\n    mode: [u8; 8],\n    uid: [u8; 8],\n    gid: [u8; 8],\n    nlink: [u8; 8],\n    mtime: [u8; 8],\n    filesize: [u8; 8],\n    devmajor: [u8; 8],\n    devminor: [u8; 8],\n    rdevmajor: [u8; 8],\n    rdevminor: [u8; 8],\n    namesize: [u8; 8],\n    check: [u8; 8],\n}\n\nstruct Cpio {\n    entries: BTreeMap<String, Box<CpioEntry>>,\n}\n\nstruct CpioEntry {\n    mode: mode_t,\n    uid: uid_t,\n    gid: gid_t,\n    rdevmajor: dev_t,\n    rdevminor: dev_t,\n    data: Vec<u8>,\n}\n\nimpl Cpio {\n    fn new() -> Self {\n        Self {\n            entries: BTreeMap::new(),\n        }\n    }\n\n    fn load_from_data(data: &[u8]) -> LoggedResult<Self> {\n        let mut cpio = Cpio::new();\n        let mut pos = 0_usize;\n        while pos < data.len() {\n            let hdr_sz = size_of::<CpioHeader>();\n            let hdr = from_bytes::<CpioHeader>(&data[pos..(pos + hdr_sz)]);\n            if &hdr.magic != b\"070701\" {\n                return log_err!(\"invalid cpio magic\");\n            }\n            pos += hdr_sz;\n            let name_sz = x8u(&hdr.namesize)? as usize;\n            let name = Utf8CStr::from_bytes(&data[pos..(pos + name_sz)])?.to_string();\n            pos += name_sz;\n            pos = align_4(pos);\n            if name == \".\" || name == \"..\" {\n                continue;\n            }\n            if name == \"TRAILER!!!\" {\n                match data[pos..].find(b\"070701\") {\n                    Some(x) => pos += x,\n                    None => break,\n                }\n                continue;\n            }\n            let file_sz = x8u(&hdr.filesize)? as usize;\n            let entry = Box::new(CpioEntry {\n                mode: x8u(&hdr.mode)?.as_(),\n                uid: x8u(&hdr.uid)?.as_(),\n                gid: x8u(&hdr.gid)?.as_(),\n                rdevmajor: x8u(&hdr.rdevmajor)?.as_(),\n                rdevminor: x8u(&hdr.rdevminor)?.as_(),\n                data: data[pos..(pos + file_sz)].to_vec(),\n            });\n            pos += file_sz;\n            cpio.entries.insert(name, entry);\n            pos = align_4(pos);\n        }\n        Ok(cpio)\n    }\n\n    fn load_from_file(path: &Utf8CStr) -> LoggedResult<Self> {\n        eprintln!(\"Loading cpio: [{path}]\");\n        let file = MappedFile::open(path)?;\n        Self::load_from_data(file.as_ref())\n    }\n\n    fn dump(&self, path: &str) -> LoggedResult<()> {\n        eprintln!(\"Dumping cpio: [{path}]\");\n        let mut file = File::create(path)?;\n        let mut pos = 0usize;\n        let mut inode = 300000i64;\n        for (name, entry) in &self.entries {\n            pos += file.write(\n                format!(\n                    \"070701{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}\",\n                    inode,\n                    entry.mode,\n                    entry.uid,\n                    entry.gid,\n                    1,\n                    0,\n                    entry.data.len(),\n                    0,\n                    0,\n                    entry.rdevmajor,\n                    entry.rdevminor,\n                    name.len() + 1,\n                    0\n                ).as_bytes(),\n            )?;\n            pos += file.write(name.as_bytes())?;\n            pos += file.write(&[0])?;\n            file.write_zeros(align_4(pos) - pos)?;\n            pos = align_4(pos);\n            pos += file.write(&entry.data)?;\n            file.write_zeros(align_4(pos) - pos)?;\n            pos = align_4(pos);\n            inode += 1;\n        }\n        pos += file.write(\n            format!(\"070701{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}{:08x}\",\n                    inode, 0o755, 0, 0, 1, 0, 0, 0, 0, 0, 0, 11, 0\n            ).as_bytes()\n        )?;\n        pos += file.write(\"TRAILER!!!\\0\".as_bytes())?;\n        file.write_zeros(align_4(pos) - pos)?;\n        Ok(())\n    }\n\n    fn rm(&mut self, path: &str, recursive: bool) {\n        let path = norm_path(path);\n        if self.entries.remove(&path).is_some() {\n            eprintln!(\"Removed entry [{path}]\");\n        }\n        if recursive {\n            let path = path + \"/\";\n            self.entries.retain(|k, _| {\n                if k.starts_with(&path) {\n                    eprintln!(\"Removed entry [{k}]\");\n                    false\n                } else {\n                    true\n                }\n            })\n        }\n    }\n\n    fn extract_entry(&self, path: &str, out: &mut String) -> LoggedResult<()> {\n        let entry = self\n            .entries\n            .get(path)\n            .ok_or_log_msg(|w| w.write_str(\"No such file\"))?;\n        eprintln!(\"Extracting entry [{path}] to [{out}]\");\n\n        let out = Utf8CStr::from_string(out);\n\n        let mut buf = cstr::buf::default();\n\n        // Make sure its parent directories exist\n        if let Some(dir) = out.parent_dir() {\n            buf.push_str(dir);\n            buf.mkdirs(0o755)?;\n        }\n\n        let mode: mode_t = (entry.mode & 0o777).into();\n\n        match entry.mode & S_IFMT {\n            S_IFDIR => out.mkdir(mode)?,\n            S_IFREG => {\n                let mut file = out.create(\n                    OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_WRONLY | OFlag::O_CLOEXEC,\n                    mode,\n                )?;\n                file.write_all(&entry.data)?;\n            }\n            S_IFLNK => {\n                buf.clear();\n                buf.push_str(str::from_utf8(entry.data.as_slice())?);\n                out.create_symlink_to(&buf)?;\n            }\n            S_IFBLK | S_IFCHR => {\n                let dev = makedev(entry.rdevmajor.try_into()?, entry.rdevminor.try_into()?);\n                unsafe { mknod(out.as_ptr().cast(), entry.mode, dev) };\n            }\n            _ => {\n                return log_err!(\"unknown entry type\");\n            }\n        }\n        Ok(())\n    }\n\n    fn extract(&self, path: Option<&mut String>, out: Option<&mut String>) -> LoggedResult<()> {\n        let path = path.map(|s| norm_path(s.as_str()));\n        if let (Some(path), Some(out)) = (&path, out) {\n            return self.extract_entry(path, out);\n        } else {\n            for path in self.entries.keys() {\n                if path == \".\" || path == \"..\" {\n                    continue;\n                }\n                self.extract_entry(path, &mut path.clone())?;\n            }\n        }\n        Ok(())\n    }\n\n    fn exists(&self, path: &str) -> bool {\n        self.entries.contains_key(&norm_path(path))\n    }\n\n    fn add(&mut self, mode: mode_t, path: &str, file: &mut String) -> LoggedResult<()> {\n        if path.ends_with('/') {\n            return log_err!(\"path cannot end with / for add\");\n        }\n        let file = Utf8CStr::from_string(file);\n        let attr = file.get_attr()?;\n\n        let mut content = Vec::<u8>::new();\n        let rdevmajor: dev_t;\n        let rdevminor: dev_t;\n\n        // Treat symlinks as regular files as symlinks are created by the 'ln TARGET ENTRY' command\n        let mode = if attr.is_file() || attr.is_symlink() {\n            rdevmajor = 0;\n            rdevminor = 0;\n            file.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?\n                .read_to_end(&mut content)?;\n            mode | S_IFREG\n        } else {\n            rdevmajor = major(attr.st.st_rdev.as_()).as_();\n            rdevminor = minor(attr.st.st_rdev.as_()).as_();\n            if attr.is_block_device() {\n                mode | S_IFBLK\n            } else if attr.is_char_device() {\n                mode | S_IFCHR\n            } else {\n                return log_err!(\"unsupported file type\");\n            }\n        };\n\n        self.entries.insert(\n            norm_path(path),\n            Box::new(CpioEntry {\n                mode,\n                uid: 0,\n                gid: 0,\n                rdevmajor,\n                rdevminor,\n                data: content,\n            }),\n        );\n        eprintln!(\"Add file [{path}] ({mode:04o})\");\n        Ok(())\n    }\n\n    fn mkdir(&mut self, mode: mode_t, dir: &str) {\n        self.entries.insert(\n            norm_path(dir),\n            Box::new(CpioEntry {\n                mode: mode | S_IFDIR,\n                uid: 0,\n                gid: 0,\n                rdevmajor: 0,\n                rdevminor: 0,\n                data: vec![],\n            }),\n        );\n        eprintln!(\"Create directory [{dir}] ({mode:04o})\");\n    }\n\n    fn ln(&mut self, src: &str, dst: &str) {\n        self.entries.insert(\n            norm_path(dst),\n            Box::new(CpioEntry {\n                mode: S_IFLNK,\n                uid: 0,\n                gid: 0,\n                rdevmajor: 0,\n                rdevminor: 0,\n                data: norm_path(src).as_bytes().to_vec(),\n            }),\n        );\n        eprintln!(\"Create symlink [{dst}] -> [{src}]\");\n    }\n\n    fn mv(&mut self, from: &str, to: &str) -> LoggedResult<()> {\n        let entry = self\n            .entries\n            .remove(&norm_path(from))\n            .ok_or_log_msg(|w| w.write_fmt(format_args!(\"No such entry {from}\")))?;\n        self.entries.insert(norm_path(to), entry);\n        eprintln!(\"Move [{from}] -> [{to}]\");\n        Ok(())\n    }\n\n    fn ls(&self, path: &str, recursive: bool) {\n        let path = norm_path(path);\n        let path = if path.is_empty() {\n            path\n        } else {\n            \"/\".to_string() + path.as_str()\n        };\n        for (name, entry) in &self.entries {\n            let p = \"/\".to_string() + name.as_str();\n            let Some(p) = p.strip_prefix(&path) else {\n                continue;\n            };\n            if !p.is_empty() && !p.starts_with('/') {\n                continue;\n            }\n            if !recursive && !p.is_empty() && p.matches('/').count() > 1 {\n                continue;\n            }\n            println!(\"{entry}\\t{name}\");\n        }\n    }\n}\n\nconst MAGISK_PATCHED: i32 = 1 << 0;\nconst UNSUPPORTED_CPIO: i32 = 1 << 1;\n\nimpl Cpio {\n    fn patch(&mut self) {\n        let keep_verity = check_env(\"KEEPVERITY\");\n        let keep_force_encrypt = check_env(\"KEEPFORCEENCRYPT\");\n        eprintln!(\n            \"Patch with flag KEEPVERITY=[{keep_verity}] KEEPFORCEENCRYPT=[{keep_force_encrypt}]\"\n        );\n        self.entries.retain(|name, entry| {\n            let fstab = (!keep_verity || !keep_force_encrypt)\n                && entry.mode & S_IFMT == S_IFREG\n                && !name.starts_with(\".backup\")\n                && !name.starts_with(\"twrp\")\n                && !name.starts_with(\"recovery\")\n                && name.starts_with(\"fstab\");\n            if !keep_verity {\n                if fstab {\n                    eprintln!(\"Found fstab file [{name}]\");\n                    let len = patch_verity(entry.data.as_mut_slice());\n                    if len != entry.data.len() {\n                        entry.data.resize(len, 0);\n                    }\n                } else if name == \"verity_key\" {\n                    return false;\n                }\n            }\n            if !keep_force_encrypt && fstab {\n                let len = patch_encryption(entry.data.as_mut_slice());\n                if len != entry.data.len() {\n                    entry.data.resize(len, 0);\n                }\n            }\n            true\n        });\n    }\n\n    fn test(&self) -> i32 {\n        for file in [\n            \"sbin/launch_daemonsu.sh\",\n            \"sbin/su\",\n            \"init.xposed.rc\",\n            \"boot/sbin/launch_daemonsu.sh\",\n        ] {\n            if self.exists(file) {\n                return UNSUPPORTED_CPIO;\n            }\n        }\n        for file in [\n            \".backup/.magisk\",\n            \"init.magisk.rc\",\n            \"overlay/init.magisk.rc\",\n        ] {\n            if self.exists(file) {\n                return MAGISK_PATCHED;\n            }\n        }\n        0\n    }\n\n    fn restore(&mut self) -> LoggedResult<()> {\n        let mut backups = HashMap::<String, Box<CpioEntry>>::new();\n        let mut rm_list = String::new();\n        self.entries\n            .extract_if(.., |name, _| name.starts_with(\".backup/\"))\n            .for_each(|(name, mut entry)| {\n                if name == \".backup/.rmlist\" {\n                    if let Ok(data) = str::from_utf8(&entry.data) {\n                        rm_list.push_str(data);\n                    }\n                } else if name != \".backup/.magisk\" {\n                    let new_name = if name.ends_with(\".xz\") && entry.decompress() {\n                        &name[8..name.len() - 3]\n                    } else {\n                        &name[8..]\n                    };\n                    eprintln!(\"Restore [{name}] -> [{new_name}]\");\n                    backups.insert(new_name.to_string(), entry);\n                }\n            });\n        self.rm(\".backup\", false);\n        if rm_list.is_empty() && backups.is_empty() {\n            self.entries.clear();\n            return Ok(());\n        }\n        for rm in rm_list.split('\\0') {\n            if !rm.is_empty() {\n                self.rm(rm, false);\n            }\n        }\n        self.entries.extend(backups);\n\n        Ok(())\n    }\n\n    fn backup(&mut self, origin: &mut String, skip_compress: bool) -> LoggedResult<()> {\n        let mut backups = HashMap::<String, Box<CpioEntry>>::new();\n        let mut rm_list = String::new();\n        backups.insert(\n            \".backup\".to_string(),\n            Box::new(CpioEntry {\n                mode: S_IFDIR,\n                uid: 0,\n                gid: 0,\n                rdevmajor: 0,\n                rdevminor: 0,\n                data: vec![],\n            }),\n        );\n        let origin = Utf8CStr::from_string(origin);\n        let mut o = Cpio::load_from_file(origin)?;\n        o.rm(\".backup\", true);\n        self.rm(\".backup\", true);\n\n        let mut left_iter = o.entries.into_iter();\n        let mut right_iter = self.entries.iter();\n\n        let mut lhs = left_iter.next();\n        let mut rhs = right_iter.next();\n\n        loop {\n            enum Action<'a> {\n                Backup(String, Box<CpioEntry>),\n                Record(&'a String),\n                Noop,\n            }\n\n            // Move the iterator forward if needed\n            if lhs.is_none() {\n                lhs = left_iter.next();\n            }\n            if rhs.is_none() {\n                rhs = right_iter.next();\n            }\n\n            let action = match (lhs.take(), rhs.take()) {\n                (Some((ln, le)), Some((rn, re))) => match ln.as_str().cmp(rn.as_str()) {\n                    Ordering::Less => {\n                        // Put rhs back\n                        rhs = Some((rn, re));\n                        Action::Backup(ln, le)\n                    }\n                    Ordering::Greater => {\n                        // Put lhs back\n                        lhs = Some((ln, le));\n                        Action::Record(rn)\n                    }\n                    Ordering::Equal => {\n                        if re.data != le.data {\n                            Action::Backup(ln, le)\n                        } else {\n                            Action::Noop\n                        }\n                    }\n                },\n                (Some((ln, le)), None) => Action::Backup(ln, le),\n                (None, Some((rn, _))) => Action::Record(rn),\n                (None, None) => break,\n            };\n            match action {\n                Action::Backup(name, mut entry) => {\n                    let backup = if !skip_compress && entry.compress() {\n                        format!(\".backup/{name}.xz\")\n                    } else {\n                        format!(\".backup/{name}\")\n                    };\n                    eprintln!(\"Backup [{name}] -> [{backup}]\");\n                    backups.insert(backup, entry);\n                }\n                Action::Record(name) => {\n                    eprintln!(\"Record new entry: [{name}] -> [.backup/.rmlist]\");\n                    rm_list.push_str(&format!(\"{name}\\0\"));\n                }\n                Action::Noop => {}\n            }\n        }\n        if !rm_list.is_empty() {\n            backups.insert(\n                \".backup/.rmlist\".to_string(),\n                Box::new(CpioEntry {\n                    mode: S_IFREG,\n                    uid: 0,\n                    gid: 0,\n                    rdevmajor: 0,\n                    rdevminor: 0,\n                    data: rm_list.as_bytes().to_vec(),\n                }),\n            );\n        }\n        self.entries.extend(backups);\n\n        Ok(())\n    }\n}\n\nimpl CpioEntry {\n    pub(crate) fn compress(&mut self) -> bool {\n        if self.mode & S_IFMT != S_IFREG {\n            return false;\n        }\n        let Ok(data) = || -> std::io::Result<Vec<u8>> {\n            let mut encoder = get_encoder(FileFormat::XZ, Vec::new())?;\n            encoder.write_all(&self.data)?;\n            encoder.finish()\n        }() else {\n            eprintln!(\"xz compression failed\");\n            return false;\n        };\n\n        self.data = data;\n        true\n    }\n\n    pub(crate) fn decompress(&mut self) -> bool {\n        if self.mode & S_IFMT != S_IFREG {\n            return false;\n        }\n\n        let Ok(data) = || -> std::io::Result<Vec<u8>> {\n            let mut decoder = get_decoder(FileFormat::XZ, Cursor::new(&self.data))?;\n            let mut data = Vec::new();\n            std::io::copy(decoder.as_mut(), &mut data)?;\n            Ok(data)\n        }() else {\n            eprintln!(\"xz compression failed\");\n            return false;\n        };\n\n        self.data = data;\n        true\n    }\n}\n\nimpl Display for CpioEntry {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        write!(\n            f,\n            \"{}{}{}{}{}{}{}{}{}{}\\t{}\\t{}\\t{}\\t{}:{}\",\n            match self.mode & S_IFMT {\n                S_IFDIR => \"d\",\n                S_IFREG => \"-\",\n                S_IFLNK => \"l\",\n                S_IFBLK => \"b\",\n                S_IFCHR => \"c\",\n                _ => \"?\",\n            },\n            if self.mode & S_IRUSR != 0 { \"r\" } else { \"-\" },\n            if self.mode & S_IWUSR != 0 { \"w\" } else { \"-\" },\n            if self.mode & S_IXUSR != 0 { \"x\" } else { \"-\" },\n            if self.mode & S_IRGRP != 0 { \"r\" } else { \"-\" },\n            if self.mode & S_IWGRP != 0 { \"w\" } else { \"-\" },\n            if self.mode & S_IXGRP != 0 { \"x\" } else { \"-\" },\n            if self.mode & S_IROTH != 0 { \"r\" } else { \"-\" },\n            if self.mode & S_IWOTH != 0 { \"w\" } else { \"-\" },\n            if self.mode & S_IXOTH != 0 { \"x\" } else { \"-\" },\n            self.uid,\n            self.gid,\n            Size::from_bytes(self.data.len())\n                .format()\n                .with_style(Style::Abbreviated)\n                .with_base(Base::Base10),\n            self.rdevmajor,\n            self.rdevminor,\n        )\n    }\n}\n\npub(crate) fn cpio_commands(file: &Utf8CStr, cmds: &Vec<String>) -> LoggedResult<()> {\n    let mut cpio = if file.exists() {\n        Cpio::load_from_file(file)?\n    } else {\n        Cpio::new()\n    };\n\n    for cmd in cmds {\n        if cmd.starts_with('#') {\n            continue;\n        }\n        let mut cmd = CpioCommand::from_args(\n            &[\"magiskboot\", \"cpio\", file],\n            cmd.split(' ')\n                .filter(|x| !x.is_empty())\n                .collect::<Vec<_>>()\n                .as_slice(),\n        )\n        .on_early_exit(print_cpio_usage);\n\n        match &mut cmd.action {\n            CpioAction::Test(_) => exit(cpio.test()),\n            CpioAction::Restore(_) => cpio.restore()?,\n            CpioAction::Patch(_) => cpio.patch(),\n            CpioAction::Exists(Exists { path }) => {\n                return if cpio.exists(path) {\n                    Ok(())\n                } else {\n                    log_err!()\n                };\n            }\n            CpioAction::Backup(Backup {\n                origin,\n                skip_compress,\n            }) => cpio.backup(origin, *skip_compress)?,\n            CpioAction::Remove(Remove { path, recursive }) => cpio.rm(path, *recursive),\n            CpioAction::Move(Move { from, to }) => cpio.mv(from, to)?,\n            CpioAction::MakeDir(MakeDir { mode, dir }) => cpio.mkdir(*mode, dir),\n            CpioAction::Link(Link { src, dst }) => cpio.ln(src, dst),\n            CpioAction::Add(Add { mode, path, file }) => cpio.add(*mode, path, file)?,\n            CpioAction::Extract(Extract { paths }) => {\n                if !paths.is_empty() && paths.len() != 2 {\n                    log_err!(\"invalid arguments\")?;\n                }\n                let mut it = paths.iter_mut();\n                cpio.extract(it.next(), it.next())?;\n            }\n            CpioAction::List(List { path, recursive }) => {\n                cpio.ls(path.as_str(), *recursive);\n                return Ok(());\n            }\n        };\n    }\n    cpio.dump(file)?;\n    Ok(())\n}\n\nfn x8u(x: &[u8; 8]) -> LoggedResult<u32> {\n    // parse hex\n    let mut ret = 0u32;\n    let s = str::from_utf8(x).log_with_msg(|w| w.write_str(\"bad cpio header\"))?;\n    for c in s.chars() {\n        ret = ret * 16\n            + c.to_digit(16)\n                .ok_or_log_msg(|w| w.write_str(\"bad cpio header\"))?;\n    }\n    Ok(ret)\n}\n\n#[inline(always)]\nfn align_4(x: usize) -> usize {\n    (x + 3) & !3\n}\n\n#[inline(always)]\nfn norm_path(path: &str) -> String {\n    path.split('/')\n        .filter(|x| !x.is_empty())\n        .intersperse(\"/\")\n        .collect()\n}\n\nfn parse_mode(s: &str) -> Result<mode_t, String> {\n    mode_t::from_str_radix(s, 8).map_err(|e| e.to_string())\n}\n"
  },
  {
    "path": "native/src/boot/dtb.rs",
    "content": "use argh::FromArgs;\nuse base::{LoggedResult, MappedFile, Utf8CStr, argh};\nuse fdt::node::{FdtNode, NodeProperty};\nuse fdt::{Fdt, FdtError};\nuse std::cell::UnsafeCell;\n\nuse crate::check_env;\nuse crate::patch::patch_verity;\n\n#[derive(FromArgs)]\n#[argh(subcommand)]\npub(crate) enum DtbAction {\n    Print(Print),\n    Patch(Patch),\n    Test(Test),\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"print\")]\npub(crate) struct Print {\n    #[argh(switch, short = 'f', long = none)]\n    fstab: bool,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"patch\")]\npub(crate) struct Patch {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"test\")]\npub(crate) struct Test {}\n\npub(crate) fn print_dtb_usage() {\n    eprintln!(\n        r#\"Usage: magiskboot dtb <file> <action> [args...]\nDo dtb related actions to <file>.\n\nSupported actions:\n  print [-f]\n    Print all contents of dtb for debugging\n    Specify [-f] to only print fstab nodes\n  patch\n    Search for fstab and remove verity/avb\n    Modifications are done directly to the file in-place\n    Configure with env variables: KEEPVERITY\n  test\n    Test the fstab's status\n    Return values:\n    0:valid    1:error\"#\n    );\n}\n\nconst MAX_PRINT_LEN: usize = 32;\n\nfn print_node(node: &FdtNode) {\n    fn pretty_node(depth_set: &[bool]) {\n        let mut depth_set = depth_set.iter().peekable();\n        while let Some(depth) = depth_set.next() {\n            let last = depth_set.peek().is_none();\n            if *depth {\n                if last {\n                    print!(\"├── \");\n                } else {\n                    print!(\"│   \");\n                }\n            } else if last {\n                print!(\"└── \");\n            } else {\n                print!(\"    \");\n            }\n        }\n    }\n\n    fn pretty_prop(depth_set: &[bool]) {\n        let mut depth_set = depth_set.iter().peekable();\n        while let Some(depth) = depth_set.next() {\n            let last = depth_set.peek().is_none();\n            if *depth {\n                if last {\n                    print!(\"│  \");\n                } else {\n                    print!(\"│   \");\n                }\n            } else if last {\n                print!(\"└─ \");\n            } else {\n                print!(\"    \");\n            }\n        }\n    }\n\n    fn do_print_node(node: &FdtNode, depth_set: &mut Vec<bool>) {\n        pretty_node(depth_set);\n        let depth = depth_set.len();\n        depth_set.push(true);\n        println!(\"{}\", node.name);\n        let mut properties = node.properties().peekable();\n        let mut children = node.children().peekable();\n        while let Some(NodeProperty { name, value }) = properties.next() {\n            let size = value.len();\n            let is_str = !(size > 1 && value[0] == 0)\n                && matches!(value.last(), Some(0u8) | None)\n                && value.iter().all(|c| *c == 0 || (*c >= 32 && *c < 127));\n\n            if depth_set[depth] && properties.peek().is_none() && children.peek().is_none() {\n                depth_set[depth] = false;\n            }\n\n            pretty_prop(depth_set);\n            if is_str {\n                println!(\n                    \"[{}]: [\\\"{}\\\"]\",\n                    name,\n                    if value.is_empty() {\n                        \"\"\n                    } else {\n                        unsafe { Utf8CStr::from_bytes_unchecked(value) }\n                    }\n                );\n            } else if size > MAX_PRINT_LEN {\n                println!(\"[{name}]: <bytes>({size})\");\n            } else {\n                println!(\"[{name}]: {value:02x?}\");\n            }\n        }\n\n        while let Some(child) = children.next() {\n            if depth_set[depth] && children.peek().is_none() {\n                depth_set[depth] = false;\n            }\n            do_print_node(&child, depth_set);\n        }\n        depth_set.pop();\n    }\n\n    do_print_node(node, &mut vec![]);\n}\n\nfn for_each_fdt<F: FnMut(usize, Fdt) -> LoggedResult<()>>(\n    file: &Utf8CStr,\n    rw: bool,\n    mut f: F,\n) -> LoggedResult<()> {\n    eprintln!(\"Loading dtbs from [{file}]\");\n    let file = if rw {\n        MappedFile::open_rw(file)?\n    } else {\n        MappedFile::open(file)?\n    };\n    let mut buf = Some(file.as_ref());\n    let mut dtb_num = 0usize;\n    while let Some(slice) = buf {\n        let slice = if let Some(pos) = slice.windows(4).position(|w| w == b\"\\xd0\\x0d\\xfe\\xed\") {\n            &slice[pos..]\n        } else {\n            break;\n        };\n        if slice.len() < 40 {\n            break;\n        }\n        let fdt = match Fdt::new(slice) {\n            Err(FdtError::BufferTooSmall) => {\n                eprintln!(\"dtb.{dtb_num:04} is truncated\");\n                break;\n            }\n            Ok(fdt) => fdt,\n            e => e?,\n        };\n\n        let size = fdt.total_size();\n\n        f(dtb_num, fdt)?;\n\n        dtb_num += 1;\n        buf = Some(&slice[size..]);\n    }\n    Ok(())\n}\n\nfn find_fstab<'b, 'a: 'b>(fdt: &'b Fdt<'a>) -> Option<FdtNode<'b, 'a>> {\n    fdt.all_nodes().find(|node| node.name == \"fstab\")\n}\n\nfn dtb_print(file: &Utf8CStr, fstab: bool) -> LoggedResult<()> {\n    for_each_fdt(file, false, |n, fdt| {\n        if fstab {\n            if let Some(fstab) = find_fstab(&fdt) {\n                eprintln!(\"Found fstab in dtb.{n:04}\");\n                print_node(&fstab);\n            }\n        } else if let Some(mut root) = fdt.find_node(\"/\") {\n            eprintln!(\"Printing dtb.{n:04}\");\n            if root.name.is_empty() {\n                root.name = \"/\";\n            }\n            print_node(&root);\n        }\n        Ok(())\n    })\n}\n\nfn dtb_test(file: &Utf8CStr) -> LoggedResult<bool> {\n    let mut ret = true;\n    for_each_fdt(file, false, |_, fdt| {\n        if let Some(fstab) = find_fstab(&fdt) {\n            for child in fstab.children() {\n                if child.name != \"system\" {\n                    continue;\n                }\n                if let Some(mount_point) = child.property(\"mnt_point\")\n                    && mount_point.value == b\"/system_root\\0\"\n                {\n                    ret = false;\n                    break;\n                }\n            }\n        }\n        Ok(())\n    })?;\n    Ok(ret)\n}\n\nfn dtb_patch(file: &Utf8CStr) -> LoggedResult<bool> {\n    let keep_verity = check_env(\"KEEPVERITY\");\n    let mut patched = false;\n    for_each_fdt(file, true, |n, fdt| {\n        for node in fdt.all_nodes() {\n            if node.name != \"chosen\" {\n                continue;\n            }\n            if let Some(boot_args) = node.property(\"bootargs\") {\n                boot_args.value.windows(14).for_each(|w| {\n                    if w == b\"skip_initramfs\" {\n                        let w = unsafe {\n                            &mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(w).get()\n                        };\n                        w[..=4].copy_from_slice(b\"want\");\n                        eprintln!(\"Patch [skip_initramfs] -> [want_initramfs] in dtb.{n:04}\");\n                        patched = true;\n                    }\n                });\n            }\n        }\n        if keep_verity {\n            return Ok(());\n        }\n        if let Some(fstab) = find_fstab(&fdt) {\n            for child in fstab.children() {\n                if let Some(flags) = child.property(\"fsmgr_flags\") {\n                    let flags = unsafe {\n                        &mut *std::mem::transmute::<&[u8], &UnsafeCell<[u8]>>(flags.value).get()\n                    };\n                    if patch_verity(flags) != flags.len() {\n                        patched = true;\n                    }\n                }\n            }\n        }\n        Ok(())\n    })?;\n    Ok(patched)\n}\n\npub(crate) fn dtb_commands(file: &Utf8CStr, action: &DtbAction) -> LoggedResult<bool> {\n    match action {\n        DtbAction::Print(Print { fstab }) => {\n            dtb_print(file, *fstab)?;\n            Ok(true)\n        }\n        DtbAction::Test(_) => Ok(dtb_test(file)?),\n        DtbAction::Patch(_) => Ok(dtb_patch(file)?),\n    }\n}\n"
  },
  {
    "path": "native/src/boot/format.rs",
    "content": "use crate::ffi::FileFormat;\nuse base::{Utf8CStr, cstr, libc};\nuse std::fmt::{Display, Formatter};\nuse std::str::FromStr;\n\nimpl FromStr for FileFormat {\n    type Err = ();\n\n    fn from_str(s: &str) -> Result<Self, Self::Err> {\n        match s {\n            \"gzip\" => Ok(Self::GZIP),\n            \"zopfli\" => Ok(Self::ZOPFLI),\n            \"xz\" => Ok(Self::XZ),\n            \"lzma\" => Ok(Self::LZMA),\n            \"bzip2\" => Ok(Self::BZIP2),\n            \"lz4\" => Ok(Self::LZ4),\n            \"lz4_legacy\" => Ok(Self::LZ4_LEGACY),\n            \"lz4_lg\" => Ok(Self::LZ4_LG),\n            _ => Err(()),\n        }\n    }\n}\n\nimpl Display for FileFormat {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        f.write_str(self.as_cstr())\n    }\n}\n\nimpl FileFormat {\n    fn as_cstr(&self) -> &'static Utf8CStr {\n        match *self {\n            Self::GZIP => cstr!(\"gzip\"),\n            Self::ZOPFLI => cstr!(\"zopfli\"),\n            Self::LZOP => cstr!(\"lzop\"),\n            Self::XZ => cstr!(\"xz\"),\n            Self::LZMA => cstr!(\"lzma\"),\n            Self::BZIP2 => cstr!(\"bzip2\"),\n            Self::LZ4 => cstr!(\"lz4\"),\n            Self::LZ4_LEGACY => cstr!(\"lz4_legacy\"),\n            Self::LZ4_LG => cstr!(\"lz4_lg\"),\n            Self::DTB => cstr!(\"dtb\"),\n            Self::ZIMAGE => cstr!(\"zimage\"),\n            _ => cstr!(\"raw\"),\n        }\n    }\n}\n\nimpl FileFormat {\n    pub fn ext(&self) -> &'static str {\n        match *self {\n            Self::GZIP | Self::ZOPFLI => \"gz\",\n            Self::LZOP => \"lzo\",\n            Self::XZ => \"xz\",\n            Self::LZMA => \"lzma\",\n            Self::BZIP2 => \"bz2\",\n            Self::LZ4 | Self::LZ4_LEGACY | Self::LZ4_LG => \"lz4\",\n            _ => \"\",\n        }\n    }\n\n    pub fn is_compressed(&self) -> bool {\n        matches!(\n            *self,\n            Self::GZIP\n                | Self::ZOPFLI\n                | Self::XZ\n                | Self::LZMA\n                | Self::BZIP2\n                | Self::LZ4\n                | Self::LZ4_LEGACY\n                | Self::LZ4_LG\n        )\n    }\n\n    pub fn formats() -> String {\n        [\n            Self::GZIP,\n            Self::ZOPFLI,\n            Self::XZ,\n            Self::LZMA,\n            Self::BZIP2,\n            Self::LZ4,\n            Self::LZ4_LEGACY,\n            Self::LZ4_LG,\n        ]\n        .map(|f| f.to_string())\n        .join(\" \")\n    }\n}\n\n// C++ FFI\n\npub fn fmt2name(fmt: FileFormat) -> *const libc::c_char {\n    fmt.as_cstr().as_ptr()\n}\n\npub fn fmt_compressed(fmt: FileFormat) -> bool {\n    fmt.is_compressed()\n}\n\npub fn fmt_compressed_any(fmt: FileFormat) -> bool {\n    fmt.is_compressed() || matches!(fmt, FileFormat::LZOP)\n}\n"
  },
  {
    "path": "native/src/boot/lib.rs",
    "content": "#![feature(format_args_nl)]\n#![feature(iter_intersperse)]\n\npub use base;\nuse compress::{compress_bytes, decompress_bytes};\nuse format::{fmt_compressed, fmt_compressed_any, fmt2name};\nuse sign::{SHA, get_sha, sha256_hash, sign_payload_for_cxx};\nuse std::env;\n\nmod cli;\nmod compress;\nmod cpio;\nmod dtb;\nmod format;\nmod patch;\nmod payload;\n// Suppress warnings in generated code\n#[allow(warnings)]\nmod proto;\nmod sign;\n\n#[cxx::bridge]\npub mod ffi {\n    enum FileFormat {\n        UNKNOWN,\n        /* Boot formats */\n        CHROMEOS,\n        AOSP,\n        AOSP_VENDOR,\n        DHTB,\n        BLOB,\n        /* Compression formats */\n        GZIP,\n        ZOPFLI,\n        XZ,\n        LZMA,\n        BZIP2,\n        LZ4,\n        LZ4_LEGACY,\n        LZ4_LG,\n        /* Unsupported compression */\n        LZOP,\n        /* Misc */\n        MTK,\n        DTB,\n        ZIMAGE,\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"magiskboot.hpp\");\n\n        #[cxx_name = \"Utf8CStr\"]\n        type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;\n\n        fn cleanup();\n        fn unpack(image: Utf8CStrRef, skip_decomp: bool, hdr: bool) -> i32;\n        fn repack(src_img: Utf8CStrRef, out_img: Utf8CStrRef, skip_comp: bool);\n        fn split_image_dtb(filename: Utf8CStrRef, skip_decomp: bool) -> i32;\n        fn check_fmt(buf: &[u8]) -> FileFormat;\n    }\n\n    extern \"Rust\" {\n        type SHA;\n        fn get_sha(use_sha1: bool) -> Box<SHA>;\n        fn update(self: &mut SHA, data: &[u8]);\n        fn finalize_into(self: &mut SHA, out: &mut [u8]);\n        fn output_size(self: &SHA) -> usize;\n        fn sha256_hash(data: &[u8], out: &mut [u8]);\n\n        fn compress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);\n        fn decompress_bytes(format: FileFormat, in_bytes: &[u8], out_fd: i32);\n        fn fmt2name(fmt: FileFormat) -> *const c_char;\n        fn fmt_compressed(fmt: FileFormat) -> bool;\n        fn fmt_compressed_any(fmt: FileFormat) -> bool;\n\n        #[cxx_name = \"sign_payload\"]\n        fn sign_payload_for_cxx(payload: &[u8]) -> Vec<u8>;\n    }\n\n    // BootImage FFI\n    unsafe extern \"C++\" {\n        include!(\"bootimg.hpp\");\n        #[cxx_name = \"boot_img\"]\n        type BootImage;\n\n        #[cxx_name = \"get_payload\"]\n        fn payload(self: &BootImage) -> &[u8];\n        #[cxx_name = \"get_tail\"]\n        fn tail(self: &BootImage) -> &[u8];\n        fn is_signed(self: &BootImage) -> bool;\n        fn tail_off(self: &BootImage) -> u64;\n\n        #[Self = BootImage]\n        #[cxx_name = \"create\"]\n        fn new(img: Utf8CStrRef) -> UniquePtr<BootImage>;\n    }\n    extern \"Rust\" {\n        #[cxx_name = \"verify\"]\n        fn verify_for_cxx(self: &BootImage) -> bool;\n    }\n}\n\n#[inline(always)]\npub(crate) fn check_env(env: &str) -> bool {\n    env::var(env).is_ok_and(|var| var == \"true\")\n}\n"
  },
  {
    "path": "native/src/boot/magiskboot.hpp",
    "content": "#pragma once\n\n#include <base.hpp>\n\n#define HEADER_FILE     \"header\"\n#define KERNEL_FILE     \"kernel\"\n#define RAMDISK_FILE    \"ramdisk.cpio\"\n#define VND_RAMDISK_DIR \"vendor_ramdisk\"\n#define SECOND_FILE     \"second\"\n#define EXTRA_FILE      \"extra\"\n#define KER_DTB_FILE    \"kernel_dtb\"\n#define RECV_DTBO_FILE  \"recovery_dtbo\"\n#define DTB_FILE        \"dtb\"\n#define BOOTCONFIG_FILE \"bootconfig\"\n#define NEW_BOOT        \"new-boot.img\"\n\n#define BUFFER_MATCH(buf, s) (memcmp(buf, s, sizeof(s) - 1) == 0)\n#define BUFFER_CONTAIN(buf, sz, s) (memmem(buf, sz, s, sizeof(s) - 1) != nullptr)\n#define CHECKED_MATCH(s) (len >= (sizeof(s) - 1) && BUFFER_MATCH(buf, s))\n\n#define BOOT_MAGIC      \"ANDROID!\"\n#define VENDOR_BOOT_MAGIC \"VNDRBOOT\"\n#define CHROMEOS_MAGIC  \"CHROMEOS\"\n#define GZIP1_MAGIC     \"\\x1f\\x8b\"\n#define GZIP2_MAGIC     \"\\x1f\\x9e\"\n#define LZOP_MAGIC      \"\\x89\"\"LZO\"\n#define XZ_MAGIC        \"\\xfd\"\"7zXZ\"\n#define BZIP_MAGIC      \"BZh\"\n#define LZ4_LEG_MAGIC   \"\\x02\\x21\\x4c\\x18\"\n#define LZ41_MAGIC      \"\\x03\\x21\\x4c\\x18\"\n#define LZ42_MAGIC      \"\\x04\\x22\\x4d\\x18\"\n#define MTK_MAGIC       \"\\x88\\x16\\x88\\x58\"\n#define DTB_MAGIC       \"\\xd0\\x0d\\xfe\\xed\"\n#define LG_BUMP_MAGIC   \"\\x41\\xa9\\xe4\\x67\\x74\\x4d\\x1d\\x1b\\xa4\\x29\\xf2\\xec\\xea\\x65\\x52\\x79\"\n#define DHTB_MAGIC      \"\\x44\\x48\\x54\\x42\\x01\\x00\\x00\\x00\"\n#define SEANDROID_MAGIC \"SEANDROIDENFORCE\"\n#define TEGRABLOB_MAGIC \"-SIGNED-BY-SIGNBLOB-\"\n#define NOOKHD_RL_MAGIC \"Red Loader\"\n#define NOOKHD_GL_MAGIC \"Green Loader\"\n#define NOOKHD_GR_MAGIC \"Green Recovery\"\n#define NOOKHD_EB_MAGIC \"eMMC boot.img+secondloader\"\n#define NOOKHD_ER_MAGIC \"eMMC recovery.img+secondloader\"\n#define NOOKHD_PRE_HEADER_SZ 1048576\n#define ACCLAIM_MAGIC   \"BauwksBoot\"\n#define ACCLAIM_PRE_HEADER_SZ 262144\n#define AMONET_MICROLOADER_MAGIC \"microloader\"\n#define AMONET_MICROLOADER_SZ 1024\n#define AVB_FOOTER_MAGIC \"AVBf\"\n#define AVB_MAGIC \"AVB0\"\n#define ZIMAGE_MAGIC \"\\x18\\x28\\x6f\\x01\"\n\nenum class FileFormat : uint8_t;\n\nint unpack(Utf8CStr image, bool skip_decomp = false, bool hdr = false);\nvoid repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp = false);\nint split_image_dtb(Utf8CStr filename, bool skip_decomp = false);\nvoid cleanup();\nFileFormat check_fmt(const void *buf, size_t len);\n\nstatic inline FileFormat check_fmt(rust::Slice<const uint8_t> bytes) {\n    return check_fmt(bytes.data(), bytes.size());\n}\n"
  },
  {
    "path": "native/src/boot/patch.rs",
    "content": "use base::{LoggedResult, MappedFile, MutBytesExt, Utf8CStr};\n\n// SAFETY: assert(buf.len() >= 1) && assert(len <= buf.len())\nmacro_rules! match_patterns {\n    ($buf:ident, $($str:literal), *) => {{\n        let mut len = if *$buf.get_unchecked(0) == b',' { 1 } else { 0 };\n        let b = $buf.get_unchecked(len..);\n        let found = if b.is_empty() {\n            false\n        }\n        $(\n        else if b.starts_with($str) {\n            len += $str.len();\n            true\n        }\n        )*\n        else {\n            false\n        };\n        if found {\n            let b = $buf.get_unchecked(len..);\n            if !b.is_empty() && b[0] == b'=' {\n                for c in b.iter() {\n                    if b\" \\n\\0\".contains(c) {\n                        break;\n                    }\n                    len += 1;\n                }\n            }\n            Some(len)\n        } else {\n            None\n        }\n    }};\n}\n\nfn remove_pattern(buf: &mut [u8], pattern_matcher: unsafe fn(&[u8]) -> Option<usize>) -> usize {\n    let mut write = 0_usize;\n    let mut read = 0_usize;\n    let mut sz = buf.len();\n    // SAFETY: assert(write <= read) && assert(read <= buf.len())\n    unsafe {\n        while read < buf.len() {\n            if let Some(len) = pattern_matcher(buf.get_unchecked(read..)) {\n                let skipped = buf.get_unchecked(read..(read + len));\n                // SAFETY: all matching patterns are ASCII bytes\n                let skipped = std::str::from_utf8_unchecked(skipped);\n                eprintln!(\"Remove pattern [{skipped}]\");\n                sz -= len;\n                read += len;\n            } else {\n                *buf.get_unchecked_mut(write) = *buf.get_unchecked(read);\n                write += 1;\n                read += 1;\n            }\n        }\n    }\n    if let Some(buf) = buf.get_mut(write..) {\n        buf.fill(0);\n    }\n    sz\n}\n\npub fn patch_verity(buf: &mut [u8]) -> usize {\n    unsafe fn match_verity_pattern(buf: &[u8]) -> Option<usize> {\n        unsafe {\n            match_patterns!(\n                buf,\n                b\"verifyatboot\",\n                b\"verify\",\n                b\"avb_keys\",\n                b\"avb\",\n                b\"support_scfs\",\n                b\"fsverity\"\n            )\n        }\n    }\n\n    remove_pattern(buf, match_verity_pattern)\n}\n\npub fn patch_encryption(buf: &mut [u8]) -> usize {\n    unsafe fn match_encryption_pattern(buf: &[u8]) -> Option<usize> {\n        unsafe { match_patterns!(buf, b\"forceencrypt\", b\"forcefdeorfbe\", b\"fileencryption\") }\n    }\n\n    remove_pattern(buf, match_encryption_pattern)\n}\n\nfn hex2byte(hex: &[u8]) -> Vec<u8> {\n    let mut v = Vec::with_capacity(hex.len() / 2);\n    for bytes in hex.chunks(2) {\n        if bytes.len() != 2 {\n            break;\n        }\n        let high = bytes[0].to_ascii_uppercase() - b'0';\n        let low = bytes[1].to_ascii_uppercase() - b'0';\n        let h = if high > 9 { high - 7 } else { high };\n        let l = if low > 9 { low - 7 } else { low };\n        v.push((h << 4) | l);\n    }\n    v\n}\n\npub fn hexpatch(file: &Utf8CStr, from: &Utf8CStr, to: &Utf8CStr) -> bool {\n    let res = || -> LoggedResult<bool> {\n        let mut map = MappedFile::open_rw(file)?;\n        let pattern = hex2byte(from.as_bytes());\n        let patch = hex2byte(to.as_bytes());\n\n        let v = map.patch(pattern.as_slice(), patch.as_slice());\n        for off in &v {\n            eprintln!(\"Patch @ {off:#010X} [{from}] -> [{to}]\");\n        }\n        Ok(!v.is_empty())\n    }();\n    res.unwrap_or(false)\n}\n"
  },
  {
    "path": "native/src/boot/payload.rs",
    "content": "use crate::compress::get_decoder;\nuse crate::ffi::check_fmt;\nuse crate::proto::update_metadata::DeltaArchiveManifest;\nuse crate::proto::update_metadata::mod_InstallOperation::Type;\nuse base::{LoggedError, LoggedResult, ReadSeekExt, ResultExt, WriteExt, error};\nuse byteorder::{BigEndian, ReadBytesExt};\nuse quick_protobuf::{BytesReader, MessageRead};\nuse std::fs::File;\nuse std::io::{BufReader, Cursor, Read, Seek, SeekFrom, Write};\nuse std::os::fd::FromRawFd;\n\nmacro_rules! bad_payload {\n    ($msg:literal) => {{\n        error!(concat!(\"Invalid payload: \", $msg));\n        LoggedError::default()\n    }};\n    ($($args:tt)*) => {{\n        error!(\"Invalid payload: {}\", format_args!($($args)*));\n        LoggedError::default()\n    }};\n}\n\nconst PAYLOAD_MAGIC: &str = \"CrAU\";\n\npub fn extract_boot_from_payload(\n    in_path: &str,\n    partition_name: Option<&str>,\n    out_path: Option<&str>,\n) -> LoggedResult<()> {\n    let mut reader = BufReader::new(if in_path == \"-\" {\n        unsafe { File::from_raw_fd(0) }\n    } else {\n        File::open(in_path).log_with_msg(|w| write!(w, \"Cannot open '{in_path}'\"))?\n    });\n\n    let buf = &mut [0u8; 4];\n    reader.read_exact(buf)?;\n\n    if buf != PAYLOAD_MAGIC.as_bytes() {\n        return Err(bad_payload!(\"invalid magic\"));\n    }\n\n    let version = reader.read_u64::<BigEndian>()?;\n    if version != 2 {\n        return Err(bad_payload!(\"unsupported version: {}\", version));\n    }\n\n    let manifest_len = reader.read_u64::<BigEndian>()? as usize;\n    if manifest_len == 0 {\n        return Err(bad_payload!(\"manifest length is zero\"));\n    }\n\n    let manifest_sig_len = reader.read_u32::<BigEndian>()?;\n    if manifest_sig_len == 0 {\n        return Err(bad_payload!(\"manifest signature length is zero\"));\n    }\n\n    let mut buf = vec![0; manifest_len];\n\n    let manifest = {\n        let manifest = &mut buf[..manifest_len];\n        reader.read_exact(manifest)?;\n        let mut br = BytesReader::from_bytes(manifest);\n        DeltaArchiveManifest::from_reader(&mut br, manifest)?\n    };\n    if manifest.get_minor_version() != 0 {\n        return Err(bad_payload!(\n            \"delta payloads are not supported, please use a full payload file\"\n        ));\n    }\n\n    let block_size = manifest.get_block_size() as u64;\n\n    let partition = match partition_name {\n        None => {\n            let boot = manifest\n                .partitions\n                .iter()\n                .find(|p| p.partition_name == \"init_boot\");\n            let boot = match boot {\n                Some(boot) => Some(boot),\n                None => manifest\n                    .partitions\n                    .iter()\n                    .find(|p| p.partition_name == \"boot\"),\n            };\n            boot.ok_or_else(|| bad_payload!(\"boot partition not found\"))?\n        }\n        Some(name) => manifest\n            .partitions\n            .iter()\n            .find(|p| p.partition_name.as_str() == name)\n            .ok_or_else(|| bad_payload!(\"partition '{}' not found\", name))?,\n    };\n\n    let out_str: String;\n    let out_path = match out_path {\n        None => {\n            out_str = format!(\"{}.img\", partition.partition_name);\n            out_str.as_str()\n        }\n        Some(s) => s,\n    };\n\n    let mut out_file =\n        File::create(out_path).log_with_msg(|w| write!(w, \"Cannot write to '{out_path}'\"))?;\n\n    // Skip the manifest signature\n    reader.skip(manifest_sig_len as usize)?;\n\n    // Sort the install operations with data_offset so we will only ever need to seek forward\n    // This makes it possible to support non-seekable input file descriptors\n    let mut operations = partition.operations.clone();\n    operations.sort_by_key(|e| e.data_offset.unwrap_or(0));\n    let mut curr_data_offset: u64 = 0;\n\n    for operation in operations.iter() {\n        let data_len = operation\n            .data_length\n            .ok_or_else(|| bad_payload!(\"data length not found\"))? as usize;\n\n        let data_offset = operation\n            .data_offset\n            .ok_or_else(|| bad_payload!(\"data offset not found\"))?;\n\n        let data_type = operation.type_pb;\n\n        buf.resize(data_len, 0u8);\n        let data = &mut buf[..data_len];\n\n        // Skip to the next offset and read data\n        let skip = data_offset - curr_data_offset;\n        reader.skip(skip as usize)?;\n        reader.read_exact(data)?;\n        curr_data_offset = data_offset + data_len as u64;\n\n        let out_offset = operation\n            .dst_extents\n            .first()\n            .ok_or_else(|| bad_payload!(\"dst extents not found\"))?\n            .start_block\n            .ok_or_else(|| bad_payload!(\"start block not found\"))?\n            * block_size;\n\n        match data_type {\n            Type::REPLACE => {\n                out_file.seek(SeekFrom::Start(out_offset))?;\n                out_file.write_all(data)?;\n            }\n            Type::ZERO => {\n                for ext in operation.dst_extents.iter() {\n                    let out_seek = ext\n                        .start_block\n                        .ok_or_else(|| bad_payload!(\"start block not found\"))?\n                        * block_size;\n                    let num_blocks = ext\n                        .num_blocks\n                        .ok_or_else(|| bad_payload!(\"num blocks not found\"))?;\n                    out_file.seek(SeekFrom::Start(out_seek))?;\n                    out_file.write_zeros(num_blocks as usize)?;\n                }\n            }\n            Type::REPLACE_BZ | Type::REPLACE_XZ => {\n                out_file.seek(SeekFrom::Start(out_offset))?;\n                let fmt = check_fmt(data);\n\n                let Ok(_) = || -> std::io::Result<()> {\n                    let mut decoder = get_decoder(fmt, Cursor::new(data))?;\n                    std::io::copy(decoder.as_mut(), &mut out_file)?;\n                    Ok(())\n                }() else {\n                    return Err(bad_payload!(\"decompression failed\"));\n                };\n            }\n            _ => return Err(bad_payload!(\"unsupported operation type\")),\n        };\n    }\n\n    Ok(())\n}\n"
  },
  {
    "path": "native/src/boot/proto/update_metadata.proto",
    "content": "//\n// Copyright (C) 2010 The Android Open Source Project\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//      http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n// Update file format: An update file contains all the operations needed\n// to update a system to a specific version. It can be a full payload which\n// can update from any version, or a delta payload which can only update\n// from a specific version.\n// The update format is represented by this struct pseudocode:\n// struct delta_update_file {\n//   char magic[4] = \"CrAU\";\n//   uint64 file_format_version;  // payload major version\n//   uint64 manifest_size;  // Size of protobuf DeltaArchiveManifest\n//\n//   // Only present if format_version >= 2:\n//   uint32 metadata_signature_size;\n//\n//   // The DeltaArchiveManifest protobuf serialized, not compressed.\n//   char manifest[manifest_size];\n//\n//   // The signature of the metadata (from the beginning of the payload up to\n//   // this location, not including the signature itself). This is a serialized\n//   // Signatures message.\n//   char metadata_signature_message[metadata_signature_size];\n//\n//   // Data blobs for files, no specific format. The specific offset\n//   // and length of each data blob is recorded in the DeltaArchiveManifest.\n//   struct {\n//     char data[];\n//   } blobs[];\n//\n//   // The signature of the entire payload, everything up to this location,\n//   // except that metadata_signature_message is skipped to simplify signing\n//   // process. These two are not signed:\n//   uint64 payload_signatures_message_size;\n//   // This is a serialized Signatures message.\n//   char payload_signatures_message[payload_signatures_message_size];\n//\n// };\n// The DeltaArchiveManifest protobuf is an ordered list of InstallOperation\n// objects. These objects are stored in a linear array in the\n// DeltaArchiveManifest. Each operation is applied in order by the client.\n// The DeltaArchiveManifest also contains the initial and final\n// checksums for the device.\n// The client will perform each InstallOperation in order, beginning even\n// before the entire delta file is downloaded (but after at least the\n// protobuf is downloaded). The types of operations are explained:\n// - REPLACE: Replace the dst_extents on the drive with the attached data,\n//   zero padding out to block size.\n// - REPLACE_BZ: bzip2-uncompress the attached data and write it into\n//   dst_extents on the drive, zero padding to block size.\n// - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap,\n//   so it may be desirable to read all src_extents data into memory before\n//   writing it out. (deprecated)\n// - SOURCE_COPY: Copy the data in src_extents in the old partition to\n//   dst_extents in the new partition. There's no overlapping of data because\n//   the extents are in different partitions.\n// - BSDIFF: Read src_length bytes from src_extents into memory, perform\n//   bspatch with attached data, write new data to dst_extents, zero padding\n//   to block size. (deprecated)\n// - SOURCE_BSDIFF: Read the data in src_extents in the old partition, perform\n//   bspatch with the attached data and write the new data to dst_extents in the\n//   new partition.\n// - ZERO: Write zeros to the destination dst_extents.\n// - DISCARD: Discard the destination dst_extents blocks on the physical medium.\n//   the data read from those blocks is undefined.\n// - REPLACE_XZ: Replace the dst_extents with the contents of the attached\n//   xz file after decompression. The xz file should only use crc32 or no crc at\n//   all to be compatible with xz-embedded.\n// - PUFFDIFF: Read the data in src_extents in the old partition, perform\n//   puffpatch with the attached data and write the new data to dst_extents in\n//   the new partition.\n//\n// The operations allowed in the payload (supported by the client) depend on the\n// major and minor version. See InstallOperation.Type below for details.\nsyntax = \"proto2\";\npackage chromeos_update_engine;\n// Data is packed into blocks on disk, always starting from the beginning\n// of the block. If a file's data is too large for one block, it overflows\n// into another block, which may or may not be the following block on the\n// physical partition. An ordered list of extents is another\n// representation of an ordered list of blocks. For example, a file stored\n// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in\n// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order).\n// In general, files are stored sequentially on disk, so it's more efficient\n// to use extents to encode the block lists (this is effectively\n// run-length encoding).\n// A sentinel value (kuint64max) as the start block denotes a sparse-hole\n// in a file whose block-length is specified by num_blocks.\nmessage Extent {\n  optional uint64 start_block = 1;\n  optional uint64 num_blocks = 2;\n}\n// Signatures: Updates may be signed by the OS vendor. The client verifies\n// an update's signature by hashing the entire download. The section of the\n// download that contains the signature is at the end of the file, so when\n// signing a file, only the part up to the signature part is signed.\n// Then, the client looks inside the download's Signatures message for a\n// Signature message that it knows how to handle. Generally, a client will\n// only know how to handle one type of signature, but an update may contain\n// many signatures to support many different types of client. Then client\n// selects a Signature message and uses that, along with a known public key,\n// to verify the download. The public key is expected to be part of the\n// client.\nmessage Signatures {\n  message Signature {\n    optional uint32 version = 1 [deprecated = true];\n    optional bytes data = 2;\n    // The DER encoded signature size of EC keys is nondeterministic for\n    // different input of sha256 hash. However, we need the size of the\n    // serialized signatures protobuf string to be fixed before signing;\n    // because this size is part of the content to be signed. Therefore, we\n    // always pad the signature data to the maximum possible signature size of\n    // a given key. And the payload verifier will truncate the signature to\n    // its correct size based on the value of |unpadded_signature_size|.\n    optional fixed32 unpadded_signature_size = 3;\n  }\n  repeated Signature signatures = 1;\n}\nmessage PartitionInfo {\n  optional uint64 size = 1;\n  optional bytes hash = 2;\n}\nmessage InstallOperation {\n  enum Type {\n    REPLACE = 0;     // Replace destination extents w/ attached data.\n    REPLACE_BZ = 1;  // Replace destination extents w/ attached bzipped data.\n    MOVE = 2 [deprecated = true];    // Move source extents to target extents.\n    BSDIFF = 3 [deprecated = true];  // The data is a bsdiff binary diff.\n    // On minor version 2 or newer, these operations are supported:\n    SOURCE_COPY = 4;    // Copy from source to target partition\n    SOURCE_BSDIFF = 5;  // Like BSDIFF, but read from source partition\n    // On minor version 3 or newer and on major version 2 or newer, these\n    // operations are supported:\n    REPLACE_XZ = 8;  // Replace destination extents w/ attached xz data.\n    // On minor version 4 or newer, these operations are supported:\n    ZERO = 6;     // Write zeros in the destination.\n    DISCARD = 7;  // Discard the destination blocks, reading as undefined.\n    BROTLI_BSDIFF = 10;  // Like SOURCE_BSDIFF, but compressed with brotli.\n    // On minor version 5 or newer, these operations are supported:\n    PUFFDIFF = 9;  // The data is in puffdiff format.\n    // On minor version 8 or newer, these operations are supported:\n    ZUCCHINI = 11;\n    // On minor version 9 or newer, these operations are supported:\n    LZ4DIFF_BSDIFF = 12;\n    LZ4DIFF_PUFFDIFF = 13;\n  }\n  required Type type = 1;\n  // Only minor version 6 or newer support 64 bits |data_offset| and\n  // |data_length|, older client will read them as uint32.\n  // The offset into the delta file (after the protobuf)\n  // where the data (if any) is stored\n  optional uint64 data_offset = 2;\n  // The length of the data in the delta file\n  optional uint64 data_length = 3;\n  // Ordered list of extents that are read from (if any) and written to.\n  repeated Extent src_extents = 4;\n  // Byte length of src, equal to the number of blocks in src_extents *\n  // block_size. It is used for BSDIFF and SOURCE_BSDIFF, because we need to\n  // pass that external program the number of bytes to read from the blocks we\n  // pass it.  This is not used in any other operation.\n  optional uint64 src_length = 5;\n  repeated Extent dst_extents = 6;\n  // Byte length of dst, equal to the number of blocks in dst_extents *\n  // block_size. Used for BSDIFF and SOURCE_BSDIFF, but not in any other\n  // operation.\n  optional uint64 dst_length = 7;\n  // Optional SHA 256 hash of the blob associated with this operation.\n  // This is used as a primary validation for http-based downloads and\n  // as a defense-in-depth validation for https-based downloads. If\n  // the operation doesn't refer to any blob, this field will have\n  // zero bytes.\n  optional bytes data_sha256_hash = 8;\n  // Indicates the SHA 256 hash of the source data referenced in src_extents at\n  // the time of applying the operation. If present, the update_engine daemon\n  // MUST read and verify the source data before applying the operation.\n  optional bytes src_sha256_hash = 9;\n}\n// Hints to VAB snapshot to skip writing some blocks if these blocks are\n// identical to the ones on the source image. The src & dst extents for each\n// CowMergeOperation should be contiguous, and they're a subset of an OTA\n// InstallOperation.\n// During merge time, we need to follow the pre-computed sequence to avoid\n// read after write, similar to the inplace update schema.\nmessage CowMergeOperation {\n  enum Type {\n    COW_COPY = 0;     // identical blocks\n    COW_XOR = 1;      // used when src/dst blocks are highly similar\n    COW_REPLACE = 2;  // Raw replace operation\n  }\n  optional Type type = 1;\n  optional Extent src_extent = 2;\n  optional Extent dst_extent = 3;\n  // For COW_XOR, source location might be unaligned, so this field is in range\n  // [0, block_size), representing how much should the src_extent shift toward\n  // larger block number. If this field is non-zero, then src_extent will\n  // include 1 extra block in the end, as the merge op actually references the\n  // first |src_offset| bytes of that extra block. For example, if |dst_extent|\n  // is [10, 15], |src_offset| is 500, then src_extent might look like [25, 31].\n  // Note that |src_extent| contains 1 extra block than the |dst_extent|.\n  optional uint32 src_offset = 4;\n}\n// Describes the update to apply to a single partition.\nmessage PartitionUpdate {\n  // A platform-specific name to identify the partition set being updated. For\n  // example, in Chrome OS this could be \"ROOT\" or \"KERNEL\".\n  required string partition_name = 1;\n  // Whether this partition carries a filesystem with post-install program that\n  // must be run to finalize the update process. See also |postinstall_path| and\n  // |filesystem_type|.\n  optional bool run_postinstall = 2;\n  // The path of the executable program to run during the post-install step,\n  // relative to the root of this filesystem. If not set, the default \"postinst\"\n  // will be used. This setting is only used when |run_postinstall| is set and\n  // true.\n  optional string postinstall_path = 3;\n  // The filesystem type as passed to the mount(2) syscall when mounting the new\n  // filesystem to run the post-install program. If not set, a fixed list of\n  // filesystems will be attempted. This setting is only used if\n  // |run_postinstall| is set and true.\n  optional string filesystem_type = 4;\n  // If present, a list of signatures of the new_partition_info.hash signed with\n  // different keys. If the update_engine daemon requires vendor-signed images\n  // and has its public key installed, one of the signatures should be valid\n  // for /postinstall to run.\n  repeated Signatures.Signature new_partition_signature = 5;\n  optional PartitionInfo old_partition_info = 6;\n  optional PartitionInfo new_partition_info = 7;\n  // The list of operations to be performed to apply this PartitionUpdate. The\n  // associated operation blobs (in operations[i].data_offset, data_length)\n  // should be stored contiguously and in the same order.\n  repeated InstallOperation operations = 8;\n  // Whether a failure in the postinstall step for this partition should be\n  // ignored.\n  optional bool postinstall_optional = 9;\n  // On minor version 6 or newer, these fields are supported:\n  // The extent for data covered by verity hash tree.\n  optional Extent hash_tree_data_extent = 10;\n  // The extent to store verity hash tree.\n  optional Extent hash_tree_extent = 11;\n  // The hash algorithm used in verity hash tree.\n  optional string hash_tree_algorithm = 12;\n  // The salt used for verity hash tree.\n  optional bytes hash_tree_salt = 13;\n  // The extent for data covered by FEC.\n  optional Extent fec_data_extent = 14;\n  // The extent to store FEC.\n  optional Extent fec_extent = 15;\n  // The number of FEC roots.\n  optional uint32 fec_roots = 16 [default = 2];\n  // Per-partition version used for downgrade detection, added\n  // as an effort to support partial updates. For most partitions,\n  // this is the build timestamp.\n  optional string version = 17;\n  // A sorted list of CowMergeOperation. When writing cow, we can choose to\n  // skip writing the raw bytes for these extents. During snapshot merge, the\n  // bytes will read from the source partitions instead.\n  repeated CowMergeOperation merge_operations = 18;\n  // Estimated size for COW image. This is used by libsnapshot\n  // as a hint. If set to 0, libsnapshot should use alternative\n  // methods for estimating size.\n  optional uint64 estimate_cow_size = 19;\n}\nmessage DynamicPartitionGroup {\n  // Name of the group.\n  required string name = 1;\n  // Maximum size of the group. The sum of sizes of all partitions in the group\n  // must not exceed the maximum size of the group.\n  optional uint64 size = 2;\n  // A list of partitions that belong to the group.\n  repeated string partition_names = 3;\n}\nmessage VABCFeatureSet {\n  optional bool threaded = 1;\n  optional bool batch_writes = 2;\n}\n// Metadata related to all dynamic partitions.\nmessage DynamicPartitionMetadata {\n  // All updatable groups present in |partitions| of this DeltaArchiveManifest.\n  // - If an updatable group is on the device but not in the manifest, it is\n  //   not updated. Hence, the group will not be resized, and partitions cannot\n  //   be added to or removed from the group.\n  // - If an updatable group is in the manifest but not on the device, the group\n  //   is added to the device.\n  repeated DynamicPartitionGroup groups = 1;\n  // Whether dynamic partitions have snapshots during the update. If this is\n  // set to true, the update_engine daemon creates snapshots for all dynamic\n  // partitions if possible. If this is unset, the update_engine daemon MUST\n  // NOT create snapshots for dynamic partitions.\n  optional bool snapshot_enabled = 2;\n  // If this is set to false, update_engine should not use VABC regardless. If\n  // this is set to true, update_engine may choose to use VABC if device\n  // supports it, but not guaranteed.\n  // VABC stands for Virtual AB Compression\n  optional bool vabc_enabled = 3;\n  // The compression algorithm used by VABC. Available ones are \"gz\", \"brotli\".\n  // See system/core/fs_mgr/libsnapshot/cow_writer.cpp for available options,\n  // as this parameter is ultimated forwarded to libsnapshot's CowWriter\n  optional string vabc_compression_param = 4;\n  // COW version used by VABC. The represents the major version in the COW\n  // header\n  optional uint32 cow_version = 5;\n  // A collection of knobs to tune Virtual AB Compression\n  optional VABCFeatureSet vabc_feature_set = 6;\n}\n// Definition has been duplicated from\n// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync.\nmessage ApexInfo {\n  optional string package_name = 1;\n  optional int64 version = 2;\n  optional bool is_compressed = 3;\n  optional int64 decompressed_size = 4;\n}\n// Definition has been duplicated from\n// $ANDROID_BUILD_TOP/build/tools/releasetools/ota_metadata.proto. Keep in sync.\nmessage ApexMetadata {\n  repeated ApexInfo apex_info = 1;\n}\nmessage DeltaArchiveManifest {\n  // Only present in major version = 1. List of install operations for the\n  // kernel and rootfs partitions. For major version = 2 see the |partitions|\n  // field.\n  reserved 1, 2;\n  // (At time of writing) usually 4096\n  optional uint32 block_size = 3 [default = 4096];\n  // If signatures are present, the offset into the blobs, generally\n  // tacked onto the end of the file, and the length. We use an offset\n  // rather than a bool to allow for more flexibility in future file formats.\n  // If either is absent, it means signatures aren't supported in this\n  // file.\n  optional uint64 signatures_offset = 4;\n  optional uint64 signatures_size = 5;\n  // Fields deprecated in major version 2.\n  reserved 6,7,8,9,10,11;\n  // The minor version, also referred as \"delta version\", of the payload.\n  // Minor version 0 is full payload, everything else is delta payload.\n  optional uint32 minor_version = 12 [default = 0];\n  // Only present in major version >= 2. List of partitions that will be\n  // updated, in the order they will be updated. This field replaces the\n  // |install_operations|, |kernel_install_operations| and the\n  // |{old,new}_{kernel,rootfs}_info| fields used in major version = 1. This\n  // array can have more than two partitions if needed, and they are identified\n  // by the partition name.\n  repeated PartitionUpdate partitions = 13;\n  // The maximum timestamp of the OS allowed to apply this payload.\n  // Can be used to prevent downgrading the OS.\n  optional int64 max_timestamp = 14;\n  // Metadata related to all dynamic partitions.\n  optional DynamicPartitionMetadata dynamic_partition_metadata = 15;\n  // If the payload only updates a subset of partitions on the device.\n  optional bool partial_update = 16;\n  // Information on compressed APEX to figure out how much space is required for\n  // their decompression\n  repeated ApexInfo apex_info = 17;\n  // Security patch level of the device, usually in the format of\n  // yyyy-mm-dd\n  optional string security_patch_level = 18;\n}\n"
  },
  {
    "path": "native/src/boot/sign.rs",
    "content": "use der::referenced::OwnedToRef;\nuse der::{Decode, DecodePem, Encode, Sequence, SliceReader};\nuse digest::DynDigest;\nuse p256::ecdsa::{\n    Signature as P256Signature, SigningKey as P256SigningKey, VerifyingKey as P256VerifyingKey,\n};\nuse p256::pkcs8::DecodePrivateKey;\nuse p384::ecdsa::{\n    Signature as P384Signature, SigningKey as P384SigningKey, VerifyingKey as P384VerifyingKey,\n};\nuse p521::ecdsa::{\n    Signature as P521Signature, SigningKey as P521SigningKey, VerifyingKey as P521VerifyingKey,\n};\nuse rsa::pkcs1v15::{\n    Signature as RsaSignature, SigningKey as RsaSigningKey, VerifyingKey as RsaVerifyingKey,\n};\nuse rsa::pkcs8::SubjectPublicKeyInfoRef;\nuse rsa::signature::SignatureEncoding;\nuse rsa::signature::hazmat::{PrehashSigner, PrehashVerifier};\nuse rsa::{RsaPrivateKey, RsaPublicKey};\nuse sha1::Sha1;\nuse sha2::{Sha256, Sha384, Sha512};\nuse x509_cert::Certificate;\nuse x509_cert::der::Any;\nuse x509_cert::der::asn1::{OctetString, PrintableString};\nuse x509_cert::spki::AlgorithmIdentifier;\n\nuse base::{LoggedResult, MappedFile, ResultExt, SilentLogExt, Utf8CStr, cstr, log_err};\n\nuse crate::ffi::BootImage;\n\n#[allow(clippy::upper_case_acronyms)]\npub enum SHA {\n    SHA1(Sha1),\n    SHA256(Sha256),\n}\n\nimpl SHA {\n    pub fn update(&mut self, data: &[u8]) {\n        match self {\n            SHA::SHA1(h) => h.update(data),\n            SHA::SHA256(h) => h.update(data),\n        }\n    }\n\n    pub fn output_size(&self) -> usize {\n        match self {\n            SHA::SHA1(h) => h.output_size(),\n            SHA::SHA256(h) => h.output_size(),\n        }\n    }\n\n    pub fn finalize_into(&mut self, out: &mut [u8]) {\n        match self {\n            SHA::SHA1(h) => h.finalize_into_reset(out),\n            SHA::SHA256(h) => h.finalize_into_reset(out),\n        }\n        .ok();\n    }\n}\n\npub fn get_sha(use_sha1: bool) -> Box<SHA> {\n    Box::new(if use_sha1 {\n        SHA::SHA1(Sha1::default())\n    } else {\n        SHA::SHA256(Sha256::default())\n    })\n}\n\npub fn sha1_hash(data: &[u8], out: &mut [u8]) {\n    let mut h = Sha1::default();\n    h.update(data);\n    DynDigest::finalize_into(h, out).ok();\n}\n\npub fn sha256_hash(data: &[u8], out: &mut [u8]) {\n    let mut h = Sha256::default();\n    h.update(data);\n    DynDigest::finalize_into(h, out).ok();\n}\n\n#[allow(clippy::large_enum_variant)]\nenum SigningKey {\n    SHA256withRSA(RsaSigningKey<Sha256>),\n    SHA256withECDSA(P256SigningKey),\n    SHA384withECDSA(P384SigningKey),\n    SHA521withECDSA(P521SigningKey),\n}\n\n#[allow(clippy::large_enum_variant)]\nenum VerifyingKey {\n    SHA256withRSA(RsaVerifyingKey<Sha256>),\n    SHA256withECDSA(P256VerifyingKey),\n    SHA384withECDSA(P384VerifyingKey),\n    SHA521withECDSA(P521VerifyingKey),\n}\n\nstruct Verifier {\n    digest: Box<dyn DynDigest>,\n    key: VerifyingKey,\n}\n\nimpl Verifier {\n    fn from_public_key(key: SubjectPublicKeyInfoRef) -> LoggedResult<Verifier> {\n        let digest: Box<dyn DynDigest>;\n        let key = if let Ok(rsa) = RsaPublicKey::try_from(key.clone()) {\n            digest = Box::<Sha256>::default();\n            VerifyingKey::SHA256withRSA(RsaVerifyingKey::<Sha256>::new(rsa))\n        } else if let Ok(ec) = P256VerifyingKey::try_from(key.clone()) {\n            digest = Box::<Sha256>::default();\n            VerifyingKey::SHA256withECDSA(ec)\n        } else if let Ok(ec) = P384VerifyingKey::try_from(key.clone()) {\n            digest = Box::<Sha384>::default();\n            VerifyingKey::SHA384withECDSA(ec)\n        } else if let Ok(ec) = P521VerifyingKey::try_from(key.clone()) {\n            digest = Box::<Sha512>::default();\n            VerifyingKey::SHA521withECDSA(ec)\n        } else {\n            return log_err!(\"Unsupported private key\");\n        };\n        Ok(Verifier { digest, key })\n    }\n\n    fn update(&mut self, data: &[u8]) {\n        self.digest.update(data)\n    }\n\n    fn verify(mut self, signature: &[u8]) -> LoggedResult<()> {\n        let hash = self.digest.finalize_reset();\n        match &self.key {\n            VerifyingKey::SHA256withRSA(key) => {\n                let sig = RsaSignature::try_from(signature)?;\n                key.verify_prehash(hash.as_ref(), &sig).log()\n            }\n            VerifyingKey::SHA256withECDSA(key) => {\n                let sig = P256Signature::from_slice(signature)?;\n                key.verify_prehash(hash.as_ref(), &sig).log()\n            }\n            VerifyingKey::SHA384withECDSA(key) => {\n                let sig = P384Signature::from_slice(signature)?;\n                key.verify_prehash(hash.as_ref(), &sig).log()\n            }\n            VerifyingKey::SHA521withECDSA(key) => {\n                let sig = P521Signature::from_slice(signature)?;\n                key.verify_prehash(hash.as_ref(), &sig).log()\n            }\n        }\n    }\n}\n\nstruct Signer {\n    digest: Box<dyn DynDigest>,\n    key: SigningKey,\n}\n\nimpl Signer {\n    fn from_private_key(key: &[u8]) -> LoggedResult<Signer> {\n        let digest: Box<dyn DynDigest>;\n        let key = match RsaPrivateKey::from_pkcs8_der(key) {\n            Ok(rsa) => {\n                digest = Box::<Sha256>::default();\n                SigningKey::SHA256withRSA(RsaSigningKey::<Sha256>::new(rsa))\n            }\n            _ => match P256SigningKey::from_pkcs8_der(key) {\n                Ok(ec) => {\n                    digest = Box::<Sha256>::default();\n                    SigningKey::SHA256withECDSA(ec)\n                }\n                _ => match P384SigningKey::from_pkcs8_der(key) {\n                    Ok(ec) => {\n                        digest = Box::<Sha384>::default();\n                        SigningKey::SHA384withECDSA(ec)\n                    }\n                    _ => match P521SigningKey::from_pkcs8_der(key) {\n                        Ok(ec) => {\n                            digest = Box::<Sha512>::default();\n                            SigningKey::SHA521withECDSA(ec)\n                        }\n                        _ => {\n                            return log_err!(\"Unsupported private key\");\n                        }\n                    },\n                },\n            },\n        };\n        Ok(Signer { digest, key })\n    }\n\n    fn update(&mut self, data: &[u8]) {\n        self.digest.update(data)\n    }\n\n    fn sign(mut self) -> LoggedResult<Vec<u8>> {\n        let hash = self.digest.finalize_reset();\n        let v = match &self.key {\n            SigningKey::SHA256withRSA(key) => {\n                let sig: RsaSignature = key.sign_prehash(hash.as_ref())?;\n                sig.to_vec()\n            }\n            SigningKey::SHA256withECDSA(key) => {\n                let sig: P256Signature = key.sign_prehash(hash.as_ref())?;\n                sig.to_vec()\n            }\n            SigningKey::SHA384withECDSA(key) => {\n                let sig: P384Signature = key.sign_prehash(hash.as_ref())?;\n                sig.to_vec()\n            }\n            SigningKey::SHA521withECDSA(key) => {\n                let sig: P521Signature = key.sign_prehash(hash.as_ref())?;\n                sig.to_vec()\n            }\n        };\n        Ok(v)\n    }\n}\n\n/*\n * BootSignature ::= SEQUENCE {\n *     formatVersion ::= INTEGER,\n *     certificate ::= Certificate,\n *     algorithmIdentifier ::= SEQUENCE {\n *         algorithm OBJECT IDENTIFIER,\n *         parameters ANY DEFINED BY algorithm OPTIONAL\n *     },\n *     authenticatedAttributes ::= SEQUENCE {\n *         target CHARACTER STRING,\n *         length INTEGER\n *     },\n *     signature ::= OCTET STRING\n * }\n */\n\n#[derive(Sequence)]\nstruct AuthenticatedAttributes {\n    target: PrintableString,\n    length: u64,\n}\n\n#[derive(Sequence)]\nstruct BootSignature {\n    format_version: i32,\n    certificate: Certificate,\n    algorithm_identifier: AlgorithmIdentifier<Any>,\n    authenticated_attributes: AuthenticatedAttributes,\n    signature: OctetString,\n}\n\nimpl BootSignature {\n    fn verify(self, payload: &[u8]) -> LoggedResult<()> {\n        if self.authenticated_attributes.length as usize != payload.len() {\n            return log_err!(\"Invalid image size\");\n        }\n        let mut verifier = Verifier::from_public_key(\n            self.certificate\n                .tbs_certificate()\n                .subject_public_key_info()\n                .owned_to_ref(),\n        )?;\n        verifier.update(payload);\n        let attr = self.authenticated_attributes.to_der()?;\n        verifier.update(attr.as_slice());\n        verifier.verify(self.signature.as_bytes())?;\n        Ok(())\n    }\n}\n\nimpl BootImage {\n    pub fn verify(&self, cert: Option<&Utf8CStr>) -> LoggedResult<()> {\n        let tail = self.tail();\n        if tail.starts_with(b\"AVB0\") {\n            return log_err!();\n        }\n\n        // Don't use BootSignature::from_der because tail might have trailing zeros\n        let mut reader = SliceReader::new(tail)?;\n        let mut sig = BootSignature::decode(&mut reader).silent()?;\n        if let Some(s) = cert {\n            let pem = MappedFile::open(s)?;\n            sig.certificate = Certificate::from_pem(pem)?;\n        };\n\n        sig.verify(self.payload()).log()\n    }\n\n    pub fn verify_for_cxx(&self) -> bool {\n        self.verify(None).is_ok()\n    }\n}\n\nenum Bytes {\n    Mapped(MappedFile),\n    Slice(&'static [u8]),\n}\n\nimpl AsRef<[u8]> for Bytes {\n    fn as_ref(&self) -> &[u8] {\n        match self {\n            Bytes::Mapped(m) => m.as_ref(),\n            Bytes::Slice(s) => s,\n        }\n    }\n}\n\nconst VERITY_PEM: &[u8] = include_bytes!(\"../../../tools/keys/verity.x509.pem\");\nconst VERITY_PK8: &[u8] = include_bytes!(\"../../../tools/keys/verity.pk8\");\n\npub fn sign_boot_image(\n    payload: &[u8],\n    name: &Utf8CStr,\n    cert: Option<&Utf8CStr>,\n    key: Option<&Utf8CStr>,\n) -> LoggedResult<Vec<u8>> {\n    let cert = match cert {\n        Some(s) => Bytes::Mapped(MappedFile::open(s)?),\n        None => Bytes::Slice(VERITY_PEM),\n    };\n    let key = match key {\n        Some(s) => Bytes::Mapped(MappedFile::open(s)?),\n        None => Bytes::Slice(VERITY_PK8),\n    };\n\n    // Parse cert and private key\n    let cert = Certificate::from_pem(cert)?;\n    let mut signer = Signer::from_private_key(key.as_ref())?;\n\n    // Sign image\n    let attr = AuthenticatedAttributes {\n        target: PrintableString::new(name.as_bytes())?,\n        length: payload.len() as u64,\n    };\n    signer.update(payload);\n    signer.update(attr.to_der()?.as_slice());\n    let sig = signer.sign()?;\n\n    // Create BootSignature DER\n    let alg_id = cert.signature_algorithm().clone();\n    let sig = BootSignature {\n        format_version: 1,\n        certificate: cert,\n        algorithm_identifier: alg_id,\n        authenticated_attributes: attr,\n        signature: OctetString::new(sig)?,\n    };\n    sig.to_der().log()\n}\n\npub fn sign_payload_for_cxx(payload: &[u8]) -> Vec<u8> {\n    sign_boot_image(payload, cstr!(\"/boot\"), None, None).unwrap_or_default()\n}\n"
  },
  {
    "path": "native/src/core/Cargo.toml",
    "content": "[package]\nname = \"magisk\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\ncrate-type = [\"staticlib\"]\npath = \"lib.rs\"\n\n[features]\ndefault = [\"check-signature\", \"check-client\", \"su-check-db\"]\n\n# Disable these features for easier debugging during development\ncheck-signature = []\ncheck-client = []\nsu-check-db = []\n\n[lints]\nworkspace = true\n\n[build-dependencies]\ncxx-gen = { workspace = true }\npb-rs = { workspace = true }\n\n[dependencies]\nbase = { workspace = true, features = [\"selinux\"] }\ncxx = { workspace = true }\nnum-traits = { workspace = true }\nnum-derive = { workspace = true }\nquick-protobuf = { workspace = true }\nbytemuck = { workspace = true, features = [\"derive\"] }\nthiserror = { workspace = true }\nbit-set = { workspace = true }\nnix = { workspace = true, features = [\"fs\", \"mount\", \"poll\", \"signal\", \"term\", \"user\", \"zerocopy\"] }\nbitflags = { workspace = true }\n"
  },
  {
    "path": "native/src/core/applet_stub.cpp",
    "content": "#include <core.hpp>\n\nint main(int argc, char *argv[]) {\n    if (argc < 1)\n        return 1;\n    cmdline_logging();\n    init_argv0(argc, argv);\n    umask(0);\n    return APPLET_STUB_MAIN(argc, argv);\n}\n"
  },
  {
    "path": "native/src/core/applets.cpp",
    "content": "#include <libgen.h>\n#include <sys/stat.h>\n\n#include <core.hpp>\n\nusing namespace std;\n\nstruct Applet {\n    string_view name;\n    int (*fn)(int, char *[]);\n};\n\nconstexpr Applet applets[] = {\n    { \"su\", su_client_main },\n    { \"resetprop\", resetprop_main },\n};\n\nconstexpr Applet private_applets[] = {\n    { \"zygisk\", zygisk_main },\n};\n\nint main(int argc, char *argv[]) {\n    if (argc < 1)\n        return 1;\n\n    cmdline_logging();\n    init_argv0(argc, argv);\n\n    Utf8CStr argv0 = basename(argv[0]);\n\n    umask(0);\n\n    if (argv[0][0] == '\\0') {\n        // When argv[0] is an empty string, we're calling private applets\n        if (argc < 2)\n            return 1;\n        --argc;\n        ++argv;\n        for (const auto &app : private_applets) {\n            if (argv[0] == app.name) {\n                return app.fn(argc, argv);\n            }\n        }\n        fprintf(stderr, \"%s: applet not found\\n\", argv[0]);\n        return 1;\n    }\n\n    if (argv0 == \"magisk\" || argv0 == \"magisk32\" || argv0 == \"magisk64\") {\n        if (argc > 1 && argv[1][0] != '-') {\n            // Calling applet with \"magisk [applet] args...\"\n            --argc;\n            ++argv;\n            argv0 = argv[0];\n        } else {\n            return magisk_main(argc, argv);\n        }\n    }\n\n    for (const auto &app : applets) {\n        if (argv0 == app.name) {\n            return app.fn(argc, argv);\n        }\n    }\n    fprintf(stderr, \"%s: applet not found\\n\", argv0.c_str());\n    return 1;\n}\n"
  },
  {
    "path": "native/src/core/bootstages.rs",
    "content": "use crate::consts::{APP_PACKAGE_NAME, BBPATH, DATABIN, MODULEROOT, SECURE_DIR};\nuse crate::daemon::MagiskD;\nuse crate::ffi::{\n    DbEntryKey, RequestCode, check_key_combo, exec_common_scripts, exec_module_scripts,\n    get_magisk_tmp, initialize_denylist,\n};\nuse crate::logging::setup_logfile;\nuse crate::module::disable_modules;\nuse crate::mount::{clean_mounts, setup_preinit_dir};\nuse crate::resetprop::get_prop;\nuse crate::selinux::restorecon;\nuse base::const_format::concatcp;\nuse base::{BufReadExt, FsPathBuilder, ResultExt, cstr, error, info};\nuse bitflags::bitflags;\nuse nix::fcntl::OFlag;\nuse std::io::BufReader;\nuse std::os::unix::net::UnixStream;\nuse std::process::{Command, Stdio};\nuse std::sync::atomic::Ordering;\n\nbitflags! {\n    #[derive(Default)]\n    pub struct BootState : u32 {\n        const PostFsDataDone = 1 << 0;\n        const LateStartDone = 1 << 1;\n        const BootComplete = 1 << 2;\n        const SafeMode = 1 << 3;\n    }\n}\n\nimpl MagiskD {\n    fn setup_magisk_env(&self) -> bool {\n        info!(\"* Initializing Magisk environment\");\n\n        let mut buf = cstr::buf::default();\n\n        let app_bin_dir = buf\n            .append_path(self.app_data_dir())\n            .append_path(\"0\")\n            .append_path(APP_PACKAGE_NAME)\n            .append_path(\"install\");\n\n        // Alternative binaries paths\n        let alt_bin_dirs = &[\n            cstr!(\"/cache/data_adb/magisk\"),\n            cstr!(\"/data/magisk\"),\n            app_bin_dir,\n        ];\n        for dir in alt_bin_dirs {\n            if dir.exists() {\n                cstr!(DATABIN).remove_all().ok();\n                dir.copy_to(cstr!(DATABIN)).ok();\n                dir.remove_all().ok();\n            }\n        }\n        cstr!(\"/cache/data_adb\").remove_all().ok();\n\n        // Directories in /data/adb\n        cstr!(SECURE_DIR).follow_link().chmod(0o700).log_ok();\n        cstr!(DATABIN).mkdir(0o755).log_ok();\n        cstr!(MODULEROOT).mkdir(0o755).log_ok();\n        cstr!(concatcp!(SECURE_DIR, \"/post-fs-data.d\"))\n            .mkdir(0o755)\n            .log_ok();\n        cstr!(concatcp!(SECURE_DIR, \"/service.d\"))\n            .mkdir(0o755)\n            .log_ok();\n        restorecon();\n\n        let busybox = cstr!(concatcp!(DATABIN, \"/busybox\"));\n        if !busybox.exists() {\n            return false;\n        }\n\n        let tmp_bb = buf.append_path(get_magisk_tmp()).append_path(BBPATH);\n        tmp_bb.mkdirs(0o755).ok();\n        tmp_bb.append_path(\"busybox\");\n        busybox.copy_to(tmp_bb).ok();\n        tmp_bb.follow_link().chmod(0o755).log_ok();\n\n        // Install busybox applets\n        Command::new(&tmp_bb)\n            .arg(\"--install\")\n            .arg(\"-s\")\n            .arg(tmp_bb.parent_dir().unwrap_or_default())\n            .stdout(Stdio::null())\n            .stderr(Stdio::null())\n            .status()\n            .log_ok();\n\n        // magisk32 and magiskpolicy are not installed into ramdisk and has to be copied\n        // from data to magisk tmp\n        let magisk32 = cstr!(concatcp!(DATABIN, \"/magisk32\"));\n        if magisk32.exists() {\n            let tmp = buf.append_path(get_magisk_tmp()).append_path(\"magisk32\");\n            magisk32.copy_to(tmp).log_ok();\n        }\n        let magiskpolicy = cstr!(concatcp!(DATABIN, \"/magiskpolicy\"));\n        if magiskpolicy.exists() {\n            let tmp = buf\n                .append_path(get_magisk_tmp())\n                .append_path(\"magiskpolicy\");\n            magiskpolicy.copy_to(tmp).log_ok();\n        }\n\n        true\n    }\n\n    fn post_fs_data(&self) -> bool {\n        setup_logfile();\n        info!(\"** post-fs-data mode running\");\n\n        self.preserve_stub_apk();\n\n        // Check secure dir\n        let secure_dir = cstr!(SECURE_DIR);\n        if !secure_dir.exists() {\n            if self.sdk_int < 24 {\n                secure_dir.mkdir(0o700).log_ok();\n            } else {\n                error!(\"* {} is not present, abort\", SECURE_DIR);\n                return true;\n            }\n        }\n\n        self.prune_su_access();\n\n        if !self.setup_magisk_env() {\n            error!(\"* Magisk environment incomplete, abort\");\n            return true;\n        }\n\n        // Check safe mode\n        let boot_cnt = self.get_db_setting(DbEntryKey::BootloopCount);\n        self.set_db_setting(DbEntryKey::BootloopCount, boot_cnt + 1)\n            .log()\n            .ok();\n        let safe_mode = boot_cnt >= 2\n            || get_prop(cstr!(\"persist.sys.safemode\")) == \"1\"\n            || get_prop(cstr!(\"ro.sys.safemode\")) == \"1\"\n            || check_key_combo();\n\n        if safe_mode {\n            info!(\"* Safe mode triggered\");\n            // Disable all modules and zygisk so next boot will be clean\n            disable_modules();\n            self.set_db_setting(DbEntryKey::ZygiskConfig, 0).log_ok();\n            return true;\n        }\n\n        exec_common_scripts(cstr!(\"post-fs-data\"));\n        self.zygisk_enabled.store(\n            self.get_db_setting(DbEntryKey::ZygiskConfig) != 0,\n            Ordering::Release,\n        );\n        initialize_denylist();\n        self.handle_modules();\n        clean_mounts();\n\n        false\n    }\n\n    fn late_start(&self) {\n        setup_logfile();\n        info!(\"** late_start service mode running\");\n\n        exec_common_scripts(cstr!(\"service\"));\n        if let Some(module_list) = self.module_list.get() {\n            exec_module_scripts(cstr!(\"service\"), module_list);\n        }\n    }\n\n    fn boot_complete(&self) {\n        setup_logfile();\n        info!(\"** boot-complete triggered\");\n\n        // Reset the bootloop counter once we have boot-complete\n        self.set_db_setting(DbEntryKey::BootloopCount, 0).log_ok();\n\n        // At this point it's safe to create the folder\n        let secure_dir = cstr!(SECURE_DIR);\n        if !secure_dir.exists() {\n            secure_dir.mkdir(0o700).log_ok();\n        }\n\n        setup_preinit_dir();\n        self.ensure_manager();\n        if self.zygisk_enabled.load(Ordering::Relaxed) {\n            self.zygisk.lock().reset(true);\n        }\n    }\n\n    pub fn boot_stage_handler(&self, client: UnixStream, code: RequestCode) {\n        // Make sure boot stage execution is always serialized\n        let mut state = self.boot_stage_lock.lock();\n\n        match code {\n            RequestCode::POST_FS_DATA => {\n                if check_data() && !state.contains(BootState::PostFsDataDone) {\n                    if self.post_fs_data() {\n                        state.insert(BootState::SafeMode);\n                    }\n                    state.insert(BootState::PostFsDataDone);\n                }\n            }\n            RequestCode::LATE_START => {\n                drop(client);\n                if state.contains(BootState::PostFsDataDone) && !state.contains(BootState::SafeMode)\n                {\n                    self.late_start();\n                    state.insert(BootState::LateStartDone);\n                }\n            }\n            RequestCode::BOOT_COMPLETE => {\n                drop(client);\n                if state.contains(BootState::PostFsDataDone) {\n                    state.insert(BootState::BootComplete);\n                    self.boot_complete()\n                }\n            }\n            _ => {}\n        }\n    }\n}\n\nfn check_data() -> bool {\n    if let Ok(file) = cstr!(\"/proc/mounts\").open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n        let mut mnt = false;\n        BufReader::new(file).for_each_line(|line| {\n            if line.contains(\" /data \") && !line.contains(\"tmpfs\") {\n                mnt = true;\n                return false;\n            }\n            true\n        });\n        if !mnt {\n            return false;\n        }\n        let crypto = get_prop(cstr!(\"ro.crypto.state\"));\n        return if !crypto.is_empty() {\n            if crypto != \"encrypted\" {\n                // Unencrypted, we can directly access data\n                true\n            } else {\n                // Encrypted, check whether vold is started\n                !get_prop(cstr!(\"init.svc.vold\")).is_empty()\n            }\n        } else {\n            // ro.crypto.state is not set, assume it's unencrypted\n            true\n        };\n    }\n    false\n}\n"
  },
  {
    "path": "native/src/core/build.rs",
    "content": "use pb_rs::ConfigBuilder;\nuse pb_rs::types::FileDescriptor;\n\nuse crate::codegen::gen_cxx_binding;\n\n#[path = \"../include/codegen.rs\"]\nmod codegen;\n\n#[allow(clippy::unwrap_used)]\nfn main() {\n    println!(\"cargo:rerun-if-changed=resetprop/proto/persistent_properties.proto\");\n\n    gen_cxx_binding(\"core-rs\");\n\n    let cb = ConfigBuilder::new(\n        &[\"resetprop/proto/persistent_properties.proto\"],\n        None,\n        Some(&\"resetprop/proto\"),\n        &[\".\"],\n    )\n    .unwrap();\n    FileDescriptor::run(\n        &cb.single_module(true)\n            .dont_use_cow(true)\n            .generate_getters(true)\n            .build(),\n    )\n    .unwrap();\n}\n"
  },
  {
    "path": "native/src/core/daemon.rs",
    "content": "use crate::bootstages::BootState;\nuse crate::consts::{\n    MAGISK_FILE_CON, MAGISK_FULL_VER, MAGISK_PROC_CON, MAGISK_VER_CODE, MAGISK_VERSION,\n    MAIN_CONFIG, MAIN_SOCKET, ROOTMNT, ROOTOVL,\n};\nuse crate::db::Sqlite3;\nuse crate::ffi::{\n    ModuleInfo, RequestCode, RespondCode, denylist_handler, get_magisk_tmp, scan_deny_apps,\n};\nuse crate::logging::{android_logging, magisk_logging, setup_logfile, start_log_daemon};\nuse crate::module::remove_modules;\nuse crate::package::ManagerInfo;\nuse crate::resetprop::{get_prop, set_prop};\nuse crate::selinux::restore_tmpcon;\nuse crate::socket::{IpcRead, IpcWrite};\nuse crate::su::SuInfo;\nuse crate::thread::ThreadPool;\nuse crate::zygisk::ZygiskState;\nuse base::const_format::concatcp;\nuse base::{\n    AtomicArc, BufReadExt, FileAttr, FsPathBuilder, LoggedResult, ReadExt, ResultExt, Utf8CStr,\n    Utf8CStrBuf, WriteExt, cstr, fork_dont_care, info, libc, log_err, set_nice_name,\n};\nuse nix::fcntl::OFlag;\nuse nix::mount::MsFlags;\nuse nix::sys::signal::SigSet;\nuse nix::unistd::{dup2_stderr, dup2_stdin, dup2_stdout, getpid, getuid, setsid};\nuse num_traits::AsPrimitive;\nuse std::fmt::Write as _;\nuse std::io::{BufReader, Write};\nuse std::os::fd::{AsFd, AsRawFd, IntoRawFd, RawFd};\nuse std::os::unix::net::{UCred, UnixListener, UnixStream};\nuse std::process::{Command, exit};\nuse std::sync::OnceLock;\nuse std::sync::atomic::{AtomicBool, Ordering};\nuse std::sync::nonpoison::Mutex;\nuse std::time::Duration;\n\n// Global magiskd singleton\npub static MAGISKD: OnceLock<MagiskD> = OnceLock::new();\n\npub const AID_ROOT: i32 = 0;\npub const AID_SHELL: i32 = 2000;\npub const AID_APP_START: i32 = 10000;\npub const AID_APP_END: i32 = 19999;\npub const AID_USER_OFFSET: i32 = 100000;\n\npub const fn to_app_id(uid: i32) -> i32 {\n    uid % AID_USER_OFFSET\n}\n\npub const fn to_user_id(uid: i32) -> i32 {\n    uid / AID_USER_OFFSET\n}\n\n#[derive(Default)]\npub struct MagiskD {\n    pub sql_connection: Mutex<Option<Sqlite3>>,\n    pub manager_info: Mutex<ManagerInfo>,\n    pub boot_stage_lock: Mutex<BootState>,\n    pub module_list: OnceLock<Vec<ModuleInfo>>,\n    pub zygisk_enabled: AtomicBool,\n    pub zygisk: Mutex<ZygiskState>,\n    pub cached_su_info: AtomicArc<SuInfo>,\n    pub sdk_int: i32,\n    pub is_emulator: bool,\n    is_recovery: bool,\n    exe_attr: FileAttr,\n}\n\nimpl MagiskD {\n    pub fn get() -> &'static MagiskD {\n        unsafe { MAGISKD.get().unwrap_unchecked() }\n    }\n\n    pub fn sdk_int(&self) -> i32 {\n        self.sdk_int\n    }\n\n    pub fn app_data_dir(&self) -> &'static Utf8CStr {\n        if self.sdk_int >= 24 {\n            cstr!(\"/data/user_de\")\n        } else {\n            cstr!(\"/data/user\")\n        }\n    }\n\n    fn handle_request_sync(&self, mut client: UnixStream, code: RequestCode) {\n        match code {\n            RequestCode::CHECK_VERSION => {\n                #[cfg(debug_assertions)]\n                let s = concatcp!(MAGISK_VERSION, \":MAGISK:D\");\n                #[cfg(not(debug_assertions))]\n                let s = concatcp!(MAGISK_VERSION, \":MAGISK:R\");\n\n                client.write_encodable(s).log_ok();\n            }\n            RequestCode::CHECK_VERSION_CODE => {\n                client.write_pod(&MAGISK_VER_CODE).log_ok();\n            }\n            RequestCode::START_DAEMON => {\n                setup_logfile();\n            }\n            RequestCode::STOP_DAEMON => {\n                // Unmount all overlays\n                denylist_handler(-1);\n\n                // Restore native bridge property\n                self.zygisk.lock().restore_prop();\n\n                client.write_pod(&0).log_ok();\n\n                // Terminate the daemon!\n                exit(0);\n            }\n            _ => {}\n        }\n    }\n\n    fn handle_request_async(&self, mut client: UnixStream, code: RequestCode, cred: UCred) {\n        match code {\n            RequestCode::DENYLIST => {\n                denylist_handler(client.into_raw_fd());\n            }\n            RequestCode::SUPERUSER => {\n                self.su_daemon_handler(client, cred);\n            }\n            RequestCode::ZYGOTE_RESTART => {\n                info!(\"** zygote restarted\");\n                self.prune_su_access();\n                scan_deny_apps();\n                if self.zygisk_enabled.load(Ordering::Relaxed) {\n                    self.zygisk.lock().reset(false);\n                }\n            }\n            RequestCode::SQLITE_CMD => {\n                self.db_exec_for_cli(client).ok();\n            }\n            RequestCode::REMOVE_MODULES => {\n                let do_reboot: bool = client.read_decodable().log().unwrap_or_default();\n                remove_modules();\n                client.write_pod(&0).log_ok();\n                if do_reboot {\n                    self.reboot();\n                }\n            }\n            RequestCode::ZYGISK => {\n                self.zygisk_handler(client);\n            }\n            _ => {}\n        }\n    }\n\n    fn reboot(&self) {\n        if self.is_recovery {\n            Command::new(\"/system/bin/reboot\").arg(\"recovery\").status()\n        } else {\n            Command::new(\"/system/bin/reboot\").status()\n        }\n        .ok();\n    }\n\n    #[cfg(feature = \"check-client\")]\n    fn is_client(&self, pid: i32) -> bool {\n        let mut buf = cstr::buf::new::<32>();\n        write!(buf, \"/proc/{pid}/exe\").ok();\n        if let Ok(attr) = buf.follow_link().get_attr() {\n            attr.st.st_dev == self.exe_attr.st.st_dev && attr.st.st_ino == self.exe_attr.st.st_ino\n        } else {\n            false\n        }\n    }\n\n    #[cfg(not(feature = \"check-client\"))]\n    fn is_client(&self, pid: i32) -> bool {\n        true\n    }\n\n    fn handle_requests(&'static self, mut client: UnixStream) {\n        let Ok(cred) = client.peer_cred() else {\n            // Client died\n            return;\n        };\n\n        // There are no abstractions for SO_PEERSEC yet, call the raw C API.\n        let mut context = cstr::buf::new::<256>();\n        unsafe {\n            let mut len: libc::socklen_t = context.capacity().as_();\n            libc::getsockopt(\n                client.as_raw_fd(),\n                libc::SOL_SOCKET,\n                libc::SO_PEERSEC,\n                context.as_mut_ptr().cast(),\n                &mut len,\n            );\n        }\n        context.rebuild().ok();\n\n        let is_root = cred.uid == 0;\n        let is_shell = cred.uid == 2000;\n        let is_zygote = &context == \"u:r:zygote:s0\";\n\n        if !is_root && !is_zygote && !self.is_client(cred.pid.unwrap_or(-1)) {\n            // Unsupported client state\n            client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();\n            return;\n        }\n\n        let mut code = -1;\n        client.read_pod(&mut code).ok();\n        if !(0..RequestCode::END.repr).contains(&code)\n            || code == RequestCode::_SYNC_BARRIER_.repr\n            || code == RequestCode::_STAGE_BARRIER_.repr\n        {\n            // Unknown request code\n            return;\n        }\n\n        let code = RequestCode { repr: code };\n\n        // Permission checks\n        match code {\n            RequestCode::POST_FS_DATA\n            | RequestCode::LATE_START\n            | RequestCode::BOOT_COMPLETE\n            | RequestCode::ZYGOTE_RESTART\n            | RequestCode::SQLITE_CMD\n            | RequestCode::DENYLIST\n            | RequestCode::STOP_DAEMON => {\n                if !is_root {\n                    client.write_pod(&RespondCode::ROOT_REQUIRED.repr).log_ok();\n                    return;\n                }\n            }\n            RequestCode::REMOVE_MODULES => {\n                if !is_root && !is_shell {\n                    // Only allow root and ADB shell to remove modules\n                    client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();\n                    return;\n                }\n            }\n            RequestCode::ZYGISK => {\n                if !is_zygote {\n                    // Invalid client context\n                    client.write_pod(&RespondCode::ACCESS_DENIED.repr).log_ok();\n                    return;\n                }\n            }\n            _ => {}\n        }\n\n        if client.write_pod(&RespondCode::OK.repr).is_err() {\n            return;\n        }\n\n        if code.repr < RequestCode::_SYNC_BARRIER_.repr {\n            self.handle_request_sync(client, code)\n        } else if code.repr < RequestCode::_STAGE_BARRIER_.repr {\n            ThreadPool::exec_task(move || {\n                self.handle_request_async(client, code, cred);\n            })\n        } else {\n            ThreadPool::exec_task(move || {\n                self.boot_stage_handler(client, code);\n            })\n        }\n    }\n}\n\nfn switch_cgroup(cgroup: &str, pid: i32) {\n    let mut buf = cstr::buf::new::<64>()\n        .join_path(cgroup)\n        .join_path(\"cgroup.procs\");\n    if !buf.exists() {\n        return;\n    }\n    if let Ok(mut file) = buf.open(OFlag::O_WRONLY | OFlag::O_APPEND | OFlag::O_CLOEXEC) {\n        buf.clear();\n        write!(buf, \"{pid}\").ok();\n        file.write_all(buf.as_bytes()).log_ok();\n    }\n}\n\nfn daemon_entry() {\n    set_nice_name(cstr!(\"magiskd\"));\n    android_logging();\n\n    // Block all signals\n    SigSet::all().thread_set_mask().log_ok();\n\n    // Swap out the original stdio\n    if let Ok(null) = cstr!(\"/dev/null\").open(OFlag::O_WRONLY).log() {\n        dup2_stdout(null.as_fd()).log_ok();\n        dup2_stderr(null.as_fd()).log_ok();\n    }\n    if let Ok(zero) = cstr!(\"/dev/zero\").open(OFlag::O_RDONLY).log() {\n        dup2_stdin(zero).log_ok();\n    }\n\n    setsid().log_ok();\n\n    // Make sure the current context is magisk\n    if let Ok(mut current) =\n        cstr!(\"/proc/self/attr/current\").open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)\n    {\n        let con = cstr!(MAGISK_PROC_CON);\n        current.write_all(con.as_bytes_with_nul()).log_ok();\n    }\n\n    start_log_daemon();\n    magisk_logging();\n    info!(\"Magisk {MAGISK_FULL_VER} daemon started\");\n\n    let is_emulator = get_prop(cstr!(\"ro.kernel.qemu\")) == \"1\"\n        || get_prop(cstr!(\"ro.boot.qemu\")) == \"1\"\n        || get_prop(cstr!(\"ro.product.device\")).contains(\"vsoc\");\n\n    // Load config status\n    let magisk_tmp = get_magisk_tmp();\n    let mut tmp_path = cstr::buf::new::<64>()\n        .join_path(magisk_tmp)\n        .join_path(MAIN_CONFIG);\n    let mut is_recovery = false;\n    if let Ok(main_config) = tmp_path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n        BufReader::new(main_config).for_each_prop(|key, val| {\n            if key == \"RECOVERYMODE\" {\n                is_recovery = val == \"true\";\n                return false;\n            }\n            true\n        });\n    }\n    tmp_path.truncate(magisk_tmp.len());\n\n    let mut sdk_int = -1;\n    if let Ok(build_prop) = cstr!(\"/system/build.prop\").open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n        BufReader::new(build_prop).for_each_prop(|key, val| {\n            if key == \"ro.build.version.sdk\" {\n                sdk_int = val.parse::<i32>().unwrap_or(-1);\n                return false;\n            }\n            true\n        });\n    }\n    if sdk_int < 0 {\n        // In case some devices do not store this info in build.prop, fallback to getprop\n        sdk_int = get_prop(cstr!(\"ro.build.version.sdk\"))\n            .parse::<i32>()\n            .unwrap_or(-1);\n    }\n    info!(\"* Device API level: {sdk_int}\");\n\n    restore_tmpcon().log_ok();\n\n    // Escape from cgroup\n    let pid = getpid().as_raw();\n    switch_cgroup(\"/acct\", pid);\n    switch_cgroup(\"/dev/cg2_bpf\", pid);\n    switch_cgroup(\"/sys/fs/cgroup\", pid);\n    if get_prop(cstr!(\"ro.config.per_app_memcg\")) != \"false\" {\n        switch_cgroup(\"/dev/memcg/apps\", pid);\n    }\n\n    // Samsung workaround #7887\n    if cstr!(\"/system_ext/app/mediatek-res/mediatek-res.apk\").exists() {\n        set_prop(cstr!(\"ro.vendor.mtk_model\"), cstr!(\"0\"));\n    }\n\n    // Cleanup pre-init mounts\n    tmp_path.append_path(ROOTMNT);\n    if let Ok(mount_list) = tmp_path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n        BufReader::new(mount_list).for_each_line(|line| {\n            line.truncate(line.trim_end().len());\n            let item = Utf8CStr::from_string(line);\n            item.unmount().log_ok();\n            true\n        })\n    }\n    tmp_path.truncate(magisk_tmp.len());\n\n    // Remount rootfs as read-only if requested\n    if std::env::var_os(\"REMOUNT_ROOT\").is_some() {\n        cstr!(\"/\").remount_mount_flags(MsFlags::MS_RDONLY).log_ok();\n        unsafe { std::env::remove_var(\"REMOUNT_ROOT\") };\n    }\n\n    // Remove all pre-init overlay files to free-up memory\n    tmp_path.append_path(ROOTOVL);\n    tmp_path.remove_all().ok();\n    tmp_path.truncate(magisk_tmp.len());\n\n    let exe_attr = cstr!(\"/proc/self/exe\")\n        .follow_link()\n        .get_attr()\n        .log()\n        .unwrap_or_default();\n\n    let daemon = MagiskD {\n        sdk_int,\n        is_emulator,\n        is_recovery,\n        exe_attr,\n        ..Default::default()\n    };\n    MAGISKD.set(daemon).ok();\n\n    let sock_path = cstr::buf::new::<64>()\n        .join_path(get_magisk_tmp())\n        .join_path(MAIN_SOCKET);\n    sock_path.remove().ok();\n\n    let Ok(sock) = UnixListener::bind(&sock_path).log() else {\n        exit(1);\n    };\n\n    sock_path.follow_link().chmod(0o666).log_ok();\n    sock_path.set_secontext(cstr!(MAGISK_FILE_CON)).log_ok();\n\n    // Loop forever to listen for requests\n    let daemon = MagiskD::get();\n    for client in sock.incoming() {\n        if let Ok(client) = client.log() {\n            daemon.handle_requests(client);\n        } else {\n            exit(1);\n        }\n    }\n}\n\npub fn connect_daemon(code: RequestCode, create: bool) -> LoggedResult<UnixStream> {\n    let sock_path = cstr::buf::new::<64>()\n        .join_path(get_magisk_tmp())\n        .join_path(MAIN_SOCKET);\n\n    fn send_request(code: RequestCode, mut socket: UnixStream) -> LoggedResult<UnixStream> {\n        socket.write_pod(&code.repr).log_ok();\n        let mut res = -1;\n        socket.read_pod(&mut res).log_ok();\n        let res = RespondCode { repr: res };\n        match res {\n            RespondCode::OK => Ok(socket),\n            RespondCode::ROOT_REQUIRED => {\n                log_err!(\"Root is required for this operation\")\n            }\n            RespondCode::ACCESS_DENIED => {\n                log_err!(\"Accessed denied\")\n            }\n            _ => {\n                log_err!(\"Daemon error\")\n            }\n        }\n    }\n\n    match UnixStream::connect(&sock_path) {\n        Ok(socket) => send_request(code, socket),\n        Err(e) => {\n            if !create || !getuid().is_root() {\n                return log_err!(\"Cannot connect to daemon: {e}\");\n            }\n\n            let mut buf = cstr::buf::new::<64>();\n            if cstr!(\"/proc/self/exe\").read_link(&mut buf).is_err()\n                || !buf.starts_with(get_magisk_tmp().as_str())\n            {\n                return log_err!(\"Start daemon on magisk tmpfs\");\n            }\n\n            // Fork a process and run the daemon\n            if fork_dont_care() == 0 {\n                daemon_entry();\n                exit(0);\n            }\n\n            // In the client, we keep retry and connect to the socket\n            loop {\n                if let Ok(socket) = UnixStream::connect(&sock_path) {\n                    return send_request(code, socket);\n                } else {\n                    std::thread::sleep(Duration::from_millis(100));\n                }\n            }\n        }\n    }\n}\n\npub fn connect_daemon_for_cxx(code: RequestCode, create: bool) -> RawFd {\n    connect_daemon(code, create)\n        .map(IntoRawFd::into_raw_fd)\n        .unwrap_or(-1)\n}\n"
  },
  {
    "path": "native/src/core/db.rs",
    "content": "#![allow(improper_ctypes, improper_ctypes_definitions)]\nuse crate::daemon::{MAGISKD, MagiskD};\nuse crate::ffi::{\n    DbEntryKey, DbStatement, DbValues, MntNsMode, open_and_init_db, sqlite3, sqlite3_errstr,\n};\nuse crate::socket::{IpcRead, IpcWrite};\nuse DbArg::{Integer, Text};\nuse base::{LoggedResult, ResultExt, Utf8CStr};\nuse num_derive::FromPrimitive;\nuse num_traits::FromPrimitive;\nuse std::ffi::c_void;\nuse std::io::{BufReader, BufWriter};\nuse std::os::unix::net::UnixStream;\nuse std::pin::Pin;\nuse std::ptr;\nuse std::ptr::NonNull;\nuse thiserror::Error;\n\nfn sqlite_err_str(code: i32) -> &'static Utf8CStr {\n    // SAFETY: sqlite3 always returns UTF-8 strings\n    unsafe { Utf8CStr::from_ptr_unchecked(sqlite3_errstr(code)) }\n}\n\n#[repr(transparent)]\n#[derive(Error, Debug)]\n#[error(\"sqlite3: {}\", sqlite_err_str(self.0))]\npub struct SqliteError(i32);\n\npub type SqliteResult<T> = Result<T, SqliteError>;\n\npub trait SqliteReturn {\n    fn sql_result(self) -> SqliteResult<()>;\n}\n\nimpl SqliteReturn for i32 {\n    fn sql_result(self) -> SqliteResult<()> {\n        if self != 0 {\n            Err(SqliteError(self))\n        } else {\n            Ok(())\n        }\n    }\n}\n\npub trait SqlTable {\n    fn on_row(&mut self, columns: &[String], values: &DbValues);\n}\n\nimpl<T> SqlTable for T\nwhere\n    T: FnMut(&[String], &DbValues),\n{\n    fn on_row(&mut self, columns: &[String], values: &DbValues) {\n        self.call_mut((columns, values))\n    }\n}\n\n#[derive(Default)]\npub struct DbSettings {\n    pub root_access: RootAccess,\n    pub multiuser_mode: MultiuserMode,\n    pub mnt_ns: MntNsMode,\n    pub boot_count: i32,\n    pub denylist: bool,\n    pub zygisk: bool,\n}\n\n#[repr(i32)]\n#[derive(Default, FromPrimitive)]\npub enum RootAccess {\n    Disabled,\n    AppsOnly,\n    AdbOnly,\n    #[default]\n    AppsAndAdb,\n}\n\n#[repr(i32)]\n#[derive(Default, FromPrimitive)]\npub enum MultiuserMode {\n    #[default]\n    OwnerOnly,\n    OwnerManaged,\n    User,\n}\n\nimpl Default for MntNsMode {\n    fn default() -> Self {\n        MntNsMode::Requester\n    }\n}\n\nimpl DbEntryKey {\n    fn to_str(self) -> &'static str {\n        match self {\n            DbEntryKey::RootAccess => \"root_access\",\n            DbEntryKey::SuMultiuserMode => \"multiuser_mode\",\n            DbEntryKey::SuMntNs => \"mnt_ns\",\n            DbEntryKey::DenylistConfig => \"denylist\",\n            DbEntryKey::ZygiskConfig => \"zygisk\",\n            DbEntryKey::BootloopCount => \"bootloop\",\n            DbEntryKey::SuManager => \"requester\",\n            _ => \"\",\n        }\n    }\n}\n\nimpl SqlTable for DbSettings {\n    fn on_row(&mut self, columns: &[String], values: &DbValues) {\n        let mut key = \"\";\n        let mut value = 0;\n        for (i, column) in columns.iter().enumerate() {\n            if column == \"key\" {\n                key = values.get_text(i as i32);\n            } else if column == \"value\" {\n                value = values.get_int(i as i32);\n            }\n        }\n        match key {\n            \"root_access\" => self.root_access = RootAccess::from_i32(value).unwrap_or_default(),\n            \"multiuser_mode\" => {\n                self.multiuser_mode = MultiuserMode::from_i32(value).unwrap_or_default()\n            }\n            \"mnt_ns\" => self.mnt_ns = MntNsMode { repr: value },\n            \"denylist\" => self.denylist = value != 0,\n            \"zygisk\" => self.zygisk = value != 0,\n            \"bootloop\" => self.boot_count = value,\n            _ => {}\n        }\n    }\n}\n\n#[repr(transparent)]\npub struct Sqlite3(NonNull<sqlite3>);\nunsafe impl Send for Sqlite3 {}\n\ntype SqlBindCallback = Option<unsafe extern \"C\" fn(*mut c_void, i32, Pin<&mut DbStatement>) -> i32>;\ntype SqlExecCallback = Option<unsafe extern \"C\" fn(*mut c_void, &[String], &DbValues)>;\n\nunsafe extern \"C\" {\n    fn sql_exec_impl(\n        db: *mut sqlite3,\n        sql: &str,\n        bind_callback: SqlBindCallback,\n        bind_cookie: *mut c_void,\n        exec_callback: SqlExecCallback,\n        exec_cookie: *mut c_void,\n    ) -> i32;\n}\n\npub enum DbArg<'a> {\n    Text(&'a str),\n    Integer(i64),\n}\n\nstruct DbArgs<'a> {\n    args: &'a [DbArg<'a>],\n    curr: usize,\n}\n\nunsafe extern \"C\" fn bind_arguments(v: *mut c_void, idx: i32, stmt: Pin<&mut DbStatement>) -> i32 {\n    unsafe {\n        let args = &mut *(v as *mut DbArgs<'_>);\n        if args.curr < args.args.len() {\n            let arg = &args.args[args.curr];\n            args.curr += 1;\n            match *arg {\n                Text(v) => stmt.bind_text(idx, v),\n                Integer(v) => stmt.bind_int64(idx, v),\n            }\n        } else {\n            0\n        }\n    }\n}\n\nunsafe extern \"C\" fn read_db_row<T: SqlTable>(\n    v: *mut c_void,\n    columns: &[String],\n    values: &DbValues,\n) {\n    unsafe {\n        let table = &mut *(v as *mut T);\n        table.on_row(columns, values);\n    }\n}\n\nimpl MagiskD {\n    fn with_db<F: FnOnce(*mut sqlite3) -> i32>(&self, f: F) -> i32 {\n        let mut db = self.sql_connection.lock();\n        if db.is_none() {\n            let raw_db = open_and_init_db();\n            *db = NonNull::new(raw_db).map(Sqlite3);\n        }\n        match *db {\n            Some(ref mut db) => f(db.0.as_ptr()),\n            _ => -1,\n        }\n    }\n\n    fn db_exec_impl(\n        &self,\n        sql: &str,\n        args: &[DbArg],\n        exec_callback: SqlExecCallback,\n        exec_cookie: *mut c_void,\n    ) -> i32 {\n        let mut bind_callback: SqlBindCallback = None;\n        let mut bind_cookie: *mut c_void = ptr::null_mut();\n        let mut db_args = DbArgs { args, curr: 0 };\n        if !args.is_empty() {\n            bind_callback = Some(bind_arguments);\n            bind_cookie = (&mut db_args) as *mut DbArgs as *mut c_void;\n        }\n        self.with_db(|db| unsafe {\n            sql_exec_impl(\n                db,\n                sql,\n                bind_callback,\n                bind_cookie,\n                exec_callback,\n                exec_cookie,\n            )\n        })\n    }\n\n    pub fn db_exec_with_rows<T: SqlTable>(&self, sql: &str, args: &[DbArg], out: &mut T) -> i32 {\n        self.db_exec_impl(\n            sql,\n            args,\n            Some(read_db_row::<T>),\n            out as *mut T as *mut c_void,\n        )\n    }\n\n    pub fn db_exec(&self, sql: &str, args: &[DbArg]) -> i32 {\n        self.db_exec_impl(sql, args, None, ptr::null_mut())\n    }\n\n    pub fn set_db_setting(&self, key: DbEntryKey, value: i32) -> SqliteResult<()> {\n        self.db_exec(\n            \"INSERT OR REPLACE INTO settings (key,value) VALUES(?,?)\",\n            &[Text(key.to_str()), Integer(value as i64)],\n        )\n        .sql_result()\n    }\n\n    pub fn get_db_setting(&self, key: DbEntryKey) -> i32 {\n        // Get default values\n        let mut val = match key {\n            DbEntryKey::RootAccess => RootAccess::default() as i32,\n            DbEntryKey::SuMultiuserMode => MultiuserMode::default() as i32,\n            DbEntryKey::SuMntNs => MntNsMode::default().repr,\n            DbEntryKey::DenylistConfig => 0,\n            DbEntryKey::ZygiskConfig => self.is_emulator as i32,\n            DbEntryKey::BootloopCount => 0,\n            _ => -1,\n        };\n        let mut func = |_: &[String], values: &DbValues| {\n            val = values.get_int(0);\n        };\n        self.db_exec_with_rows(\n            \"SELECT value FROM settings WHERE key=?\",\n            &[Text(key.to_str())],\n            &mut func,\n        )\n        .sql_result()\n        .log()\n        .ok();\n        val\n    }\n\n    pub fn get_db_settings(&self) -> SqliteResult<DbSettings> {\n        let mut cfg = DbSettings {\n            zygisk: self.is_emulator,\n            ..Default::default()\n        };\n        self.db_exec_with_rows(\"SELECT * FROM settings\", &[], &mut cfg)\n            .sql_result()?;\n        Ok(cfg)\n    }\n\n    pub fn get_db_string(&self, key: DbEntryKey) -> String {\n        let mut val = \"\".to_string();\n        let mut func = |_: &[String], values: &DbValues| {\n            val.push_str(values.get_text(0));\n        };\n        self.db_exec_with_rows(\n            \"SELECT value FROM strings WHERE key=?\",\n            &[Text(key.to_str())],\n            &mut func,\n        )\n        .sql_result()\n        .log()\n        .ok();\n        val\n    }\n\n    pub fn rm_db_string(&self, key: DbEntryKey) -> SqliteResult<()> {\n        self.db_exec(\"DELETE FROM strings WHERE key=?\", &[Text(key.to_str())])\n            .sql_result()\n    }\n\n    pub fn db_exec_for_cli(&self, mut file: UnixStream) -> LoggedResult<()> {\n        let mut reader = BufReader::new(&mut file);\n        let sql: String = reader.read_decodable()?;\n        let mut writer = BufWriter::new(&mut file);\n        let mut output_fn = |columns: &[String], values: &DbValues| {\n            let mut out = \"\".to_string();\n            for (i, column) in columns.iter().enumerate() {\n                if i != 0 {\n                    out.push('|');\n                }\n                out.push_str(column);\n                out.push('=');\n                out.push_str(values.get_text(i as i32));\n            }\n            writer.write_encodable(&out).log_ok();\n        };\n        self.db_exec_with_rows(&sql, &[], &mut output_fn);\n        writer.write_encodable(\"\").log()\n    }\n}\n\nimpl MagiskD {\n    pub fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool {\n        self.set_db_setting(key, value).log().is_ok()\n    }\n}\n\n#[unsafe(export_name = \"sql_exec_rs\")]\nunsafe extern \"C\" fn sql_exec_for_cxx(\n    sql: &str,\n    bind_callback: SqlBindCallback,\n    bind_cookie: *mut c_void,\n    exec_callback: SqlExecCallback,\n    exec_cookie: *mut c_void,\n) -> i32 {\n    unsafe {\n        MAGISKD.get().unwrap_unchecked().with_db(|db| {\n            sql_exec_impl(\n                db,\n                sql,\n                bind_callback,\n                bind_cookie,\n                exec_callback,\n                exec_cookie,\n            )\n        })\n    }\n}\n"
  },
  {
    "path": "native/src/core/deny/cli.cpp",
    "content": "#include <sys/wait.h>\n#include <sys/mount.h>\n\n#include <core.hpp>\n\n#include \"deny.hpp\"\n\nusing namespace std;\n\n[[noreturn]] static void usage() {\n    fprintf(stderr,\nR\"EOF(DenyList Config CLI\n\nUsage: magisk --denylist [action [arguments...] ]\nActions:\n   status          Return the enforcement status\n   enable          Enable denylist enforcement\n   disable         Disable denylist enforcement\n   add PKG [PROC]  Add a new target to the denylist\n   rm PKG [PROC]   Remove target(s) from the denylist\n   ls              Print the current denylist\n   exec CMDs...    Execute commands in isolated mount\n                   namespace and do all unmounts\n\n)EOF\");\n    exit(1);\n}\n\nvoid denylist_handler(int client) {\n    if (client < 0) {\n        revert_unmount();\n        return;\n    }\n\n    int req = read_int(client);\n    int res = DenyResponse::ERROR;\n\n    switch (req) {\n    case DenyRequest::ENFORCE:\n        res = enable_deny();\n        break;\n    case DenyRequest::DISABLE:\n        res = disable_deny();\n        break;\n    case DenyRequest::ADD:\n        res = add_list(client);\n        break;\n    case DenyRequest::REMOVE:\n        res = rm_list(client);\n        break;\n    case DenyRequest::LIST:\n        ls_list(client);\n        return;\n    case DenyRequest::STATUS:\n        res = denylist_enforced ? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED;\n        break;\n    default:\n        // Unknown request code\n        break;\n    }\n    write_int(client, res);\n    close(client);\n}\n\nint denylist_cli(rust::Vec<rust::String> &args) {\n    if (args.empty())\n        usage();\n\n    // Convert rust strings into c strings\n    size_t argc = args.size();\n    std::vector<const char *> argv;\n    ranges::transform(args, std::back_inserter(argv), [](rust::String &arg) { return arg.c_str(); });\n    // End with nullptr\n    argv.push_back(nullptr);\n\n    int req;\n    if (argv[0] == \"enable\"sv)\n        req = DenyRequest::ENFORCE;\n    else if (argv[0] == \"disable\"sv)\n        req = DenyRequest::DISABLE;\n    else if (argv[0] == \"add\"sv)\n        req = DenyRequest::ADD;\n    else if (argv[0] == \"rm\"sv)\n        req = DenyRequest::REMOVE;\n    else if (argv[0] == \"ls\"sv)\n        req = DenyRequest::LIST;\n    else if (argv[0] == \"status\"sv)\n        req = DenyRequest::STATUS;\n    else if (argv[0] == \"exec\"sv && argc > 1) {\n        xunshare(CLONE_NEWNS);\n        xmount(nullptr, \"/\", nullptr, MS_PRIVATE | MS_REC, nullptr);\n        revert_unmount();\n        execvp(argv[1], (char **) argv.data() + 1);\n        exit(1);\n    } else {\n        usage();\n    }\n\n    // Send request\n    int fd = connect_daemon(RequestCode::DENYLIST);\n    write_int(fd, req);\n    if (req == DenyRequest::ADD || req == DenyRequest::REMOVE) {\n        write_string(fd, argv[1]);\n        write_string(fd, argv[2] ? argv[2] : \"\");\n    }\n\n    // Get response\n    int res = read_int(fd);\n    if (res < 0 || res >= DenyResponse::END)\n        res = DenyResponse::ERROR;\n    switch (res) {\n    case DenyResponse::NOT_ENFORCED:\n        fprintf(stderr, \"Denylist is not enforced\\n\");\n        goto return_code;\n    case DenyResponse::ENFORCED:\n        fprintf(stderr, \"Denylist is enforced\\n\");\n        goto return_code;\n    case DenyResponse::ITEM_EXIST:\n        fprintf(stderr, \"Target already exists in denylist\\n\");\n        goto return_code;\n    case DenyResponse::ITEM_NOT_EXIST:\n        fprintf(stderr, \"Target does not exist in denylist\\n\");\n        goto return_code;\n    case DenyResponse::NO_NS:\n        fprintf(stderr, \"The kernel does not support mount namespace\\n\");\n        goto return_code;\n    case DenyResponse::INVALID_PKG:\n        fprintf(stderr, \"Invalid package / process name\\n\");\n        goto return_code;\n    case DenyResponse::ERROR:\n        fprintf(stderr, \"deny: Daemon error\\n\");\n        return -1;\n    case DenyResponse::OK:\n        break;\n    default:\n        __builtin_unreachable();\n    }\n\n    if (req == DenyRequest::LIST) {\n        string out;\n        for (;;) {\n            read_string(fd, out);\n            if (out.empty())\n                break;\n            printf(\"%s\\n\", out.data());\n        }\n    }\n\nreturn_code:\n    return req == DenyRequest::STATUS ? res != DenyResponse::ENFORCED : res != DenyResponse::OK;\n}\n"
  },
  {
    "path": "native/src/core/deny/deny.hpp",
    "content": "#pragma once\n\n#include <string_view>\n\n#define ISOLATED_MAGIC \"isolated\"\n\nnamespace DenyRequest {\nenum : int {\n    ENFORCE,\n    DISABLE,\n    ADD,\n    REMOVE,\n    LIST,\n    STATUS,\n\n    END\n};\n}\n\nnamespace DenyResponse {\nenum : int {\n    OK,\n    ENFORCED,\n    NOT_ENFORCED,\n    ITEM_EXIST,\n    ITEM_NOT_EXIST,\n    INVALID_PKG,\n    NO_NS,\n    ERROR,\n\n    END\n};\n}\n\n// CLI entries\nint enable_deny();\nint disable_deny();\nint add_list(int client);\nint rm_list(int client);\nvoid ls_list(int client);\n\nbool proc_context_match(int pid, std::string_view context);\nvoid *logcat(void *arg);\nextern bool logcat_exit;\n"
  },
  {
    "path": "native/src/core/deny/logcat.cpp",
    "content": "#include <unistd.h>\n#include <android/log.h>\n#include <sys/syscall.h>\n#include <string>\n#include <map>\n\n#include <core.hpp>\n\n#include \"deny.hpp\"\n\nusing namespace std;\n\nstruct logger_entry {\n    uint16_t len;      /* length of the payload */\n    uint16_t hdr_size; /* sizeof(struct logger_entry) */\n    int32_t pid;       /* generating process's pid */\n    uint32_t tid;      /* generating process's tid */\n    uint32_t sec;      /* seconds since Epoch */\n    uint32_t nsec;     /* nanoseconds */\n    uint32_t lid;      /* log id of the payload, bottom 4 bits currently */\n    uint32_t uid;      /* generating process's uid */\n};\n\n#define LOGGER_ENTRY_MAX_LEN (5 * 1024)\nstruct log_msg {\n    union [[gnu::aligned(4)]] {\n        unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];\n        struct logger_entry entry;\n    };\n};\n\nstruct AndroidLogEntry {\n    time_t tv_sec;\n    long tv_nsec;\n    android_LogPriority priority;\n    int32_t uid;\n    int32_t pid;\n    int32_t tid;\n    const char *tag;\n    size_t tagLen;\n    size_t messageLen;\n    const char *message;\n};\n\nstruct [[gnu::packed]] android_event_header_t {\n    int32_t tag;    // Little Endian Order\n};\n\nstruct [[gnu::packed]] android_event_int_t {\n    int8_t type;    // EVENT_TYPE_INT\n    int32_t data;   // Little Endian Order\n};\n\nstruct [[gnu::packed]] android_event_string_t {\n    int8_t type;    // EVENT_TYPE_STRING;\n    int32_t length; // Little Endian Order\n    char data[];\n};\n\nstruct [[gnu::packed]] android_event_list_t {\n    int8_t type;    // EVENT_TYPE_LIST\n    int8_t element_count;\n} ;\n\n// 30014 am_proc_start (User|1|5),(PID|1|5),(UID|1|5),(Process Name|3),(Type|3),(Component|3)\nstruct [[gnu::packed]] android_event_am_proc_start {\n    android_event_header_t tag;\n    android_event_list_t list;\n    android_event_int_t user;\n    android_event_int_t pid;\n    android_event_int_t uid;\n    android_event_string_t process_name;\n//  android_event_string_t type;\n//  android_event_string_t component;\n};\n\n// 3040 boot_progress_ams_ready (time|2|3)\n\nextern \"C\" {\n\n[[gnu::weak]] struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);\n[[gnu::weak]] void android_logger_list_free(struct logger_list *list);\n[[gnu::weak]] int android_logger_list_read(struct logger_list *list, struct log_msg *log_msg);\n[[gnu::weak]] struct logger *android_logger_open(struct logger_list *list, log_id_t id);\n[[gnu::weak]] int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry);\n\n}\n\n// zygote pid -> mnt ns\nstatic map<int, struct stat> zygote_map;\nbool logcat_exit;\n\nstatic int read_ns(const int pid, struct stat *st) {\n    char path[32];\n    sprintf(path, \"/proc/%d/ns/mnt\", pid);\n    return stat(path, st);\n}\n\nstatic int parse_ppid(int pid) {\n    char path[32];\n    int ppid;\n    sprintf(path, \"/proc/%d/stat\", pid);\n    auto stat = open_file(path, \"re\");\n    if (!stat) return -1;\n    // PID COMM STATE PPID .....\n    fscanf(stat.get(), \"%*d %*s %*c %d\", &ppid);\n    return ppid;\n}\n\nstatic void check_zygote() {\n    zygote_map.clear();\n    int proc = open(\"/proc\", O_RDONLY | O_CLOEXEC);\n    auto proc_dir = xopen_dir(proc);\n    if (!proc_dir) return;\n    struct stat st{};\n    for (dirent *entry; (entry = readdir(proc_dir.get()));) {\n        int pid = parse_int(entry->d_name);\n        if (pid <= 0) continue;\n        if (fstatat(proc, entry->d_name, &st, 0)) continue;\n        if (st.st_uid != 0) continue;\n        if (proc_context_match(pid, \"u:r:zygote:s0\") && parse_ppid(pid) == 1) {\n            if (read_ns(pid, &st) == 0) {\n                LOGI(\"logcat: zygote PID=[%d]\\n\", pid);\n                zygote_map[pid] = st;\n            }\n        }\n    }\n}\n\nstatic void process_main_buffer(struct log_msg *msg) {\n    AndroidLogEntry entry{};\n    if (android_log_processLogBuffer(&msg->entry, &entry) < 0) return;\n    entry.tagLen--;\n    auto tag = string_view(entry.tag, entry.tagLen);\n\n    static bool ready = false;\n    if (tag == \"AppZygote\") {\n        if (entry.uid != 1000) return;\n        if (entry.message[0] == 'S') {\n            ready = true;\n        } else {\n            ready = false;\n        }\n        return;\n    }\n\n    if (!ready || tag != \"AppZygoteInit\") return;\n    if (!proc_context_match(msg->entry.pid, \"u:r:app_zygote:s0\")) return;\n    ready = false;\n\n    char cmdline[1024];\n    sprintf(cmdline, \"/proc/%d/cmdline\", msg->entry.pid);\n    if (auto f = open_file(cmdline, \"re\")) {\n        fgets(cmdline, sizeof(cmdline), f.get());\n    } else {\n        return;\n    }\n\n    if (is_deny_target(entry.uid, cmdline)) {\n        int pid = msg->entry.pid;\n        kill(pid, SIGSTOP);\n        if (fork_dont_care() == 0) {\n            LOGI(\"logcat: revert [%s] PID=[%d] UID=[%d]\\n\", cmdline, pid, entry.uid);\n            revert_unmount(pid);\n            kill(pid, SIGCONT);\n            _exit(0);\n        }\n    } else {\n        LOGD(\"logcat: skip [%s] PID=[%d] UID=[%d]\\n\", cmdline, msg->entry.pid, entry.uid);\n    }\n}\n\nstatic void process_events_buffer(struct log_msg *msg) {\n    if (msg->entry.uid != 1000) return;\n    auto event_data = &msg->buf[msg->entry.hdr_size];\n    auto event_header = reinterpret_cast<const android_event_header_t *>(event_data);\n    if (event_header->tag == 30014) {\n        auto am_proc_start = reinterpret_cast<const android_event_am_proc_start *>(event_data);\n        auto proc = string_view(am_proc_start->process_name.data,\n                                am_proc_start->process_name.length);\n        if (is_deny_target(am_proc_start->uid.data, proc)) {\n            int pid = am_proc_start->pid.data;\n            if (fork_dont_care() == 0) {\n                int ppid = parse_ppid(pid);\n                auto it = zygote_map.find(ppid);\n                if (it == zygote_map.end()) {\n                    LOGW(\"logcat: skip [%.*s] PID=[%d] UID=[%d] PPID=[%d]; parent not zygote\\n\",\n                         (int) proc.length(), proc.data(),\n                         pid, am_proc_start->uid.data, ppid);\n                    _exit(0);\n                }\n\n                char path[16];\n                ssprintf(path, sizeof(path), \"/proc/%d\", pid);\n                struct stat st{};\n                int fd = syscall(__NR_pidfd_open, pid, 0);\n                if (fd > 0 && setns(fd, CLONE_NEWNS) == 0) {\n                    pid = getpid();\n                } else {\n                    close(fd);\n                    fd = -1;\n                }\n                while (read_ns(pid, &st) == 0 && it->second.st_ino == st.st_ino) {\n                    if (stat(path, &st) == 0 && st.st_uid == 0) {\n                        usleep(10 * 1000);\n                    } else {\n                        LOGW(\"logcat: skip [%.*s] PID=[%s] UID=[%d]; namespace not isolated\\n\",\n                             (int) proc.length(), proc.data(),\n                             path + 6, am_proc_start->uid.data);\n                        _exit(0);\n                    }\n                    if (fd > 0) setns(fd, CLONE_NEWNS);\n                }\n                close(fd);\n\n                LOGI(\"logcat: revert [%.*s] PID=[%d] UID=[%d]\\n\",\n                     (int) proc.length(), proc.data(), pid, am_proc_start->uid.data);\n                revert_unmount(pid);\n                _exit(0);\n            }\n        } else {\n            LOGD(\"logcat: skip [%.*s] PID=[%d] UID=[%d]\\n\",\n                 (int) proc.length(), proc.data(),\n                 am_proc_start->pid.data, am_proc_start->uid.data);\n        }\n        return;\n    }\n    if (event_header->tag == 3040) {\n        LOGD(\"logcat: soft reboot\\n\");\n        check_zygote();\n    }\n}\n\n[[noreturn]] void run() {\n    while (true) {\n        const unique_ptr<logger_list, decltype(&android_logger_list_free)> logger_list{\n            android_logger_list_alloc(0, 1, 0), &android_logger_list_free};\n\n        for (log_id id: {LOG_ID_MAIN, LOG_ID_EVENTS}) {\n            auto *logger = android_logger_open(logger_list.get(), id);\n            if (logger == nullptr) continue;\n        }\n\n        struct log_msg msg{};\n        while (true) {\n            if (!denylist_enforced) {\n                break;\n            }\n\n            if (android_logger_list_read(logger_list.get(), &msg) <= 0) {\n                break;\n            }\n\n            switch (msg.entry.lid) {\n                case LOG_ID_EVENTS:\n                    process_events_buffer(&msg);\n                    break;\n                case LOG_ID_MAIN:\n                    process_main_buffer(&msg);\n                default:\n                    break;\n            }\n        }\n\n        if (!denylist_enforced) {\n            break;\n        }\n\n        sleep(1);\n    }\n\n    LOGD(\"logcat: terminate\\n\");\n    pthread_exit(nullptr);\n}\n\nvoid *logcat(void *) {\n    check_zygote();\n    run();\n}\n"
  },
  {
    "path": "native/src/core/deny/utils.cpp",
    "content": "#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/inotify.h>\n#include <unistd.h>\n#include <fcntl.h>\n#include <dirent.h>\n#include <set>\n#include <map>\n\n#include <consts.hpp>\n#include <sqlite.hpp>\n#include <core.hpp>\n\n#include \"deny.hpp\"\n\nusing namespace std;\n\n// For the following data structures:\n// If package name == ISOLATED_MAGIC, or app ID == -1, it means isolated service\n\n// Package name -> list of process names\nstatic unique_ptr<map<string, set<string, StringCmp>, StringCmp>> pkg_to_procs_;\n#define pkg_to_procs (*pkg_to_procs_)\n\n// app ID -> list of pkg names (string_view points to a pkg_to_procs key)\nstatic unique_ptr<map<int, set<string_view>>> app_id_to_pkgs_;\n#define app_id_to_pkgs (*app_id_to_pkgs_)\n\n// Locks the data structures above\nstatic pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;\n\natomic<bool> denylist_enforced = false;\n\nstatic int get_app_id(const vector<int> &users, const string &pkg) {\n    struct stat st{};\n    char buf[PATH_MAX];\n    for (const auto &user_id: users) {\n        ssprintf(buf, sizeof(buf), \"%s/%d/%s\", APP_DATA_DIR, user_id, pkg.data());\n        if (stat(buf, &st) == 0) {\n            return to_app_id(st.st_uid);\n        }\n    }\n    return 0;\n}\n\nstatic void collect_users(vector<int> &users) {\n    auto data_dir = xopen_dir(APP_DATA_DIR);\n    if (!data_dir)\n        return;\n    dirent *entry;\n    while ((entry = xreaddir(data_dir.get()))) {\n        users.emplace_back(parse_int(entry->d_name));\n    }\n}\n\nstatic int get_app_id(const string &pkg) {\n    if (pkg == ISOLATED_MAGIC)\n        return -1;\n    vector<int> users;\n    collect_users(users);\n    return get_app_id(users, pkg);\n}\n\nstatic void update_app_id(int app_id, const string &pkg, bool remove) {\n    if (app_id <= 0)\n        return;\n    if (remove) {\n        if (auto it = app_id_to_pkgs.find(app_id); it != app_id_to_pkgs.end()) {\n            it->second.erase(pkg);\n            if (it->second.empty()) {\n                app_id_to_pkgs.erase(it);\n            }\n        }\n    } else {\n        app_id_to_pkgs[app_id].emplace(pkg);\n    }\n}\n\n// Leave /proc fd opened as we're going to read from it repeatedly\nstatic DIR *procfp;\n\ntemplate<class F>\nstatic void crawl_procfs(const F &fn) {\n    rewinddir(procfp);\n    dirent *dp;\n    int pid;\n    while ((dp = readdir(procfp))) {\n        pid = parse_int(dp->d_name);\n        if (pid > 0 && !fn(pid))\n            break;\n    }\n}\n\nstatic bool str_eql(string_view a, string_view b) { return a == b; }\nstatic bool str_starts_with(string_view a, string_view b) { return a.starts_with(b); }\n\ntemplate<bool str_op(string_view, string_view) = str_eql>\nstatic bool proc_name_match(int pid, string_view name) {\n    char buf[4019];\n    sprintf(buf, \"/proc/%d/cmdline\", pid);\n    if (auto fp = open_file(buf, \"re\")) {\n        fgets(buf, sizeof(buf), fp.get());\n        if (str_op(buf, name)) {\n            return true;\n        }\n    }\n    return false;\n}\n\nbool proc_context_match(int pid, string_view context) {\n    char buf[PATH_MAX];\n    char con[1024] = {0};\n\n    sprintf(buf, \"/proc/%d\", pid);\n    if (lgetfilecon(buf, byte_data{ con, sizeof(con) })) {\n        return string_view(con).starts_with(context);\n    }\n    return false;\n}\n\ntemplate<bool matcher(int, string_view) = &proc_name_match>\nstatic void kill_process(const char *name, bool multi = false) {\n    crawl_procfs([=](int pid) -> bool {\n        if (matcher(pid, name)) {\n            kill(pid, SIGKILL);\n            LOGD(\"denylist: kill PID=[%d] (%s)\\n\", pid, name);\n            return multi;\n        }\n        return true;\n    });\n}\n\nstatic bool validate(const char *pkg, const char *proc) {\n    bool pkg_valid = false;\n    bool proc_valid = true;\n\n    if (str_eql(pkg, ISOLATED_MAGIC)) {\n        pkg_valid = true;\n        for (char c; (c = *proc); ++proc) {\n            if (isalnum(c) || c == '_' || c == '.')\n                continue;\n            if (c == ':')\n                break;\n            proc_valid = false;\n            break;\n        }\n    } else {\n        for (char c; (c = *pkg); ++pkg) {\n            if (isalnum(c) || c == '_')\n                continue;\n            if (c == '.') {\n                pkg_valid = true;\n                continue;\n            }\n            pkg_valid = false;\n            break;\n        }\n\n        for (char c; (c = *proc); ++proc) {\n            if (isalnum(c) || c == '_' || c == ':' || c == '.')\n                continue;\n            proc_valid = false;\n            break;\n        }\n    }\n    return pkg_valid && proc_valid;\n}\n\nstatic bool add_hide_set(const char *pkg, const char *proc) {\n    auto p = pkg_to_procs[pkg].emplace(proc);\n    if (!p.second)\n        return false;\n    LOGI(\"denylist add: [%s/%s]\\n\", pkg, proc);\n    if (!denylist_enforced)\n        return true;\n    if (str_eql(pkg, ISOLATED_MAGIC)) {\n        // Kill all matching isolated processes\n        kill_process<&proc_name_match<str_starts_with>>(proc, true);\n    } else {\n        kill_process(proc);\n    }\n    return true;\n}\n\nvoid scan_deny_apps() {\n    if (!app_id_to_pkgs_)\n        return;\n\n    app_id_to_pkgs.clear();\n\n    char sql[4096];\n    vector<int> users;\n    collect_users(users);\n    for (auto it = pkg_to_procs.begin(); it != pkg_to_procs.end();) {\n        if (it->first == ISOLATED_MAGIC) {\n            it++;\n            continue;\n        }\n        int app_id = get_app_id(users, it->first);\n        if (app_id == 0) {\n            LOGI(\"denylist rm: [%s]\\n\", it->first.data());\n            ssprintf(sql, sizeof(sql), \"DELETE FROM denylist WHERE package_name='%s'\",\n                     it->first.data());\n            db_exec(sql);\n            it = pkg_to_procs.erase(it);\n        } else {\n            update_app_id(app_id, it->first, false);\n            it++;\n        }\n    }\n}\n\nstatic void clear_data() {\n    pkg_to_procs_.reset(nullptr);\n    app_id_to_pkgs_.reset(nullptr);\n}\n\nstatic bool ensure_data() {\n    if (pkg_to_procs_)\n        return true;\n\n    LOGI(\"denylist: initializing internal data structures\\n\");\n\n    default_new(pkg_to_procs_);\n    bool res = db_exec(\"SELECT * FROM denylist\", {}, [](StringSlice columns, const DbValues &values) {\n        const char *package_name;\n        const char *process;\n        for (int i = 0; i < columns.size(); ++i) {\n            const auto &name = columns[i];\n            if (name == \"package_name\") {\n                package_name = values.get_text(i);\n            } else if (name == \"process\") {\n                process = values.get_text(i);\n            }\n        }\n        add_hide_set(package_name, process);\n    });\n    if (!res)\n        goto error;\n\n    default_new(app_id_to_pkgs_);\n    scan_deny_apps();\n\n    return true;\n\nerror:\n    clear_data();\n    return false;\n}\n\nstatic int add_list(const char *pkg, const char *proc) {\n    if (proc[0] == '\\0')\n        proc = pkg;\n\n    if (!validate(pkg, proc))\n        return DenyResponse::INVALID_PKG;\n\n    {\n        mutex_guard lock(data_lock);\n        if (!ensure_data())\n            return DenyResponse::ERROR;\n        int app_id = get_app_id(pkg);\n        if (app_id == 0)\n            return DenyResponse::INVALID_PKG;\n        if (!add_hide_set(pkg, proc))\n            return DenyResponse::ITEM_EXIST;\n        auto it = pkg_to_procs.find(pkg);\n        update_app_id(app_id, it->first, false);\n    }\n\n    // Add to database\n    char sql[4096];\n    ssprintf(sql, sizeof(sql),\n            \"INSERT INTO denylist (package_name, process) VALUES('%s', '%s')\", pkg, proc);\n    return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR;\n}\n\nint add_list(int client) {\n    string pkg = read_string(client);\n    string proc = read_string(client);\n    return add_list(pkg.data(), proc.data());\n}\n\nstatic int rm_list(const char *pkg, const char *proc) {\n    {\n        mutex_guard lock(data_lock);\n        if (!ensure_data())\n            return DenyResponse::ERROR;\n\n        bool remove = false;\n\n        auto it = pkg_to_procs.find(pkg);\n        if (it != pkg_to_procs.end()) {\n            if (proc[0] == '\\0') {\n                update_app_id(get_app_id(pkg), it->first, true);\n                pkg_to_procs.erase(it);\n                remove = true;\n                LOGI(\"denylist rm: [%s]\\n\", pkg);\n            } else if (it->second.erase(proc) != 0) {\n                remove = true;\n                LOGI(\"denylist rm: [%s/%s]\\n\", pkg, proc);\n                if (it->second.empty()) {\n                    update_app_id(get_app_id(pkg), it->first, true);\n                    pkg_to_procs.erase(it);\n                }\n            }\n        }\n\n        if (!remove)\n            return DenyResponse::ITEM_NOT_EXIST;\n    }\n\n    char sql[4096];\n    if (proc[0] == '\\0')\n        ssprintf(sql, sizeof(sql), \"DELETE FROM denylist WHERE package_name='%s'\", pkg);\n    else\n        ssprintf(sql, sizeof(sql),\n                \"DELETE FROM denylist WHERE package_name='%s' AND process='%s'\", pkg, proc);\n    return db_exec(sql) ? DenyResponse::OK : DenyResponse::ERROR;\n}\n\nint rm_list(int client) {\n    string pkg = read_string(client);\n    string proc = read_string(client);\n    return rm_list(pkg.data(), proc.data());\n}\n\nvoid ls_list(int client) {\n    {\n        mutex_guard lock(data_lock);\n        if (!ensure_data()) {\n            write_int(client, static_cast<int>(DenyResponse::ERROR));\n            return;\n        }\n\n        scan_deny_apps();\n        write_int(client,static_cast<int>(DenyResponse::OK));\n\n        for (const auto &[pkg, procs] : pkg_to_procs) {\n            for (const auto &proc : procs) {\n                write_int(client, pkg.size() + proc.size() + 1);\n                xwrite(client, pkg.data(), pkg.size());\n                xwrite(client, \"|\", 1);\n                xwrite(client, proc.data(), proc.size());\n            }\n        }\n    }\n    write_int(client, 0);\n    close(client);\n}\n\nint enable_deny() {\n    if (denylist_enforced) {\n        return DenyResponse::OK;\n    } else {\n        mutex_guard lock(data_lock);\n\n        if (access(\"/proc/self/ns/mnt\", F_OK) != 0) {\n            LOGW(\"The kernel does not support mount namespace\\n\");\n            return DenyResponse::NO_NS;\n        }\n\n        if (procfp == nullptr && (procfp = opendir(\"/proc\")) == nullptr)\n            return DenyResponse::ERROR;\n\n        LOGI(\"* Enable DenyList\\n\");\n\n        if (!ensure_data())\n            return DenyResponse::ERROR;\n\n        denylist_enforced = true;\n\n        if (!MagiskD::Get().zygisk_enabled()) {\n            if (new_daemon_thread(&logcat)) {\n                denylist_enforced = false;\n                return DenyResponse::ERROR;\n            }\n        }\n\n        // On Android Q+, also kill blastula pool and all app zygotes\n        if (SDK_INT >= 29) {\n            kill_process(\"usap32\", true);\n            kill_process(\"usap64\", true);\n            kill_process<&proc_context_match>(\"u:r:app_zygote:s0\", true);\n        }\n    }\n\n    MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, true);\n    return DenyResponse::OK;\n}\n\nint disable_deny() {\n    if (denylist_enforced.exchange(false)) {\n        LOGI(\"* Disable DenyList\\n\");\n    }\n    MagiskD::Get().set_db_setting(DbEntryKey::DenylistConfig, false);\n    return DenyResponse::OK;\n}\n\nvoid initialize_denylist() {\n    if (!denylist_enforced) {\n        if (MagiskD::Get().get_db_setting(DbEntryKey::DenylistConfig))\n            enable_deny();\n    }\n}\n\nbool is_deny_target(int uid, string_view process) {\n    mutex_guard lock(data_lock);\n    if (!ensure_data())\n        return false;\n\n    int app_id = to_app_id(uid);\n    if (app_id >= 90000) {\n        if (auto it = pkg_to_procs.find(ISOLATED_MAGIC); it != pkg_to_procs.end()) {\n            for (const auto &s : it->second) {\n                if (process.starts_with(s))\n                    return true;\n            }\n        }\n        return false;\n    } else {\n        auto it = app_id_to_pkgs.find(app_id);\n        if (it == app_id_to_pkgs.end())\n            return false;\n        for (const auto &pkg : it->second) {\n            if (pkg_to_procs.find(pkg)->second.count(process))\n                return true;\n        }\n    }\n    return false;\n}\n\nvoid update_deny_flags(int uid, rust::Str process, uint32_t &flags) {\n    if (is_deny_target(uid, { process.begin(), process.end() })) {\n        flags |= +ZygiskStateFlags::ProcessOnDenyList;\n    }\n    if (denylist_enforced) {\n        flags |= +ZygiskStateFlags::DenyListEnforced;\n    }\n}\n"
  },
  {
    "path": "native/src/core/include/core.hpp",
    "content": "#pragma once\n\n#include <sys/socket.h>\n#include <string>\n#include <atomic>\n#include <functional>\n\n#include <base.hpp>\n\n#include \"../core-rs.hpp\"\n\n#define AID_ROOT   0\n#define AID_SHELL  2000\n#define AID_USER_OFFSET 100000\n\n#define to_app_id(uid)  (uid % AID_USER_OFFSET)\n#define to_user_id(uid) (uid / AID_USER_OFFSET)\n\n#define SDK_INT      (MagiskD::Get().sdk_int())\n#define APP_DATA_DIR (SDK_INT >= 24 ? \"/data/user_de\" : \"/data/user\")\n\ninline int connect_daemon(RequestCode req) {\n    return connect_daemon(req, false);\n}\n\n// Multi-call entrypoints\nint su_client_main(int argc, char *argv[]);\nint zygisk_main(int argc, char *argv[]);\n\nstruct ModuleInfo;\n\n// Utils\nconst char *get_magisk_tmp();\nvoid unlock_blocks();\nbool check_key_combo();\ntemplate<typename T> requires(std::is_trivially_copyable_v<T>)\nT read_any(int fd) {\n    T val;\n    if (xxread(fd, &val, sizeof(val)) != sizeof(val))\n        return -1;\n    return val;\n}\ntemplate<typename T> requires(std::is_trivially_copyable_v<T>)\nvoid write_any(int fd, T val) {\n    if (fd < 0) return;\n    xwrite(fd, &val, sizeof(val));\n}\ninline int read_int(int fd) { return read_any<int>(fd); }\ninline void write_int(int fd, int val) { write_any(fd, val); }\nstd::string read_string(int fd);\nbool read_string(int fd, std::string &str);\nvoid write_string(int fd, std::string_view str);\ntemplate<typename T> requires(std::is_trivially_copyable_v<T>)\nvoid write_vector(int fd, const std::vector<T> &vec) {\n    write_int(fd, vec.size());\n    xwrite(fd, vec.data(), vec.size() * sizeof(T));\n}\ntemplate<typename T> requires(std::is_trivially_copyable_v<T>)\nbool read_vector(int fd, std::vector<T> &vec) {\n    int size = read_int(fd);\n    vec.resize(size);\n    return xread(fd, vec.data(), size * sizeof(T)) == size * sizeof(T);\n}\n\n// Scripting\nvoid install_apk(Utf8CStr apk);\nvoid uninstall_pkg(Utf8CStr pkg);\nvoid exec_common_scripts(Utf8CStr stage);\nvoid exec_module_scripts(Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list);\nvoid exec_script(Utf8CStr script);\nvoid clear_pkg(const char *pkg, int user_id);\n[[noreturn]] void install_module(Utf8CStr file);\n\n// Denylist\nextern std::atomic<bool> denylist_enforced;\nint denylist_cli(rust::Vec<rust::String> &args);\nvoid denylist_handler(int client);\nvoid initialize_denylist();\nvoid scan_deny_apps();\nbool is_deny_target(int uid, std::string_view process);\nvoid revert_unmount(int pid = -1) noexcept;\nvoid update_deny_flags(int uid, rust::Str process, uint32_t &flags);\n\n// MagiskSU\nvoid exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode);\n\n// Rust bindings\ninline Utf8CStr get_magisk_tmp_rs() { return get_magisk_tmp(); }\ninline rust::String resolve_preinit_dir_rs(Utf8CStr base_dir) {\n    return resolve_preinit_dir(base_dir.c_str());\n}\n"
  },
  {
    "path": "native/src/core/include/sqlite.hpp",
    "content": "#pragma once\n\n#include <functional>\n\n#include <rust/cxx.h>\n\n#define SQLITE_OPEN_READWRITE        0x00000002  /* Ok for sqlite3_open_v2() */\n#define SQLITE_OPEN_CREATE           0x00000004  /* Ok for sqlite3_open_v2() */\n#define SQLITE_OPEN_NOMUTEX          0x00008000  /* Ok for sqlite3_open_v2() */\n\n#define SQLITE_OK           0   /* Successful result */\n#define SQLITE_ROW         100  /* sqlite3_step() has another row ready */\n#define SQLITE_DONE        101  /* sqlite3_step() has finished executing */\n\nstruct sqlite3;\nstruct sqlite3_stmt;\n\nextern const char *(*sqlite3_errstr)(int);\n\n// Transparent wrappers of sqlite3_stmt\nstruct DbValues {\n    const char *get_text(int index) const;\n    rust::Str get_str(int index) const { return get_text(index); }\n    int get_int(int index) const;\n    ~DbValues() = delete;\n};\nstruct DbStatement {\n    int bind_text(int index, rust::Str val);\n    int bind_int64(int index, int64_t val);\n    ~DbStatement() = delete;\n};\n\nusing StringSlice = rust::Slice<rust::String>;\nusing sql_bind_callback = int(*)(void*, int, DbStatement&);\nusing sql_exec_callback = void(*)(void*, StringSlice, const DbValues&);\n\nsqlite3 *open_and_init_db();\n\n/************\n * C++ APIs *\n ************/\n\nusing db_exec_callback = std::function<void(StringSlice, const DbValues&)>;\n\nstruct DbArg {\n    enum {\n        INT,\n        TEXT,\n    } type;\n    union {\n        int64_t int_val;\n        rust::Str str_val;\n    };\n    DbArg(int64_t v) : type(INT), int_val(v) {}\n    DbArg(const char *v) : type(TEXT), str_val(v) {}\n};\n\nstruct DbArgs {\n    DbArgs() : curr(0) {}\n    DbArgs(std::initializer_list<DbArg> list) : args(list), curr(0) {}\n    int operator()(int index, DbStatement &stmt);\n    bool empty() const { return args.empty(); }\nprivate:\n    std::vector<DbArg> args;\n    size_t curr;\n};\n\nbool db_exec(const char *sql, DbArgs args = {}, db_exec_callback exec_fn = {});\n\ntemplate<typename T>\nconcept DbData = requires(T t, StringSlice s, DbValues &v) { t(s, v); };\n\ntemplate<DbData T>\nbool db_exec(const char *sql, DbArgs args, T &data) {\n    return db_exec(sql, std::move(args), (db_exec_callback) std::ref(data));\n}\n"
  },
  {
    "path": "native/src/core/lib.rs",
    "content": "#![feature(fn_traits)]\n#![feature(unix_socket_ancillary_data)]\n#![feature(unix_socket_peek)]\n#![feature(default_field_values)]\n#![feature(peer_credentials_unix_socket)]\n#![feature(sync_nonpoison)]\n#![feature(nonpoison_mutex)]\n#![feature(nonpoison_condvar)]\n#![allow(clippy::missing_safety_doc)]\n\nuse crate::ffi::SuRequest;\nuse crate::socket::Encodable;\nuse base::derive::Decodable;\nuse daemon::{MagiskD, connect_daemon_for_cxx};\nuse logging::{android_logging, zygisk_close_logd, zygisk_get_logd, zygisk_logging};\nuse magisk::magisk_main;\nuse mount::revert_unmount;\nuse resetprop::{get_prop, resetprop_main};\nuse selinux::{lgetfilecon, setfilecon};\nuse socket::{recv_fd, recv_fds, send_fd};\nuse std::fs::File;\nuse std::mem::ManuallyDrop;\nuse std::ops::DerefMut;\nuse std::os::fd::FromRawFd;\nuse su::{get_pty_num, pump_tty};\nuse zygisk::zygisk_should_load_module;\n\nmod bootstages;\n#[path = \"../include/consts.rs\"]\nmod consts;\nmod daemon;\nmod db;\nmod logging;\nmod magisk;\nmod module;\nmod mount;\nmod package;\nmod resetprop;\nmod selinux;\nmod socket;\nmod su;\nmod thread;\nmod zygisk;\n\n#[allow(clippy::needless_lifetimes)]\n#[cxx::bridge]\npub mod ffi {\n    #[repr(i32)]\n    enum RequestCode {\n        START_DAEMON,\n        CHECK_VERSION,\n        CHECK_VERSION_CODE,\n        STOP_DAEMON,\n\n        _SYNC_BARRIER_,\n\n        SUPERUSER,\n        ZYGOTE_RESTART,\n        DENYLIST,\n        SQLITE_CMD,\n        REMOVE_MODULES,\n        ZYGISK,\n\n        _STAGE_BARRIER_,\n\n        POST_FS_DATA,\n        LATE_START,\n        BOOT_COMPLETE,\n\n        END,\n    }\n\n    #[repr(i32)]\n    enum RespondCode {\n        ERROR = -1,\n        OK = 0,\n        ROOT_REQUIRED,\n        ACCESS_DENIED,\n        END,\n    }\n\n    enum DbEntryKey {\n        RootAccess,\n        SuMultiuserMode,\n        SuMntNs,\n        DenylistConfig,\n        ZygiskConfig,\n        BootloopCount,\n        SuManager,\n    }\n\n    #[repr(i32)]\n    enum MntNsMode {\n        Global,\n        Requester,\n        Isolate,\n    }\n\n    #[repr(i32)]\n    enum SuPolicy {\n        Query,\n        Deny,\n        Allow,\n        Restrict,\n    }\n\n    struct ModuleInfo {\n        name: String,\n        z32: i32,\n        z64: i32,\n    }\n\n    #[repr(i32)]\n    enum ZygiskRequest {\n        GetInfo,\n        ConnectCompanion,\n        GetModDir,\n    }\n\n    #[repr(u32)]\n    enum ZygiskStateFlags {\n        ProcessGrantedRoot = 0x00000001,\n        ProcessOnDenyList = 0x00000002,\n        DenyListEnforced = 0x40000000,\n        ProcessIsMagiskApp = 0x80000000,\n    }\n\n    #[derive(Decodable)]\n    struct SuRequest {\n        target_uid: i32,\n        target_pid: i32,\n        login: bool,\n        keep_env: bool,\n        drop_cap: bool,\n        shell: String,\n        command: String,\n        context: String,\n        gids: Vec<u32>,\n    }\n\n    unsafe extern \"C++\" {\n        #[cxx_name = \"Utf8CStr\"]\n        type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;\n\n        include!(\"include/core.hpp\");\n\n        #[cxx_name = \"get_magisk_tmp_rs\"]\n        fn get_magisk_tmp() -> Utf8CStrRef<'static>;\n        #[cxx_name = \"resolve_preinit_dir_rs\"]\n        fn resolve_preinit_dir(base_dir: Utf8CStrRef) -> String;\n        fn check_key_combo() -> bool;\n        fn unlock_blocks();\n        fn update_deny_flags(uid: i32, process: &str, flags: &mut u32);\n        fn initialize_denylist();\n        fn switch_mnt_ns(pid: i32) -> i32;\n        fn exec_root_shell(client: i32, pid: i32, req: &mut SuRequest, mode: MntNsMode);\n\n        // Scripting\n        fn exec_script(script: Utf8CStrRef);\n        fn exec_common_scripts(stage: Utf8CStrRef);\n        fn exec_module_scripts(state: Utf8CStrRef, modules: &Vec<ModuleInfo>);\n        fn install_apk(apk: Utf8CStrRef);\n        fn uninstall_pkg(apk: Utf8CStrRef);\n        fn install_module(zip: Utf8CStrRef);\n\n        // Denylist\n        fn denylist_cli(args: &mut Vec<String>) -> i32;\n        fn denylist_handler(client: i32);\n        fn scan_deny_apps();\n\n        include!(\"include/sqlite.hpp\");\n\n        type sqlite3;\n        type DbValues;\n        type DbStatement;\n\n        fn sqlite3_errstr(code: i32) -> *const c_char;\n        fn open_and_init_db() -> *mut sqlite3;\n        fn get_int(self: &DbValues, index: i32) -> i32;\n        #[cxx_name = \"get_str\"]\n        fn get_text(self: &DbValues, index: i32) -> &str;\n        fn bind_text(self: Pin<&mut DbStatement>, index: i32, val: &str) -> i32;\n        fn bind_int64(self: Pin<&mut DbStatement>, index: i32, val: i64) -> i32;\n    }\n\n    extern \"Rust\" {\n        fn android_logging();\n        fn zygisk_logging();\n        fn zygisk_close_logd();\n        fn zygisk_get_logd() -> i32;\n        fn revert_unmount(pid: i32);\n        fn zygisk_should_load_module(flags: u32) -> bool;\n        fn send_fd(socket: i32, fd: i32) -> bool;\n        fn recv_fd(socket: i32) -> i32;\n        fn recv_fds(socket: i32) -> Vec<i32>;\n        fn write_to_fd(self: &SuRequest, fd: i32);\n        fn pump_tty(ptmx: i32, pump_stdin: bool);\n        fn get_pty_num(fd: i32) -> i32;\n        fn lgetfilecon(path: Utf8CStrRef, con: &mut [u8]) -> bool;\n        fn setfilecon(path: Utf8CStrRef, con: Utf8CStrRef) -> bool;\n\n        fn get_prop(name: Utf8CStrRef) -> String;\n        unsafe fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32;\n\n        #[cxx_name = \"connect_daemon\"]\n        fn connect_daemon_for_cxx(code: RequestCode, create: bool) -> i32;\n        unsafe fn magisk_main(argc: i32, argv: *mut *mut c_char) -> i32;\n    }\n\n    // Default constructors\n    extern \"Rust\" {\n        #[Self = SuRequest]\n        #[cxx_name = \"New\"]\n        fn default() -> SuRequest;\n    }\n\n    // FFI for MagiskD\n    extern \"Rust\" {\n        type MagiskD;\n        fn sdk_int(&self) -> i32;\n        fn zygisk_enabled(&self) -> bool;\n        fn get_db_setting(&self, key: DbEntryKey) -> i32;\n        #[cxx_name = \"set_db_setting\"]\n        fn set_db_setting_for_cxx(&self, key: DbEntryKey, value: i32) -> bool;\n\n        #[Self = MagiskD]\n        #[cxx_name = \"Get\"]\n        fn get() -> &'static MagiskD;\n    }\n}\n\nimpl SuRequest {\n    fn write_to_fd(&self, fd: i32) {\n        unsafe {\n            let mut w = ManuallyDrop::new(File::from_raw_fd(fd));\n            self.encode(w.deref_mut()).ok();\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/core/logging.rs",
    "content": "use crate::consts::{LOG_PIPE, LOGFILE};\nuse crate::ffi::get_magisk_tmp;\nuse crate::logging::LogFile::{Actual, Buffer};\nuse base::const_format::concatcp;\nuse base::{\n    FsPathBuilder, LogLevel, LoggedResult, ReadExt, ResultExt, Utf8CStr, Utf8CStrBuf, WriteExt,\n    cstr, libc, new_daemon_thread, raw_cstr, update_logger,\n};\nuse bytemuck::{Pod, Zeroable, bytes_of, write_zeroes};\nuse libc::{PIPE_BUF, c_char, localtime_r, sigtimedwait, time_t, timespec, tm};\nuse nix::fcntl::OFlag;\nuse nix::sys::signal::{SigSet, SigmaskHow, Signal};\nuse nix::unistd::{Gid, Uid, chown, getpid, gettid};\nuse num_derive::{FromPrimitive, ToPrimitive};\nuse num_traits::FromPrimitive;\nuse std::cmp::min;\nuse std::fmt::Write as _;\nuse std::fs::File;\nuse std::io::{IoSlice, Read, Write};\nuse std::mem::ManuallyDrop;\nuse std::os::fd::{FromRawFd, IntoRawFd, RawFd};\nuse std::ptr::null_mut;\nuse std::sync::Arc;\nuse std::sync::atomic::{AtomicI32, Ordering};\nuse std::sync::nonpoison::Mutex;\nuse std::time::{Duration, SystemTime, UNIX_EPOCH};\nuse std::{fs, io};\n\n#[allow(dead_code, non_camel_case_types)]\n#[derive(FromPrimitive, ToPrimitive)]\n#[repr(i32)]\nenum ALogPriority {\n    ANDROID_LOG_UNKNOWN = 0,\n    ANDROID_LOG_DEFAULT,\n    ANDROID_LOG_VERBOSE,\n    ANDROID_LOG_DEBUG,\n    ANDROID_LOG_INFO,\n    ANDROID_LOG_WARN,\n    ANDROID_LOG_ERROR,\n    ANDROID_LOG_FATAL,\n    ANDROID_LOG_SILENT,\n}\n\nunsafe extern \"C\" {\n    fn __android_log_write(prio: i32, tag: *const c_char, msg: *const c_char);\n    fn strftime(buf: *mut c_char, len: usize, fmt: *const c_char, tm: *const tm) -> usize;\n}\n\nfn level_to_prio(level: LogLevel) -> i32 {\n    match level {\n        LogLevel::Error => ALogPriority::ANDROID_LOG_ERROR as i32,\n        LogLevel::Warn => ALogPriority::ANDROID_LOG_WARN as i32,\n        LogLevel::Info => ALogPriority::ANDROID_LOG_INFO as i32,\n        LogLevel::Debug => ALogPriority::ANDROID_LOG_DEBUG as i32,\n    }\n}\n\nfn android_log_write(level: LogLevel, msg: &Utf8CStr) {\n    unsafe {\n        __android_log_write(level_to_prio(level), raw_cstr!(\"Magisk\"), msg.as_ptr());\n    }\n}\n\npub fn android_logging() {\n    update_logger(|logger| logger.write = android_log_write);\n}\n\npub fn magisk_logging() {\n    fn magisk_log_write(level: LogLevel, msg: &Utf8CStr) {\n        android_log_write(level, msg);\n        magisk_log_to_pipe(level_to_prio(level), msg);\n    }\n    update_logger(|logger| logger.write = magisk_log_write);\n}\n\npub fn zygisk_logging() {\n    fn zygisk_log_write(level: LogLevel, msg: &Utf8CStr) {\n        android_log_write(level, msg);\n        zygisk_log_to_pipe(level_to_prio(level), msg);\n    }\n    update_logger(|logger| logger.write = zygisk_log_write);\n}\n\n#[derive(Copy, Clone, Pod, Zeroable)]\n#[repr(C)]\nstruct LogMeta {\n    prio: i32,\n    len: i32,\n    pid: i32,\n    tid: i32,\n}\n\nconst MAX_MSG_LEN: usize = PIPE_BUF - size_of::<LogMeta>();\n\nfn write_log_to_pipe(mut logd: &File, prio: i32, msg: &Utf8CStr) -> io::Result<usize> {\n    // Truncate message if needed\n    let len = min(MAX_MSG_LEN, msg.len());\n    let msg = &msg.as_bytes()[..len];\n\n    let meta = LogMeta {\n        prio,\n        len: len as i32,\n        pid: getpid().as_raw(),\n        tid: gettid().as_raw(),\n    };\n\n    let io1 = IoSlice::new(bytes_of(&meta));\n    let io2 = IoSlice::new(msg);\n    let result = logd.write_vectored(&[io1, io2]);\n    if let Err(ref e) = result {\n        let mut buf = cstr::buf::new::<256>();\n        write!(buf, \"Cannot write_log_to_pipe: {e}\").ok();\n        android_log_write(LogLevel::Error, &buf);\n    }\n    result\n}\n\nstatic MAGISK_LOGD_FD: Mutex<Option<Arc<File>>> = Mutex::new(None);\n\nfn with_logd_fd<R, F: FnOnce(&File) -> io::Result<R>>(f: F) {\n    let fd = MAGISK_LOGD_FD.lock().clone();\n    if let Some(logd) = fd\n        && f(&logd).is_err()\n    {\n        // If any error occurs, shut down the logd pipe\n        *MAGISK_LOGD_FD.lock() = None;\n    }\n}\n\nfn magisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {\n    with_logd_fd(|logd| write_log_to_pipe(logd, prio, msg));\n}\n\n// SAFETY: zygisk client code runs single threaded, so no need to prevent data race\nstatic ZYGISK_LOGD: AtomicI32 = AtomicI32::new(-1);\n\npub fn zygisk_close_logd() {\n    unsafe {\n        libc::close(ZYGISK_LOGD.swap(-1, Ordering::Relaxed));\n    }\n}\n\npub fn zygisk_get_logd() -> i32 {\n    // If we don't have the log pipe set, open the log pipe FIFO. This could actually happen\n    // multiple times in the zygote daemon (parent process) because we had to close this\n    // file descriptor to prevent crashing.\n    //\n    // For some reason, zygote sanitizes and checks FDs *before* forking. This results in the fact\n    // that *every* time before zygote forks, it has to close all logging related FDs in order\n    // to pass FD checks, just to have it re-initialized immediately after any\n    // logging happens ¯\\_(ツ)_/¯.\n    //\n    // To be consistent with this behavior, we also have to close the log pipe to magiskd\n    // to make zygote NOT crash if necessary. We accomplish this by hooking __android_log_close\n    // and closing it at the same time as the rest of logging FDs.\n\n    let mut raw_fd = ZYGISK_LOGD.load(Ordering::Relaxed);\n    if raw_fd < 0 {\n        android_logging();\n        let path = cstr::buf::default()\n            .join_path(get_magisk_tmp())\n            .join_path(LOG_PIPE);\n        // Open as RW as sometimes it may block\n        if let Ok(fd) = path.open(OFlag::O_RDWR | OFlag::O_CLOEXEC) {\n            // Only re-enable zygisk logging if success\n            zygisk_logging();\n            raw_fd = fd.into_raw_fd();\n            unsafe {\n                libc::close(ZYGISK_LOGD.swap(raw_fd, Ordering::Relaxed));\n            }\n        } else {\n            return -1;\n        }\n    }\n    raw_fd\n}\n\nfn zygisk_log_to_pipe(prio: i32, msg: &Utf8CStr) {\n    let fd = zygisk_get_logd();\n    if fd < 0 {\n        // Cannot talk to pipe, abort\n        return;\n    }\n\n    // Block SIGPIPE\n    let mut mask = SigSet::empty();\n    mask.add(Signal::SIGPIPE);\n    let orig_mask = mask.thread_swap_mask(SigmaskHow::SIG_SETMASK);\n\n    let logd = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });\n    let result = write_log_to_pipe(&logd, prio, msg);\n\n    // Consume SIGPIPE if exists, then restore mask\n    if let Ok(orig_mask) = orig_mask {\n        unsafe {\n            // Unfortunately nix does not have an abstraction over sigtimedwait.\n            // Fallback to use raw libc function calls.\n            let ts: timespec = std::mem::zeroed();\n            sigtimedwait(mask.as_ref(), null_mut(), &ts);\n        }\n        orig_mask.thread_set_mask().ok();\n    }\n\n    // If any error occurs, shut down the logd pipe\n    if result.is_err() {\n        zygisk_close_logd();\n    }\n}\n\n// The following is implementation for the logging daemon\n\nenum LogFile {\n    Buffer(Vec<u8>),\n    Actual(File),\n}\n\nimpl LogFile {\n    fn as_write(&mut self) -> &mut dyn Write {\n        match self {\n            Buffer(e) => e,\n            Actual(e) => e,\n        }\n    }\n}\n\nfn logfile_write_loop(mut pipe: File) -> io::Result<()> {\n    let mut logfile: LogFile = Buffer(Vec::new());\n\n    let mut meta = LogMeta::zeroed();\n    let mut msg_buf = [0u8; MAX_MSG_LEN];\n    let mut aux = cstr::buf::new::<64>();\n\n    loop {\n        // Read request\n        write_zeroes(&mut meta);\n        pipe.read_pod(&mut meta)?;\n\n        if meta.prio < 0 {\n            if let Buffer(ref mut buf) = logfile {\n                fs::rename(LOGFILE, concatcp!(LOGFILE, \".bak\")).ok();\n                let mut out = File::create(LOGFILE)?;\n                out.write_all(buf.as_slice())?;\n                logfile = Actual(out);\n            }\n            continue;\n        }\n\n        if meta.len < 0 || meta.len > MAX_MSG_LEN as i32 {\n            continue;\n        }\n\n        // Read the rest of the message\n        let msg = &mut msg_buf[..(meta.len as usize)];\n        pipe.read_exact(msg)?;\n\n        // Start building the log string\n        aux.clear();\n        let prio = ALogPriority::from_i32(meta.prio).unwrap_or(ALogPriority::ANDROID_LOG_UNKNOWN);\n        let prio = match prio {\n            ALogPriority::ANDROID_LOG_VERBOSE => 'V',\n            ALogPriority::ANDROID_LOG_DEBUG => 'D',\n            ALogPriority::ANDROID_LOG_INFO => 'I',\n            ALogPriority::ANDROID_LOG_WARN => 'W',\n            ALogPriority::ANDROID_LOG_ERROR => 'E',\n            // Unsupported values, skip\n            _ => continue,\n        };\n\n        let now = SystemTime::now()\n            .duration_since(UNIX_EPOCH)\n            .unwrap_or(Duration::ZERO);\n\n        // Note: the obvious better implementation is to use the rust chrono crate, however\n        // the crate cannot fetch the proper local timezone without pulling in a bunch of\n        // timezone handling code. To reduce binary size, fallback to use localtime_r in libc.\n        unsafe {\n            let secs = now.as_secs() as time_t;\n            let mut tm: tm = std::mem::zeroed();\n            if localtime_r(&secs, &mut tm).is_null() {\n                continue;\n            }\n            strftime(aux.as_mut_ptr(), aux.capacity(), raw_cstr!(\"%m-%d %T\"), &tm);\n        }\n\n        if aux.rebuild().is_ok() {\n            write!(\n                aux,\n                \".{:03} {:5} {:5} {} : \",\n                now.subsec_millis(),\n                meta.pid,\n                meta.tid,\n                prio\n            )\n            .ok();\n        } else {\n            continue;\n        }\n\n        let io1 = IoSlice::new(aux.as_bytes());\n        let io2 = IoSlice::new(msg);\n        // We don't need to care the written len because we are writing less than PIPE_BUF\n        // It's guaranteed to always write the whole thing atomically\n        let _ = logfile.as_write().write_vectored(&[io1, io2])?;\n    }\n}\n\npub fn setup_logfile() {\n    with_logd_fd(|mut logd| {\n        let meta = LogMeta {\n            prio: -1,\n            len: 0,\n            pid: 0,\n            tid: 0,\n        };\n        (&mut logd).write_pod(&meta)\n    });\n}\n\npub fn start_log_daemon() {\n    let path = cstr::buf::default()\n        .join_path(get_magisk_tmp())\n        .join_path(LOG_PIPE);\n\n    extern \"C\" fn logfile_writer_thread(arg: usize) -> usize {\n        let file = unsafe { File::from_raw_fd(arg as RawFd) };\n        logfile_write_loop(file).ok();\n        // If any error occurs, shut down the logd pipe\n        *MAGISK_LOGD_FD.lock() = None;\n        0\n    }\n\n    let _ = || -> LoggedResult<()> {\n        path.mkfifo(0o666).log_ok();\n        chown(path.as_utf8_cstr(), Some(Uid::from(0)), Some(Gid::from(0)))?;\n        let read = path.open(OFlag::O_RDWR | OFlag::O_CLOEXEC)?;\n        let write = path.open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)?;\n        *MAGISK_LOGD_FD.lock() = Some(Arc::new(write));\n        unsafe {\n            new_daemon_thread(logfile_writer_thread, read.into_raw_fd() as usize);\n        }\n        Ok(())\n    }();\n}\n"
  },
  {
    "path": "native/src/core/magisk.rs",
    "content": "use crate::consts::{APPLET_NAMES, MAGISK_VER_CODE, MAGISK_VERSION, POST_FS_DATA_WAIT_TIME};\nuse crate::daemon::connect_daemon;\nuse crate::ffi::{RequestCode, denylist_cli, get_magisk_tmp, install_module, unlock_blocks};\nuse crate::mount::find_preinit_device;\nuse crate::selinux::restorecon;\nuse crate::socket::{Decodable, Encodable};\nuse argh::FromArgs;\nuse base::{CmdArgs, EarlyExitExt, LoggedResult, Utf8CString, argh, clone_attr};\nuse nix::poll::{PollFd, PollFlags, PollTimeout};\nuse std::ffi::c_char;\nuse std::os::fd::AsFd;\nuse std::process::exit;\n\nfn print_usage() {\n    eprintln!(\n        r#\"Magisk - Multi-purpose Utility\n\nUsage: magisk [applet [arguments]...]\n   or: magisk [options]...\n\nOptions:\n   -c                        print current binary version\n   -v                        print running daemon version\n   -V                        print running daemon version code\n   --list                    list all available applets\n   --remove-modules [-n]     remove all modules, reboot if -n is not provided\n   --install-module ZIP      install a module zip file\n\nAdvanced Options (Internal APIs):\n   --daemon                  manually start magisk daemon\n   --stop                    remove all magisk changes and stop daemon\n   --[init trigger]          callback on init triggers. Valid triggers:\n                             post-fs-data, service, boot-complete, zygote-restart\n   --unlock-blocks           set BLKROSET flag to OFF for all block devices\n   --restorecon              restore selinux context on Magisk files\n   --clone-attr SRC DEST     clone permission, owner, and selinux context\n   --clone SRC DEST          clone SRC to DEST\n   --sqlite SQL              exec SQL commands to Magisk database\n   --path                    print Magisk tmpfs mount path\n   --denylist ARGS           denylist config CLI\n   --preinit-device          resolve a device to store preinit files\n\nAvailable applets:\n     {}\n\"#,\n        APPLET_NAMES.join(\", \")\n    );\n}\n\n#[derive(FromArgs)]\nstruct Cli {\n    #[argh(subcommand)]\n    action: MagiskAction,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand)]\nenum MagiskAction {\n    LocalVersion(LocalVersion),\n    Version(Version),\n    VersionCode(VersionCode),\n    List(ListApplets),\n    RemoveModules(RemoveModules),\n    InstallModule(InstallModule),\n    Daemon(StartDaemon),\n    Stop(StopDaemon),\n    PostFsData(PostFsData),\n    Service(ServiceCmd),\n    BootComplete(BootComplete),\n    ZygoteRestart(ZygoteRestart),\n    UnlockBlocks(UnlockBlocks),\n    RestoreCon(RestoreCon),\n    CloneAttr(CloneAttr),\n    CloneFile(CloneFile),\n    Sqlite(Sqlite),\n    Path(PathCmd),\n    DenyList(DenyList),\n    PreInitDevice(PreInitDevice),\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"-c\")]\nstruct LocalVersion {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"-v\")]\nstruct Version {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"-V\")]\nstruct VersionCode {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--list\")]\nstruct ListApplets {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--remove-modules\")]\nstruct RemoveModules {\n    #[argh(switch, short = 'n')]\n    no_reboot: bool,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--install-module\")]\nstruct InstallModule {\n    #[argh(positional)]\n    zip: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--daemon\")]\nstruct StartDaemon {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--stop\")]\nstruct StopDaemon {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--post-fs-data\")]\nstruct PostFsData {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--service\")]\nstruct ServiceCmd {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--boot-complete\")]\nstruct BootComplete {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--zygote-restart\")]\nstruct ZygoteRestart {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--unlock-blocks\")]\nstruct UnlockBlocks {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--restorecon\")]\nstruct RestoreCon {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--clone-attr\")]\nstruct CloneAttr {\n    #[argh(positional)]\n    from: Utf8CString,\n    #[argh(positional)]\n    to: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--clone\")]\nstruct CloneFile {\n    #[argh(positional)]\n    from: Utf8CString,\n    #[argh(positional)]\n    to: Utf8CString,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--sqlite\")]\nstruct Sqlite {\n    #[argh(positional)]\n    sql: String,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--path\")]\nstruct PathCmd {}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--denylist\")]\nstruct DenyList {\n    #[argh(positional, greedy)]\n    args: Vec<String>,\n}\n\n#[derive(FromArgs)]\n#[argh(subcommand, name = \"--preinit-device\")]\nstruct PreInitDevice {}\n\nimpl MagiskAction {\n    fn exec(self) -> LoggedResult<i32> {\n        use MagiskAction::*;\n        match self {\n            LocalVersion(_) => {\n                #[cfg(debug_assertions)]\n                println!(\"{MAGISK_VERSION}:MAGISK:D ({MAGISK_VER_CODE})\");\n                #[cfg(not(debug_assertions))]\n                println!(\"{MAGISK_VERSION}:MAGISK:R ({MAGISK_VER_CODE})\");\n            }\n            Version(_) => {\n                let mut fd = connect_daemon(RequestCode::CHECK_VERSION, false)?;\n                let ver = String::decode(&mut fd)?;\n                println!(\"{ver}\");\n            }\n            VersionCode(_) => {\n                let mut fd = connect_daemon(RequestCode::CHECK_VERSION_CODE, false)?;\n                let ver = i32::decode(&mut fd)?;\n                println!(\"{ver}\");\n            }\n            List(_) => {\n                for name in APPLET_NAMES {\n                    println!(\"{name}\");\n                }\n            }\n            RemoveModules(self::RemoveModules { no_reboot }) => {\n                let mut fd = connect_daemon(RequestCode::REMOVE_MODULES, false)?;\n                let do_reboot = !no_reboot;\n                do_reboot.encode(&mut fd)?;\n                return Ok(i32::decode(&mut fd)?);\n            }\n            InstallModule(self::InstallModule { zip }) => {\n                install_module(&zip);\n            }\n            Daemon(_) => {\n                let _ = connect_daemon(RequestCode::START_DAEMON, true)?;\n            }\n            Stop(_) => {\n                let mut fd = connect_daemon(RequestCode::STOP_DAEMON, false)?;\n                return Ok(i32::decode(&mut fd)?);\n            }\n            PostFsData(_) => {\n                let fd = connect_daemon(RequestCode::POST_FS_DATA, true)?;\n                let mut pfd = [PollFd::new(fd.as_fd(), PollFlags::POLLIN)];\n                nix::poll::poll(\n                    &mut pfd,\n                    PollTimeout::try_from(POST_FS_DATA_WAIT_TIME * 1000)?,\n                )?;\n            }\n            Service(_) => {\n                let _ = connect_daemon(RequestCode::LATE_START, true)?;\n            }\n            BootComplete(_) => {\n                let _ = connect_daemon(RequestCode::BOOT_COMPLETE, false)?;\n            }\n            ZygoteRestart(_) => {\n                let _ = connect_daemon(RequestCode::ZYGOTE_RESTART, false)?;\n            }\n            UnlockBlocks(_) => {\n                unlock_blocks();\n            }\n            RestoreCon(_) => {\n                restorecon();\n            }\n            CloneAttr(self::CloneAttr { from, to }) => {\n                clone_attr(&from, &to)?;\n            }\n            CloneFile(self::CloneFile { from, to }) => {\n                from.copy_to(&to)?;\n            }\n            Sqlite(self::Sqlite { sql }) => {\n                let mut fd = connect_daemon(RequestCode::SQLITE_CMD, false)?;\n                sql.encode(&mut fd)?;\n                loop {\n                    let line = String::decode(&mut fd)?;\n                    if line.is_empty() {\n                        return Ok(0);\n                    }\n                    println!(\"{line}\");\n                }\n            }\n            Path(_) => {\n                let tmp = get_magisk_tmp();\n                if tmp.is_empty() {\n                    return Ok(1);\n                } else {\n                    println!(\"{tmp}\");\n                }\n            }\n            DenyList(self::DenyList { mut args }) => {\n                return Ok(denylist_cli(&mut args));\n            }\n            PreInitDevice(_) => {\n                let name = find_preinit_device();\n                if name.is_empty() {\n                    return Ok(1);\n                } else {\n                    println!(\"{name}\");\n                }\n            }\n        };\n        Ok(0)\n    }\n}\n\npub fn magisk_main(argc: i32, argv: *mut *mut c_char) -> i32 {\n    if argc < 2 {\n        print_usage();\n        exit(1);\n    }\n    let mut cmds = CmdArgs::new(argc, argv.cast()).0;\n    // We need to manually inject \"--\" so that all actions can be treated as subcommands\n    cmds.insert(1, \"--\");\n    let cli = Cli::from_args(&cmds[..1], &cmds[1..]).on_early_exit(print_usage);\n    cli.action.exec().unwrap_or(1)\n}\n"
  },
  {
    "path": "native/src/core/module.rs",
    "content": "use crate::consts::{MODULEMNT, MODULEROOT, MODULEUPGRADE, WORKERDIR};\nuse crate::daemon::MagiskD;\nuse crate::ffi::{ModuleInfo, exec_module_scripts, exec_script, get_magisk_tmp};\nuse crate::mount::setup_module_mount;\nuse crate::resetprop::load_prop_file;\nuse base::{\n    DirEntry, Directory, FsPathBuilder, LoggedResult, OsResult, ResultExt, SilentLogExt, Utf8CStr,\n    Utf8CStrBuf, Utf8CString, WalkResult, clone_attr, cstr, debug, error, info, libc, raw_cstr,\n    warn,\n};\nuse nix::fcntl::OFlag;\nuse nix::mount::MsFlags;\nuse nix::unistd::UnlinkatFlags;\nuse std::collections::BTreeMap;\nuse std::os::fd::IntoRawFd;\nuse std::path::{Component, Path};\nuse std::ptr;\nuse std::sync::atomic::Ordering;\n\nconst MAGISK_BIN_INJECT_PARTITIONS: [&Utf8CStr; 4] = [\n    cstr!(\"/system/\"),\n    cstr!(\"/vendor/\"),\n    cstr!(\"/product/\"),\n    cstr!(\"/system_ext/\"),\n];\n\nconst SECONDARY_READ_ONLY_PARTITIONS: [&Utf8CStr; 3] =\n    [cstr!(\"/vendor\"), cstr!(\"/product\"), cstr!(\"/system_ext\")];\n\ntype FsNodeMap = BTreeMap<String, FsNode>;\n\nmacro_rules! module_log {\n    ($($args:tt)+) => {\n        debug!(\"{:8}: {} <- {}\", $($args)+)\n    }\n}\n\n#[allow(unused_variables)]\nfn bind_mount(reason: &str, src: &Utf8CStr, dest: &Utf8CStr, rec: bool) {\n    module_log!(reason, dest, src);\n    // Ignore any kind of error here. If a single bind mount fails due to selinux permissions or\n    // kernel limitations, don't let it break module mount entirely.\n    src.bind_mount_to(dest, rec).log_ok();\n    dest.remount_mount_point_flags(MsFlags::MS_RDONLY).log_ok();\n}\n\nfn mount_dummy<'a>(\n    reason: &str,\n    src: &Utf8CStr,\n    dest: &'a Utf8CStr,\n    is_dir: bool,\n) -> OsResult<'a, ()> {\n    if is_dir {\n        dest.mkdir(0o000)?;\n    } else {\n        dest.create(OFlag::O_CREAT | OFlag::O_RDONLY | OFlag::O_CLOEXEC, 0o000)?;\n    }\n    bind_mount(reason, src, dest, false);\n    Ok(())\n}\n\n// File path that act like a stack, popping out the last element\n// automatically when out of scope. Using Rust's lifetime mechanism,\n// we can ensure the buffer will never be incorrectly copied or modified.\n// After calling append or reborrow, the mutable reference's lifetime is\n// \"transferred\" to the returned object, and the compiler will guarantee\n// that the original mutable reference can only be reused if and only if\n// the newly created instance is destroyed.\nstruct PathTracker<'a> {\n    path: &'a mut dyn Utf8CStrBuf,\n    len: usize,\n}\n\nimpl PathTracker<'_> {\n    fn from<'a>(path: &'a mut dyn Utf8CStrBuf) -> PathTracker<'a> {\n        let len = path.len();\n        PathTracker { path, len }\n    }\n\n    fn append(&mut self, name: &str) -> PathTracker<'_> {\n        let len = self.path.len();\n        self.path.append_path(name);\n        PathTracker {\n            path: self.path,\n            len,\n        }\n    }\n\n    fn reborrow(&mut self) -> PathTracker<'_> {\n        Self::from(self.path)\n    }\n}\n\nimpl Drop for PathTracker<'_> {\n    // Revert back to the original state after finish using the buffer\n    fn drop(&mut self) {\n        self.path.truncate(self.len);\n    }\n}\n\n// The comments for this struct assume real = \"/system/bin\"\nstruct ModulePaths<'a> {\n    real: PathTracker<'a>,\n    module: PathTracker<'a>,\n    module_mnt: PathTracker<'a>,\n}\n\nimpl ModulePaths<'_> {\n    fn new<'a>(\n        real: &'a mut dyn Utf8CStrBuf,\n        module: &'a mut dyn Utf8CStrBuf,\n        module_mnt: &'a mut dyn Utf8CStrBuf,\n    ) -> ModulePaths<'a> {\n        real.append_path(\"/\");\n        module.append_path(MODULEROOT);\n        module_mnt\n            .append_path(get_magisk_tmp())\n            .append_path(MODULEMNT);\n        ModulePaths {\n            real: PathTracker::from(real),\n            module: PathTracker::from(module),\n            module_mnt: PathTracker::from(module_mnt),\n        }\n    }\n\n    fn set_module(&mut self, module: &str) -> ModulePaths<'_> {\n        ModulePaths {\n            real: self.real.reborrow(),\n            module: self.module.append(module),\n            module_mnt: self.module_mnt.append(module),\n        }\n    }\n\n    fn append(&mut self, name: &str) -> ModulePaths<'_> {\n        ModulePaths {\n            real: self.real.append(name),\n            module: self.module.append(name),\n            module_mnt: self.module_mnt.append(name),\n        }\n    }\n\n    // Returns \"/system/bin\"\n    fn real(&self) -> &Utf8CStr {\n        self.real.path\n    }\n\n    // Returns \"/data/adb/modules/{module}/system/bin\"\n    fn module(&self) -> &Utf8CStr {\n        self.module.path\n    }\n\n    // Returns \"$MAGISK_TMP/.magisk/modules/{module}/system/bin\"\n    fn module_mnt(&self) -> &Utf8CStr {\n        self.module_mnt.path\n    }\n}\n\n// The comments for this struct assume real = \"/system/bin\"\nstruct MountPaths<'a> {\n    real: PathTracker<'a>,\n    worker: PathTracker<'a>,\n}\n\nimpl MountPaths<'_> {\n    fn new<'a>(real: &'a mut dyn Utf8CStrBuf, worker: &'a mut dyn Utf8CStrBuf) -> MountPaths<'a> {\n        real.append_path(\"/\");\n        worker.append_path(get_magisk_tmp()).append_path(WORKERDIR);\n        MountPaths {\n            real: PathTracker::from(real),\n            worker: PathTracker::from(worker),\n        }\n    }\n\n    fn append(&mut self, name: &str) -> MountPaths<'_> {\n        MountPaths {\n            real: self.real.append(name),\n            worker: self.worker.append(name),\n        }\n    }\n\n    fn reborrow(&mut self) -> MountPaths<'_> {\n        MountPaths {\n            real: self.real.reborrow(),\n            worker: self.worker.reborrow(),\n        }\n    }\n\n    // Returns \"/system/bin\"\n    fn real(&self) -> &Utf8CStr {\n        self.real.path\n    }\n\n    // Returns \"$MAGISK_TMP/.magisk/worker/system/bin\"\n    fn worker(&self) -> &Utf8CStr {\n        self.worker.path\n    }\n}\n\nenum FsNode {\n    Directory { children: FsNodeMap },\n    File { src: Utf8CString },\n    Symlink { target: Utf8CString },\n    MagiskLink,\n    Whiteout,\n}\n\nimpl FsNode {\n    fn new_dir() -> FsNode {\n        FsNode::Directory {\n            children: BTreeMap::new(),\n        }\n    }\n\n    fn collect(&mut self, mut paths: ModulePaths) -> LoggedResult<()> {\n        let FsNode::Directory { children } = self else {\n            return Ok(());\n        };\n        let mut dir = Directory::open(paths.module())?;\n\n        while let Some(entry) = dir.read()? {\n            let entry_paths = paths.append(entry.name());\n            let path = entry_paths.module();\n            if entry.is_dir() {\n                let node = children\n                    .entry(entry.name().to_string())\n                    .or_insert_with(FsNode::new_dir);\n                node.collect(entry_paths)?;\n            } else if entry.is_symlink() {\n                // Read the link and store its target\n                let mut link = cstr::buf::default();\n                path.read_link(&mut link)?;\n                children\n                    .entry(entry.name().to_string())\n                    .or_insert_with(|| FsNode::Symlink {\n                        target: link.to_owned(),\n                    });\n            } else {\n                if entry.is_char_device() {\n                    let attr = path.get_attr()?;\n                    if attr.is_whiteout() {\n                        children\n                            .entry(entry.name().to_string())\n                            .or_insert_with(|| FsNode::Whiteout);\n                        continue;\n                    }\n                }\n                if entry_paths.real().exists() {\n                    clone_attr(entry_paths.real(), path)?;\n                }\n                children\n                    .entry(entry.name().to_string())\n                    .or_insert_with(|| FsNode::File {\n                        // Make sure to mount from module_mnt, not module\n                        src: entry_paths.module_mnt().to_owned(),\n                    });\n            }\n        }\n\n        Ok(())\n    }\n\n    // The parent node has to be tmpfs if:\n    // - Target does not exist\n    // - Source or target is a symlink (since we cannot bind mount symlink)\n    // - Source is whiteout (used for removal)\n    fn parent_should_be_tmpfs(&self, target_path: &Utf8CStr) -> bool {\n        match self {\n            FsNode::Directory { .. } | FsNode::File { .. } => {\n                if let Ok(attr) = target_path.get_attr() {\n                    attr.is_symlink()\n                } else {\n                    true\n                }\n            }\n            _ => true,\n        }\n    }\n\n    fn children(&mut self) -> Option<&mut FsNodeMap> {\n        match self {\n            FsNode::Directory { children } => Some(children),\n            _ => None,\n        }\n    }\n\n    fn commit(&mut self, mut path: MountPaths, is_root_dir: bool) -> LoggedResult<()> {\n        match self {\n            FsNode::Directory { children } => {\n                let mut is_tmpfs = false;\n\n                // First determine whether tmpfs is required\n                children.retain(|name, node| {\n                    if name == \".replace\" {\n                        return if is_root_dir {\n                            warn!(\"Unable to replace '{}', ignore request\", path.real());\n                            false\n                        } else {\n                            is_tmpfs = true;\n                            true\n                        };\n                    }\n\n                    let path = path.append(name);\n                    if node.parent_should_be_tmpfs(path.real()) {\n                        if is_root_dir {\n                            // Ignore the unsupported child node\n                            warn!(\"Unable to add '{}', skipped\", path.real());\n                            return false;\n                        }\n                        is_tmpfs = true;\n                    }\n                    true\n                });\n\n                if is_tmpfs {\n                    self.commit_tmpfs(path.reborrow())?;\n                    // Transitioning from non-tmpfs to tmpfs, we need to actually mount the\n                    // worker dir to dest after all children are committed.\n                    bind_mount(\"move\", path.worker(), path.real(), true);\n                } else {\n                    for (name, node) in children {\n                        let path = path.append(name);\n                        node.commit(path, false)?;\n                    }\n                }\n            }\n            FsNode::File { src } => {\n                bind_mount(\"mount\", src, path.real(), false);\n            }\n            _ => {\n                error!(\"Unable to handle '{}': parent should be tmpfs\", path.real());\n            }\n        }\n\n        Ok(())\n    }\n\n    fn commit_tmpfs(&mut self, mut path: MountPaths) -> LoggedResult<()> {\n        match self {\n            FsNode::Directory { children } => {\n                path.worker().mkdirs(0o000)?;\n                if path.real().exists() {\n                    clone_attr(path.real(), path.worker())?;\n                } else if let Some(p) = path.worker().parent_dir() {\n                    let parent = Utf8CString::from(p);\n                    clone_attr(&parent, path.worker())?;\n                }\n\n                // Check whether a file named '.replace' exists\n                if let Some(FsNode::File { src }) = children.remove(\".replace\")\n                    && let Some(replace_dir) = src.parent_dir()\n                {\n                    for (name, node) in children {\n                        let path = path.append(name);\n                        match node {\n                            FsNode::Directory { .. } => {\n                                // For replace, we don't need to traverse any deeper for mirroring.\n                                // We can simply just bind mount the module dir to worker dir.\n                                let src = Utf8CString::from(replace_dir).join_path(name);\n                                mount_dummy(\"mount\", &src, path.worker(), true)?;\n                            }\n                            _ => node.commit_tmpfs(path)?,\n                        }\n                    }\n\n                    // If performing replace, we skip mirroring\n                    return Ok(());\n                }\n\n                // Traverse the real directory and mount mirror files\n                if let Ok(mut dir) = Directory::open(path.real()) {\n                    while let Ok(Some(entry)) = dir.read() {\n                        if children.contains_key(entry.name().as_str()) {\n                            // Should not be mirrored, next\n                            continue;\n                        }\n\n                        let path = path.append(entry.name());\n\n                        if entry.is_dir() {\n                            // At the first glance, it looks like we can directly mount the\n                            // real dir to worker dir as mirror. However, this should NOT be done,\n                            // because init will track these mounts with dev.mnt, causing issues.\n                            // We unfortunately have to traverse recursively for mirroring.\n                            FsNode::new_dir().commit_tmpfs(path)?;\n                        } else if entry.is_symlink() {\n                            let mut link = cstr::buf::default();\n                            entry.read_link(&mut link).log_ok();\n                            FsNode::Symlink {\n                                target: link.to_owned(),\n                            }\n                            .commit_tmpfs(path)?;\n                        } else {\n                            // Mount the mirror file\n                            mount_dummy(\"mirror\", path.real(), path.worker(), false)?;\n                        }\n                    }\n                }\n\n                // Finally, commit children\n                for (name, node) in children {\n                    let path = path.append(name);\n                    node.commit_tmpfs(path)?;\n                }\n            }\n            FsNode::File { src } => {\n                mount_dummy(\"mount\", src, path.worker(), false)?;\n            }\n            FsNode::Symlink { target } => {\n                module_log!(\"mklink\", path.worker(), target);\n                path.worker().create_symlink_to(target)?;\n                if path.real().exists() {\n                    clone_attr(path.real(), path.worker())?;\n                }\n            }\n            FsNode::MagiskLink => {\n                if let Some(name) = path.real().file_name()\n                    && name == \"supolicy\"\n                {\n                    module_log!(\"mklink\", path.worker(), \"./magiskpolicy\");\n                    path.worker().create_symlink_to(cstr!(\"./magiskpolicy\"))?;\n                } else {\n                    module_log!(\"mklink\", path.worker(), \"./magisk\");\n                    path.worker().create_symlink_to(cstr!(\"./magisk\"))?;\n                }\n            }\n            FsNode::Whiteout => {\n                module_log!(\"delete\", path.real(), \"null\");\n            }\n        }\n        Ok(())\n    }\n}\n\nfn get_path_env() -> String {\n    std::env::var_os(\"PATH\")\n        .and_then(|s| s.into_string().ok())\n        .unwrap_or_default()\n}\n\nfn inject_magisk_bins(system: &mut FsNode, is_emulator: bool) {\n    fn inject(children: &mut FsNodeMap) {\n        let mut path = cstr::buf::default().join_path(get_magisk_tmp());\n\n        // Inject binaries\n\n        let len = path.len();\n        path.append_path(\"magisk\");\n        children.insert(\n            \"magisk\".to_string(),\n            FsNode::File {\n                src: path.to_owned(),\n            },\n        );\n\n        path.truncate(len);\n        path.append_path(\"magiskpolicy\");\n        children.insert(\n            \"magiskpolicy\".to_string(),\n            FsNode::File {\n                src: path.to_owned(),\n            },\n        );\n\n        // Inject applet symlinks\n        children.insert(\"su\".to_string(), FsNode::MagiskLink);\n        children.insert(\"resetprop\".to_string(), FsNode::MagiskLink);\n        children.insert(\"supolicy\".to_string(), FsNode::MagiskLink);\n    }\n\n    // Strip /system prefix to insert correct node\n    fn strip_system_prefix(orig_item: &str) -> String {\n        match orig_item.strip_prefix(\"/system/\") {\n            Some(rest) => format!(\"/{rest}\"),\n            None => orig_item.to_string(),\n        }\n    }\n\n    let path_env = get_path_env();\n    let mut candidates = vec![];\n\n    for orig_item in path_env.split(':') {\n        // Filter non-suitable paths\n        if !MAGISK_BIN_INJECT_PARTITIONS\n            .iter()\n            .any(|p| orig_item.starts_with(p.as_str()))\n        {\n            continue;\n        }\n        // Flatten apex path is not suitable too\n        if orig_item.starts_with(\"/system/apex/\") {\n            continue;\n        }\n\n        // We want to keep /system/xbin/su on emulators (for debugging)\n        if is_emulator && orig_item.starts_with(\"/system/xbin\") {\n            continue;\n        }\n\n        // Override existing su first\n        let su_path = Utf8CString::from(format!(\"{orig_item}/su\"));\n        if su_path.exists() {\n            let item = strip_system_prefix(orig_item);\n            candidates.push((item, 0));\n            break;\n        }\n\n        let path = Utf8CString::from(orig_item);\n        if let Ok(attr) = path.get_attr()\n            && (attr.st.st_mode & 0x0001) != 0\n            && let Ok(mut dir) = Directory::open(&path)\n        {\n            let mut count = 0;\n            if dir\n                .pre_order_walk(|e| {\n                    if e.is_file() {\n                        count += 1;\n                    }\n                    Ok(WalkResult::Continue)\n                })\n                .is_err()\n            {\n                // Skip, we cannot ensure the result is correct\n                continue;\n            }\n            let item = strip_system_prefix(orig_item);\n            candidates.push((item, count));\n        }\n    }\n\n    // Sort by amount of files\n    candidates.sort_by_key(|&(_, count)| count);\n\n    'path_loop: for candidate in candidates {\n        let components = Path::new(&candidate.0)\n            .components()\n            .filter(|c| matches!(c, Component::Normal(_)))\n            .filter_map(|c| c.as_os_str().to_str());\n\n        let mut curr = match system {\n            FsNode::Directory { children } => children,\n            _ => continue,\n        };\n\n        for dir in components {\n            let node = curr.entry(dir.to_owned()).or_insert_with(FsNode::new_dir);\n            match node {\n                FsNode::Directory { children } => curr = children,\n                _ => continue 'path_loop,\n            }\n        }\n\n        // Found a suitable path, done\n        inject(curr);\n        return;\n    }\n\n    // If still not found, directly inject into /system/bin\n    let node = system\n        .children()\n        .map(|c| c.entry(\"bin\".to_string()).or_insert_with(FsNode::new_dir));\n    if let Some(FsNode::Directory { children }) = node {\n        inject(children)\n    }\n}\n\nfn inject_zygisk_bins(name: &str, system: &mut FsNode) {\n    #[cfg(target_pointer_width = \"64\")]\n    let has_32_bit = cstr!(\"/system/bin/linker\").exists();\n\n    #[cfg(target_pointer_width = \"32\")]\n    let has_32_bit = true;\n\n    if has_32_bit {\n        let lib = system\n            .children()\n            .map(|c| c.entry(\"lib\".to_string()).or_insert_with(FsNode::new_dir));\n        if let Some(FsNode::Directory { children }) = lib {\n            let mut bin_path = cstr::buf::default().join_path(get_magisk_tmp());\n\n            #[cfg(target_pointer_width = \"64\")]\n            bin_path.append_path(\"magisk32\");\n\n            #[cfg(target_pointer_width = \"32\")]\n            bin_path.append_path(\"magisk\");\n\n            // There are some devices that announce ABI as 64 bit only, but ship with linker\n            // because they make use of a special 32 bit to 64 bit translator (such as tango).\n            // In this case, magisk32 does not exist, so inserting it will cause bind mount\n            // failure and affect module mount. Native bridge injection does not support these\n            // kind of translators anyway, so simply check if magisk32 exists here.\n            if bin_path.exists() {\n                children.insert(\n                    name.to_string(),\n                    FsNode::File {\n                        src: bin_path.to_owned(),\n                    },\n                );\n            }\n        }\n    }\n\n    #[cfg(target_pointer_width = \"64\")]\n    if cstr!(\"/system/bin/linker64\").exists() {\n        let lib64 = system\n            .children()\n            .map(|c| c.entry(\"lib64\".to_string()).or_insert_with(FsNode::new_dir));\n        if let Some(FsNode::Directory { children }) = lib64 {\n            let bin_path = cstr::buf::default()\n                .join_path(get_magisk_tmp())\n                .join_path(\"magisk\");\n\n            children.insert(\n                name.to_string(),\n                FsNode::File {\n                    src: bin_path.to_owned(),\n                },\n            );\n        }\n    }\n}\n\nfn upgrade_modules() -> LoggedResult<()> {\n    let mut upgrade = Directory::open(cstr!(MODULEUPGRADE)).silent()?;\n    let root = Directory::open(cstr!(MODULEROOT))?;\n    while let Some(e) = upgrade.read()? {\n        if !e.is_dir() {\n            continue;\n        }\n        let module_name = e.name();\n        let mut disable = false;\n        // Cleanup old module if exists\n        if root.contains_path(module_name) {\n            let module = root.open_as_dir_at(module_name)?;\n            // If the old module is disabled, we need to also disable the new one\n            disable = module.contains_path(cstr!(\"disable\"));\n            module.remove_all()?;\n            root.unlink_at(module_name, UnlinkatFlags::RemoveDir)?;\n        }\n        info!(\"Upgrade / New module: {module_name}\");\n        e.rename_to(&root, module_name)?;\n        if disable {\n            let path = cstr::buf::default()\n                .join_path(module_name)\n                .join_path(\"disable\");\n            let _ = root.open_as_file_at(\n                &path,\n                OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_CLOEXEC,\n                0,\n            )?;\n        }\n    }\n    upgrade.remove_all()?;\n    cstr!(MODULEUPGRADE).remove()?;\n    Ok(())\n}\n\nfn for_each_module(mut func: impl FnMut(&DirEntry) -> LoggedResult<()>) -> LoggedResult<()> {\n    let mut root = Directory::open(cstr!(MODULEROOT))?;\n    while let Some(ref e) = root.read()? {\n        if e.is_dir() && e.name() != \".core\" {\n            func(e)?;\n        }\n    }\n    Ok(())\n}\n\npub fn disable_modules() {\n    for_each_module(|e| {\n        let dir = e.open_as_dir()?;\n        dir.open_as_file_at(\n            cstr!(\"disable\"),\n            OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_CLOEXEC,\n            0,\n        )?;\n        Ok(())\n    })\n    .log_ok();\n}\n\nfn run_uninstall_script(module_name: &Utf8CStr) {\n    let script = cstr::buf::default()\n        .join_path(MODULEROOT)\n        .join_path(module_name)\n        .join_path(\"uninstall.sh\");\n    exec_script(&script);\n}\n\npub fn remove_modules() {\n    for_each_module(|e| {\n        let dir = e.open_as_dir()?;\n        if dir.contains_path(cstr!(\"uninstall.sh\")) {\n            run_uninstall_script(e.name());\n        }\n        Ok(())\n    })\n    .log_ok();\n    cstr!(MODULEROOT).remove_all().log_ok();\n}\n\nfn collect_modules(zygisk_enabled: bool, open_zygisk: bool) -> Vec<ModuleInfo> {\n    let mut modules = Vec::new();\n\n    #[allow(unused_mut)] // It's possible that z32 and z64 are unused\n    for_each_module(|e| {\n        let name = e.name();\n        let dir = e.open_as_dir()?;\n        if dir.contains_path(cstr!(\"remove\")) {\n            info!(\"{name}: remove\");\n            if dir.contains_path(cstr!(\"uninstall.sh\")) {\n                run_uninstall_script(name);\n            }\n            dir.remove_all()?;\n            e.unlink()?;\n            return Ok(());\n        }\n        dir.unlink_at(cstr!(\"update\"), UnlinkatFlags::NoRemoveDir)\n            .ok();\n        if dir.contains_path(cstr!(\"disable\")) {\n            return Ok(());\n        }\n\n        let mut z32 = -1;\n        let mut z64 = -1;\n\n        let is_zygisk = dir.contains_path(cstr!(\"zygisk\"));\n\n        if zygisk_enabled {\n            // Riru and its modules are not compatible with zygisk\n            if name == \"riru-core\" || dir.contains_path(cstr!(\"riru\")) {\n                return Ok(());\n            }\n\n            fn open_fd_safe(dir: &Directory, name: &Utf8CStr) -> i32 {\n                dir.open_as_file_at(name, OFlag::O_RDONLY | OFlag::O_CLOEXEC, 0)\n                    .log()\n                    .map(IntoRawFd::into_raw_fd)\n                    .unwrap_or(-1)\n            }\n\n            if open_zygisk && is_zygisk {\n                #[cfg(target_arch = \"arm\")]\n                {\n                    z32 = open_fd_safe(&dir, cstr!(\"zygisk/armeabi-v7a.so\"));\n                }\n                #[cfg(target_arch = \"aarch64\")]\n                {\n                    z32 = open_fd_safe(&dir, cstr!(\"zygisk/armeabi-v7a.so\"));\n                    z64 = open_fd_safe(&dir, cstr!(\"zygisk/arm64-v8a.so\"));\n                }\n                #[cfg(target_arch = \"x86\")]\n                {\n                    z32 = open_fd_safe(&dir, cstr!(\"zygisk/x86.so\"));\n                }\n                #[cfg(target_arch = \"x86_64\")]\n                {\n                    z32 = open_fd_safe(&dir, cstr!(\"zygisk/x86.so\"));\n                    z64 = open_fd_safe(&dir, cstr!(\"zygisk/x86_64.so\"));\n                }\n                #[cfg(target_arch = \"riscv64\")]\n                {\n                    z64 = open_fd_safe(&dir, cstr!(\"zygisk/riscv64.so\"));\n                }\n                dir.unlink_at(cstr!(\"zygisk/unloaded\"), UnlinkatFlags::NoRemoveDir)\n                    .ok();\n            }\n        } else {\n            // Ignore zygisk modules when zygisk is not enabled\n            if is_zygisk {\n                info!(\"{name}: ignore\");\n                return Ok(());\n            }\n        }\n        modules.push(ModuleInfo {\n            name: name.to_string(),\n            z32,\n            z64,\n        });\n        Ok(())\n    })\n    .log_ok();\n\n    if zygisk_enabled && open_zygisk {\n        let mut use_memfd = true;\n        let mut convert_to_memfd = |fd: i32| -> i32 {\n            if fd < 0 {\n                return fd;\n            }\n            if use_memfd {\n                let memfd = unsafe {\n                    libc::syscall(\n                        libc::SYS_memfd_create,\n                        raw_cstr!(\"jit-cache\"),\n                        libc::MFD_CLOEXEC,\n                    ) as i32\n                };\n                if memfd >= 0 {\n                    unsafe {\n                        if libc::sendfile(memfd, fd, ptr::null_mut(), i32::MAX as usize) < 0 {\n                            libc::close(memfd);\n                        } else {\n                            libc::close(fd);\n                            return memfd;\n                        }\n                    }\n                }\n                // Some error occurred, don't try again\n                use_memfd = false;\n            }\n            fd\n        };\n\n        modules.iter_mut().for_each(|m| {\n            m.z32 = convert_to_memfd(m.z32);\n            m.z64 = convert_to_memfd(m.z64);\n        });\n    }\n\n    modules\n}\n\nimpl MagiskD {\n    pub fn handle_modules(&self) {\n        setup_module_mount();\n        upgrade_modules().ok();\n\n        let zygisk = self.zygisk_enabled.load(Ordering::Acquire);\n        let modules = collect_modules(zygisk, false);\n        exec_module_scripts(cstr!(\"post-fs-data\"), &modules);\n\n        // Recollect modules (module scripts could remove itself)\n        let modules = collect_modules(zygisk, true);\n        self.apply_modules(&modules);\n\n        self.module_list.set(modules).ok();\n    }\n\n    fn apply_modules(&self, module_list: &[ModuleInfo]) {\n        let mut system = FsNode::new_dir();\n\n        // Create buffers for paths\n        let mut buf1 = cstr::buf::dynamic(256);\n        let mut buf2 = cstr::buf::dynamic(256);\n        let mut buf3 = cstr::buf::dynamic(256);\n\n        let mut paths = ModulePaths::new(&mut buf1, &mut buf2, &mut buf3);\n\n        // Step 1: Create virtual filesystem tree\n        //\n        // In this step, there is zero logic applied during tree construction; we simply collect and\n        // record the union of all module filesystem trees under each of their /system directory.\n\n        for info in module_list {\n            let mut paths = paths.set_module(&info.name);\n\n            // Read props\n            let prop = paths.append(\"system.prop\");\n            if prop.module().exists() {\n                load_prop_file(prop.module());\n            }\n            drop(prop);\n\n            // Check whether skip mounting\n            let skip = paths.append(\"skip_mount\");\n            if skip.module().exists() {\n                continue;\n            }\n            drop(skip);\n\n            // Double check whether the system folder exists\n            let sys = paths.append(\"system\");\n            if sys.module().exists() {\n                info!(\"{}: loading module files\", &info.name);\n                system.collect(sys).log_ok();\n            }\n        }\n\n        // Step 2: Inject custom files\n        //\n        // Magisk provides some built-in functionality that requires augmenting the filesystem.\n        // We expose several cmdline tools (e.g. su) into PATH, and the zygisk shared library\n        // has to also be added into the default LD_LIBRARY_PATH for code injection.\n        // We directly inject file nodes into the virtual filesystem tree we built in the previous\n        // step, treating Magisk just like a special \"module\".\n\n        if get_magisk_tmp() != \"/sbin\" || get_path_env().split(\":\").all(|s| s != \"/sbin\") {\n            inject_magisk_bins(&mut system, self.is_emulator);\n        }\n\n        // Handle zygisk\n        if self.zygisk_enabled.load(Ordering::Acquire) {\n            let mut zygisk = self.zygisk.lock();\n            zygisk.set_prop();\n            inject_zygisk_bins(&zygisk.lib_name, &mut system);\n        }\n\n        // Step 3: Extract all supported read-only partition roots\n        //\n        // For simplicity and backwards compatibility on older Android versions, when constructing\n        // Magisk modules, we always assume that there is only a single read-only partition mounted\n        // at /system. However, on modern Android there are actually multiple read-only partitions\n        // mounted at their respective paths. We need to extract these subtrees out of the main\n        // tree and treat them as individual trees.\n\n        let mut roots = BTreeMap::new(); /* mapOf(partition_name -> FsNode) */\n        if let FsNode::Directory { children } = &mut system {\n            for dir in SECONDARY_READ_ONLY_PARTITIONS {\n                // Only treat these nodes as root iff it is actually a directory in rootdir\n                if let Ok(attr) = dir.get_attr()\n                    && attr.is_dir()\n                {\n                    let name = dir.trim_start_matches('/');\n                    if let Some(root) = children.remove(name) {\n                        roots.insert(name, root);\n                    }\n                }\n            }\n        }\n        roots.insert(\"system\", system);\n\n        drop(paths);\n        let mut paths = MountPaths::new(&mut buf1, &mut buf2);\n\n        for (dir, mut root) in roots {\n            // Step 4: Convert virtual filesystem tree into concrete operations\n            //\n            // Compare the virtual filesystem tree we constructed against the real filesystem\n            // structure on-device to generate a series of \"operations\".\n            // The \"core\" of the logic is to decide which directories need to be rebuilt in the\n            // tmpfs worker directory, and real sub-nodes need to be mirrored inside it.\n\n            let paths = paths.append(dir);\n            root.commit(paths, true).log_ok();\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/core/mount.rs",
    "content": "use crate::consts::{MODULEMNT, MODULEROOT, PREINITDEV, PREINITMIRR, WORKERDIR};\nuse crate::ffi::{get_magisk_tmp, resolve_preinit_dir, switch_mnt_ns};\nuse crate::resetprop::get_prop;\nuse base::{\n    FsPathBuilder, LibcReturn, LoggedResult, MountInfo, ResultExt, Utf8CStr, Utf8CStrBuf, cstr,\n    debug, info, libc, parse_mount_info, warn,\n};\nuse libc::{c_uint, dev_t, major};\nuse nix::mount::MsFlags;\nuse nix::sys::stat::{Mode, SFlag, mknod};\nuse num_traits::AsPrimitive;\nuse std::cmp::Ordering::{Greater, Less};\nuse std::ffi::OsStr;\nuse std::path::{Path, PathBuf};\n\n// Linux allocated devices: 240-254 are reserved for LOCAL/EXPERIMENTAL use.\nconst DYNAMIC_MAJOR_MIN: u32 = 240;\nconst DYNAMIC_MAJOR_MAX: u32 = 254;\n\npub fn setup_preinit_dir() {\n    let magisk_tmp = get_magisk_tmp();\n\n    // Mount preinit directory\n    let dev_path = cstr::buf::new::<64>()\n        .join_path(magisk_tmp)\n        .join_path(PREINITDEV);\n    if let Ok(attr) = dev_path.get_attr()\n        && attr.st.st_mode & libc::S_IFMT as c_uint == libc::S_IFBLK.as_()\n    {\n        // DO NOT mount the block device directly, as we do not know the flags and configs\n        // to properly mount the partition; mounting block devices directly as rw could cause\n        // crashes if the filesystem driver is crap (e.g. some broken F2FS drivers).\n        // What we do instead is to scan through the current mountinfo and find a pre-existing\n        // mount point mounting our desired partition, and then bind mount the target folder.\n        let preinit_dev = attr.st.st_rdev;\n        let mnt_path = cstr::buf::default()\n            .join_path(magisk_tmp)\n            .join_path(PREINITMIRR);\n        for info in parse_mount_info(\"self\") {\n            if info.root == \"/\" && info.device == preinit_dev {\n                if !info.fs_option.split(',').any(|s| s == \"rw\") {\n                    // Only care about rw mounts\n                    continue;\n                }\n                let mut target = info.target;\n                let target = Utf8CStr::from_string(&mut target);\n                let mut preinit_dir = resolve_preinit_dir(target);\n                let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);\n                let r = || -> LoggedResult<()> {\n                    preinit_dir.mkdir(0o700)?;\n                    mnt_path.mkdirs(0o755)?;\n                    mnt_path.remove().ok();\n                    mnt_path.create_symlink_to(preinit_dir)?;\n                    Ok(())\n                }();\n                if r.is_ok() {\n                    info!(\"* Found preinit dir: {}\", preinit_dir);\n                    return;\n                }\n            }\n        }\n    }\n\n    warn!(\"mount: preinit dir not found\");\n}\n\npub fn setup_module_mount() {\n    // Bind remount module root to clear nosuid\n    let module_mnt = cstr::buf::default()\n        .join_path(get_magisk_tmp())\n        .join_path(MODULEMNT);\n    let _ = || -> LoggedResult<()> {\n        module_mnt.mkdir(0o755)?;\n        cstr!(MODULEROOT).bind_mount_to(&module_mnt, false)?;\n        module_mnt.remount_mount_point_flags(MsFlags::MS_RDONLY)?;\n        Ok(())\n    }();\n}\n\npub fn clean_mounts() {\n    let magisk_tmp = get_magisk_tmp();\n\n    let mut buf = cstr::buf::default();\n\n    let module_mnt = buf.append_path(magisk_tmp).append_path(MODULEMNT);\n    module_mnt.unmount().log_ok();\n    buf.clear();\n\n    let worker_dir = buf.append_path(magisk_tmp).append_path(WORKERDIR);\n    let _ = || -> LoggedResult<()> {\n        worker_dir.set_mount_private(true)?;\n        worker_dir.unmount()?;\n        Ok(())\n    }();\n}\n\n// when partitions have the same fs type, the order is:\n// - data: it has sufficient space and can be safely written\n// - cache: size is limited, but still can be safely written\n// - klogdump: available on some Smartisan devices and can be safely written\n// - metadata: size is limited, and it might cause unexpected behavior if written\n// - persist: it's the last resort, as it's dangerous to write to it\n#[derive(PartialEq, Eq, PartialOrd, Ord)]\nenum PartId {\n    Data,\n    Cache,\n    Klogdump,\n    Metadata,\n    Persist,\n}\n\nenum EncryptType {\n    None,\n    Block,\n    File,\n    Metadata,\n}\n\npub fn find_preinit_device() -> String {\n    let encrypt_type = if get_prop(cstr!(\"ro.crypto.state\")) != \"encrypted\" {\n        EncryptType::None\n    } else if get_prop(cstr!(\"ro.crypto.type\")) == \"block\" {\n        EncryptType::Block\n    } else if get_prop(cstr!(\"ro.crypto.metadata.enabled\")) == \"true\" {\n        EncryptType::Metadata\n    } else {\n        EncryptType::File\n    };\n\n    let mut matched_info = parse_mount_info(\"self\")\n        .into_iter()\n        .filter_map(|info| {\n            if info.root != \"/\" || !info.source.starts_with('/') || info.source.contains(\"/dm-\") {\n                return None;\n            }\n            match info.fs_type.as_str() {\n                \"ext4\" | \"f2fs\" => (),\n                _ => return None,\n            }\n            if !info.fs_option.split(',').any(|s| s == \"rw\") {\n                return None;\n            }\n            if let Some(path) = Path::new(&info.source).parent() {\n                if !path.ends_with(\"by-name\") && !path.ends_with(\"block\") {\n                    return None;\n                }\n            } else {\n                return None;\n            }\n            // use device major number to filter out device-mapper\n            let maj = major(info.device as dev_t) as u32;\n            if (DYNAMIC_MAJOR_MIN..=DYNAMIC_MAJOR_MAX).contains(&maj)\n                && !info.source.contains(\"/vd\")\n                && !info.source.contains(\"/by-name/\")\n            {\n                return None;\n            }\n            // take data iff it's not encrypted or file-based encrypted without metadata\n            // other partitions are always taken\n            match info.target.as_str() {\n                \"/persist\" | \"/mnt/vendor/persist\" => Some((PartId::Persist, info)),\n                \"/metadata\" => Some((PartId::Metadata, info)),\n                \"/klogdump\" => Some((PartId::Klogdump, info)),\n                \"/cache\" => Some((PartId::Cache, info)),\n                \"/data\" => Some((PartId::Data, info))\n                    .take_if(|_| matches!(encrypt_type, EncryptType::None | EncryptType::File)),\n                _ => None,\n            }\n        })\n        .collect::<Vec<_>>();\n\n    if matched_info.is_empty() {\n        return String::new();\n    }\n\n    let (_, preinit_info, _) = matched_info.select_nth_unstable_by(\n        0,\n        |(ap, MountInfo { fs_type: at, .. }), (bp, MountInfo { fs_type: bt, .. })| match (\n            ap,\n            bp,\n            at.as_str() == \"ext4\",\n            bt.as_str() == \"ext4\",\n        ) {\n            // metadata is not affected by f2fs kernel bug\n            (PartId::Metadata, _, _, true) | (_, PartId::Metadata, true, _) => ap.cmp(bp),\n            // otherwise, take ext4 f2fs because f2fs has a kernel bug that causes kernel panic\n            (_, _, true, false) => Less,\n            (_, _, false, true) => Greater,\n            // if both has the same fs type, compare the mount point\n            _ => ap.cmp(bp),\n        },\n    );\n    let info = &preinit_info.1;\n    let mut target = info.target.clone();\n    let mut preinit_dir = resolve_preinit_dir(Utf8CStr::from_string(&mut target));\n    if unsafe { libc::getuid() } == 0\n        && let Ok(tmp) = std::env::var(\"MAGISKTMP\")\n        && !tmp.is_empty()\n    {\n        let mut buf = cstr::buf::default();\n        let mirror_dir = buf.append_path(&tmp).append_path(PREINITMIRR);\n        let preinit_dir = Utf8CStr::from_string(&mut preinit_dir);\n        let _ = || -> LoggedResult<()> {\n            preinit_dir.mkdirs(0o700)?;\n            mirror_dir.mkdirs(0o755)?;\n            mirror_dir.unmount().ok();\n            mirror_dir.remove().ok();\n            mirror_dir.create_symlink_to(preinit_dir)?;\n            Ok(())\n        }();\n        if std::env::var_os(\"MAKEDEV\").is_some() {\n            buf.clear();\n            let dev_path = buf.append_path(&tmp).append_path(PREINITDEV);\n            mknod(\n                dev_path.as_utf8_cstr(),\n                SFlag::S_IFBLK,\n                Mode::from_bits_truncate(0o600),\n                info.device as dev_t,\n            )\n            .check_os_err(\"mknod\", Some(dev_path), None)\n            .log_ok();\n        }\n    }\n    Path::new(&info.source)\n        .file_name()\n        .and_then(OsStr::to_str)\n        .unwrap_or_default()\n        .to_string()\n}\n\npub fn revert_unmount(pid: i32) {\n    if pid > 0 {\n        if switch_mnt_ns(pid) != 0 {\n            return;\n        }\n        debug!(\"denylist: handling PID=[{}]\", pid);\n    }\n\n    let mut targets = Vec::new();\n\n    // Unmount Magisk tmpfs and mounts from module files\n    for info in parse_mount_info(\"self\") {\n        if info.source == \"magisk\" || info.root.starts_with(\"/adb/modules\") {\n            targets.push(info.target);\n        }\n    }\n\n    if targets.is_empty() {\n        return;\n    }\n\n    let mut prev: Option<PathBuf> = None;\n    targets.sort();\n    targets.retain(|target| {\n        if let Some(prev) = &prev\n            && Path::new(target).starts_with(prev)\n        {\n            return false;\n        }\n        prev = Some(PathBuf::from(target.clone()));\n        true\n    });\n\n    for mut target in targets {\n        let target = Utf8CStr::from_string(&mut target);\n        if target.unmount().is_ok() {\n            debug!(\"denylist: Unmounted ({})\", target);\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/core/package.rs",
    "content": "use crate::consts::{APP_PACKAGE_NAME, MAGISK_VER_CODE};\nuse crate::daemon::{AID_APP_END, AID_APP_START, AID_USER_OFFSET, MagiskD, to_app_id};\nuse crate::ffi::{DbEntryKey, get_magisk_tmp, install_apk, uninstall_pkg};\nuse base::WalkResult::{Abort, Continue, Skip};\nuse base::{\n    BufReadExt, Directory, FsPathBuilder, LoggedResult, ReadExt, ResultExt, Utf8CStrBuf,\n    Utf8CString, cstr, error, fd_get_attr, warn,\n};\nuse bit_set::BitSet;\nuse nix::fcntl::OFlag;\nuse std::collections::BTreeMap;\nuse std::fs::File;\nuse std::io;\nuse std::io::{Cursor, Read, Seek, SeekFrom};\nuse std::os::fd::AsRawFd;\nuse std::time::Duration;\n\nconst EOCD_MAGIC: u32 = 0x06054B50;\nconst APK_SIGNING_BLOCK_MAGIC: [u8; 16] = *b\"APK Sig Block 42\";\nconst SIGNATURE_SCHEME_V2_MAGIC: u32 = 0x7109871A;\nconst PACKAGES_XML: &str = \"/data/system/packages.xml\";\n\nmacro_rules! bad_apk {\n    ($msg:literal) => {\n        io::Error::new(io::ErrorKind::InvalidData, concat!(\"cert: \", $msg))\n    };\n}\n\n/*\n * A v2/v3 signed APK has the format as following\n *\n * +---------------+\n * | zip content   |\n * +---------------+\n * | signing block |\n * +---------------+\n * | central dir   |\n * +---------------+\n * | EOCD          |\n * +---------------+\n *\n * Scan from end of file to find EOCD, and figure our way back to the\n * offset of the signing block. Next, directly extract the certificate\n * from the v2 signature block.\n *\n * All structures above are mostly just for documentation purpose.\n *\n * This method extracts the first certificate of the first signer\n * within the APK v2 signature block.\n */\nfn read_certificate(apk: &mut File, version: i32) -> Vec<u8> {\n    let res = || -> io::Result<Vec<u8>> {\n        let mut u32_val = 0u32;\n        let mut u64_val = 0u64;\n\n        // Find EOCD\n        for i in 0u16.. {\n            let mut comment_sz = 0u16;\n            apk.seek(SeekFrom::End(-(size_of_val(&comment_sz) as i64) - i as i64))?;\n            apk.read_pod(&mut comment_sz)?;\n\n            if comment_sz == i {\n                apk.seek(SeekFrom::Current(-22))?;\n                let mut magic = 0u32;\n                apk.read_pod(&mut magic)?;\n                if magic == EOCD_MAGIC {\n                    break;\n                }\n            }\n            if i == 0xffff {\n                Err(bad_apk!(\"invalid APK format\"))?;\n            }\n        }\n\n        // We are now at EOCD + sizeof(magic)\n        // Seek and read central_dir_off to find the start of the central directory\n        let mut central_dir_off = 0u32;\n        apk.seek(SeekFrom::Current(12))?;\n        apk.read_pod(&mut central_dir_off)?;\n\n        // Code for parse APK comment to get version code\n        if version >= 0 {\n            let mut comment_sz = 0u16;\n            apk.read_pod(&mut comment_sz)?;\n            let mut comment = vec![0u8; comment_sz as usize];\n            apk.read_exact(&mut comment)?;\n            let mut comment = Cursor::new(&comment);\n            let mut apk_ver = 0;\n            comment.for_each_prop(|k, v| {\n                if k == \"versionCode\" {\n                    apk_ver = v.parse::<i32>().unwrap_or(0);\n                    false\n                } else {\n                    true\n                }\n            });\n            if version > apk_ver {\n                Err(bad_apk!(\"APK version too low\"))?;\n            }\n        }\n\n        // Next, find the start of the APK signing block\n        apk.seek(SeekFrom::Start((central_dir_off - 24) as u64))?;\n        apk.read_pod(&mut u64_val)?; // u64_value = block_sz_\n        let mut magic = [0u8; 16];\n        apk.read_exact(&mut magic)?;\n        if magic != APK_SIGNING_BLOCK_MAGIC {\n            Err(bad_apk!(\"invalid signing block magic\"))?;\n        }\n        let mut signing_blk_sz = 0u64;\n        apk.seek(SeekFrom::Current(\n            -(u64_val as i64) - (size_of_val(&signing_blk_sz) as i64),\n        ))?;\n        apk.read_pod(&mut signing_blk_sz)?;\n        if signing_blk_sz != u64_val {\n            Err(bad_apk!(\"invalid signing block size\"))?;\n        }\n\n        // Finally, we are now at the beginning of the id-value pair sequence\n        loop {\n            apk.read_pod(&mut u64_val)?; // id-value pair length\n            if u64_val == signing_blk_sz {\n                Err(bad_apk!(\"cannot find certificate\"))?;\n            }\n\n            let mut id = 0u32;\n            apk.read_pod(&mut id)?;\n            if id == SIGNATURE_SCHEME_V2_MAGIC {\n                // Skip [signer sequence length] + [1st signer length] + [signed data length]\n                apk.seek(SeekFrom::Current((size_of_val(&u32_val) * 3) as i64))?;\n\n                apk.read_pod(&mut u32_val)?; // digest sequence length\n                apk.seek(SeekFrom::Current(u32_val as i64))?; // skip all digests\n\n                apk.seek(SeekFrom::Current(size_of_val(&u32_val) as i64))?; // cert sequence length\n                apk.read_pod(&mut u32_val)?; // 1st cert length\n\n                let mut cert = vec![0; u32_val as usize];\n                apk.read_exact(cert.as_mut())?;\n                break Ok(cert);\n            } else {\n                // Skip this id-value pair\n                apk.seek(SeekFrom::Current(\n                    u64_val as i64 - (size_of_val(&id) as i64),\n                ))?;\n            }\n        }\n    }();\n    res.log().unwrap_or(vec![])\n}\n\nfn find_apk_path(pkg: &str) -> LoggedResult<Utf8CString> {\n    let mut buf = cstr::buf::default();\n    Directory::open(cstr!(\"/data/app\"))?.pre_order_walk(|e| {\n        if !e.is_dir() {\n            return Ok(Skip);\n        }\n        let name_bytes = e.name().as_bytes();\n        if name_bytes.starts_with(pkg.as_bytes()) && name_bytes[pkg.len()] == b'-' {\n            // Found the APK path, we can abort now\n            e.resolve_path(&mut buf)?;\n            return Ok(Abort);\n        }\n        if name_bytes.starts_with(b\"~~\") {\n            return Ok(Continue);\n        }\n        Ok(Skip)\n    })?;\n    if !buf.is_empty() {\n        buf.push_str(\"/base.apk\");\n    }\n    Ok(buf.to_owned())\n}\n\nenum Status {\n    Installed,\n    NotInstalled,\n    CertMismatch,\n}\n\npub struct ManagerInfo {\n    stub_apk_fd: Option<File>,\n    trusted_cert: Vec<u8>,\n    repackaged_app_id: i32,\n    repackaged_pkg: String,\n    repackaged_cert: Vec<u8>,\n    tracked_files: BTreeMap<i32, TrackedFile>,\n}\n\nimpl Default for ManagerInfo {\n    fn default() -> Self {\n        ManagerInfo {\n            stub_apk_fd: None,\n            trusted_cert: Vec::new(),\n            repackaged_app_id: -1,\n            repackaged_pkg: String::new(),\n            repackaged_cert: Vec::new(),\n            tracked_files: BTreeMap::new(),\n        }\n    }\n}\n\n#[derive(Default)]\nstruct TrackedFile {\n    path: Utf8CString,\n    timestamp: Duration,\n}\n\nimpl TrackedFile {\n    fn new(path: Utf8CString) -> TrackedFile {\n        let attr = match path.get_attr() {\n            Ok(attr) => attr,\n            Err(_) => return TrackedFile::default(),\n        };\n        let timestamp = Duration::new(attr.st.st_ctime as u64, attr.st.st_ctime_nsec as u32);\n        TrackedFile { path, timestamp }\n    }\n\n    fn is_same(&self) -> bool {\n        if self.path.is_empty() {\n            return false;\n        }\n        let attr = match self.path.get_attr() {\n            Ok(attr) => attr,\n            Err(_) => return false,\n        };\n        let timestamp = Duration::new(attr.st.st_ctime as u64, attr.st.st_ctime_nsec as u32);\n        timestamp == self.timestamp\n    }\n}\n\nimpl ManagerInfo {\n    fn check_dyn(&mut self, daemon: &MagiskD, user: i32, pkg: &str) -> Status {\n        let apk = cstr::buf::default()\n            .join_path(daemon.app_data_dir())\n            .join_path_fmt(user)\n            .join_path(pkg)\n            .join_path(\"dyn\")\n            .join_path(\"current.apk\");\n        let uid: i32;\n        let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n            Ok(mut fd) => {\n                uid = fd_get_attr(fd.as_raw_fd())\n                    .map(|attr| attr.st.st_uid as i32)\n                    .unwrap_or(-1);\n                read_certificate(&mut fd, MAGISK_VER_CODE)\n            }\n            Err(_) => {\n                warn!(\"pkg: no dyn APK, ignore\");\n                return Status::NotInstalled;\n            }\n        };\n\n        if cert.is_empty() || cert != self.trusted_cert {\n            error!(\"pkg: dyn APK signature mismatch: {}\", apk);\n            #[cfg(all(feature = \"check-signature\", not(debug_assertions)))]\n            {\n                return Status::CertMismatch;\n            }\n        }\n\n        self.repackaged_app_id = to_app_id(uid);\n        self.tracked_files\n            .insert(user, TrackedFile::new(apk.to_owned()));\n        Status::Installed\n    }\n\n    fn check_stub(&mut self, user: i32, pkg: &str) -> Status {\n        let Ok(apk) = find_apk_path(pkg) else {\n            return Status::NotInstalled;\n        };\n\n        let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n            Ok(mut fd) => read_certificate(&mut fd, -1),\n            Err(_) => return Status::NotInstalled,\n        };\n\n        if cert.is_empty() || (pkg == self.repackaged_pkg && cert != self.repackaged_cert) {\n            error!(\"pkg: repackaged APK signature invalid: {}\", apk);\n            uninstall_pkg(&apk);\n            return Status::CertMismatch;\n        }\n\n        self.repackaged_pkg.clear();\n        self.repackaged_pkg.push_str(pkg);\n        self.repackaged_cert = cert;\n        self.tracked_files.insert(user, TrackedFile::new(apk));\n        Status::Installed\n    }\n\n    fn check_orig(&mut self, user: i32) -> Status {\n        let Ok(apk) = find_apk_path(APP_PACKAGE_NAME) else {\n            return Status::NotInstalled;\n        };\n\n        let cert = match apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n            Ok(mut fd) => read_certificate(&mut fd, MAGISK_VER_CODE),\n            Err(_) => return Status::NotInstalled,\n        };\n\n        if cert.is_empty() || cert != self.trusted_cert {\n            error!(\"pkg: APK signature mismatch: {}\", apk);\n            #[cfg(all(feature = \"check-signature\", not(debug_assertions)))]\n            {\n                uninstall_pkg(cstr!(APP_PACKAGE_NAME));\n                return Status::CertMismatch;\n            }\n        }\n\n        self.tracked_files.insert(user, TrackedFile::new(apk));\n        Status::Installed\n    }\n\n    fn install_stub(&mut self) {\n        if let Some(ref mut stub_fd) = self.stub_apk_fd {\n            // Copy the stub APK\n            let tmp_apk = cstr!(\"/data/stub.apk\");\n            let result = || -> LoggedResult<()> {\n                {\n                    let mut tmp_apk_file = tmp_apk.create(\n                        OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_TRUNC | OFlag::O_CLOEXEC,\n                        0o600,\n                    )?;\n                    io::copy(stub_fd, &mut tmp_apk_file)?;\n                }\n                // Seek the fd back to start\n                stub_fd.seek(SeekFrom::Start(0))?;\n                Ok(())\n            }();\n            if result.is_ok() {\n                install_apk(tmp_apk);\n            }\n        }\n    }\n\n    fn get_manager(&mut self, daemon: &MagiskD, user: i32, mut install: bool) -> (i32, &str) {\n        let db_pkg = daemon.get_db_string(DbEntryKey::SuManager);\n\n        // If database changed, always re-check files\n        if db_pkg != self.repackaged_pkg {\n            self.tracked_files.remove(&user);\n        }\n\n        if let Some(file) = self.tracked_files.get(&user)\n            && file.is_same()\n        {\n            // no APK\n            if &file.path == PACKAGES_XML {\n                if install && !daemon.is_emulator {\n                    self.install_stub();\n                }\n                return (-1, \"\");\n            }\n            // dyn APK is still the same\n            if file.path.starts_with(daemon.app_data_dir().as_str()) {\n                return (\n                    user * AID_USER_OFFSET + self.repackaged_app_id,\n                    &self.repackaged_pkg,\n                );\n            }\n            // stub APK is still the same\n            if !self.repackaged_pkg.is_empty() {\n                return if matches!(\n                    self.check_dyn(daemon, user, self.repackaged_pkg.clone().as_str()),\n                    Status::Installed\n                ) {\n                    (\n                        user * AID_USER_OFFSET + self.repackaged_app_id,\n                        &self.repackaged_pkg,\n                    )\n                } else {\n                    (-1, \"\")\n                };\n            }\n            // orig APK is still the same\n            let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME);\n            return if uid < 0 {\n                (-1, \"\")\n            } else {\n                (uid, APP_PACKAGE_NAME)\n            };\n        }\n\n        if !db_pkg.is_empty() {\n            match self.check_stub(user, &db_pkg) {\n                Status::Installed => {\n                    return if matches!(self.check_dyn(daemon, user, &db_pkg), Status::Installed) {\n                        (\n                            user * AID_USER_OFFSET + self.repackaged_app_id,\n                            &self.repackaged_pkg,\n                        )\n                    } else {\n                        (-1, \"\")\n                    };\n                }\n                Status::NotInstalled => {\n                    daemon.rm_db_string(DbEntryKey::SuManager).ok();\n                }\n                Status::CertMismatch => {\n                    install = true;\n                    daemon.rm_db_string(DbEntryKey::SuManager).ok();\n                }\n            }\n        }\n\n        self.repackaged_pkg.clear();\n        self.repackaged_cert.clear();\n\n        match self.check_orig(user) {\n            Status::Installed => {\n                let uid = daemon.get_package_uid(user, APP_PACKAGE_NAME);\n                return if uid < 0 {\n                    (-1, \"\")\n                } else {\n                    (uid, APP_PACKAGE_NAME)\n                };\n            }\n            Status::CertMismatch => install = true,\n            Status::NotInstalled => {}\n        }\n\n        // If we cannot find any manager, track packages.xml for new package installs\n        self.tracked_files\n            .insert(user, TrackedFile::new(PACKAGES_XML.into()));\n\n        if install && !daemon.is_emulator {\n            self.install_stub();\n        }\n        (-1, \"\")\n    }\n}\n\nimpl MagiskD {\n    fn get_package_uid(&self, user: i32, pkg: &str) -> i32 {\n        let path = cstr::buf::default()\n            .join_path(self.app_data_dir())\n            .join_path_fmt(user)\n            .join_path(pkg);\n        path.get_attr()\n            .map(|attr| attr.st.st_uid as i32)\n            .unwrap_or(-1)\n    }\n\n    pub fn preserve_stub_apk(&self) {\n        let mut info = self.manager_info.lock();\n\n        let apk = cstr::buf::default()\n            .join_path(get_magisk_tmp())\n            .join_path(\"stub.apk\");\n\n        if let Ok(mut fd) = apk.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC) {\n            info.trusted_cert = read_certificate(&mut fd, MAGISK_VER_CODE);\n            // Seek the fd back to start\n            fd.seek(SeekFrom::Start(0)).log_ok();\n            info.stub_apk_fd = Some(fd);\n        }\n\n        apk.remove().log_ok();\n    }\n\n    pub fn get_manager_uid(&self, user: i32) -> i32 {\n        let mut info = self.manager_info.lock();\n        let (uid, _) = info.get_manager(self, user, false);\n        uid\n    }\n\n    pub fn get_manager(&self, user: i32, install: bool) -> (i32, String) {\n        let mut info = self.manager_info.lock();\n        let (uid, pkg) = info.get_manager(self, user, install);\n        (uid, pkg.to_string())\n    }\n\n    pub fn ensure_manager(&self) {\n        let mut info = self.manager_info.lock();\n        let _ = info.get_manager(self, 0, true);\n    }\n\n    // app_id = app_no + AID_APP_START\n    // app_no range: [0, 9999]\n    pub fn get_app_no_list(&self) -> BitSet {\n        let mut list = BitSet::new();\n        let _ = || -> LoggedResult<()> {\n            let mut app_data_dir = Directory::open(self.app_data_dir())?;\n            // For each user\n            loop {\n                let entry = match app_data_dir.read()? {\n                    None => break,\n                    Some(e) => e,\n                };\n                let mut user_dir = match entry.open_as_dir() {\n                    Err(_) => continue,\n                    Ok(dir) => dir,\n                };\n                // For each package\n                loop {\n                    match user_dir.read()? {\n                        None => break,\n                        Some(e) => {\n                            let mut entry_path = cstr::buf::default();\n                            e.resolve_path(&mut entry_path)?;\n                            let attr = entry_path.get_attr()?;\n                            let app_id = to_app_id(attr.st.st_uid as i32);\n                            if (AID_APP_START..=AID_APP_END).contains(&app_id) {\n                                let app_no = app_id - AID_APP_START;\n                                list.insert(app_no as usize);\n                            }\n                        }\n                    }\n                }\n            }\n            Ok(())\n        }();\n        list\n    }\n}\n"
  },
  {
    "path": "native/src/core/resetprop/.gitignore",
    "content": "proto/mod.rs\nproto/persistent_properties.rs\n"
  },
  {
    "path": "native/src/core/resetprop/cli.rs",
    "content": "use super::persist::{\n    persist_delete_prop, persist_get_all_props, persist_get_prop, persist_set_prop,\n};\nuse super::{PropInfo, PropReader, SYS_PROP};\nuse argh::{EarlyExit, FromArgs, MissingRequirements};\nuse base::libc::PROP_VALUE_MAX;\nuse base::{\n    BufReadExt, CmdArgs, EarlyExitExt, LogLevel, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf,\n    Utf8CString, argh, cstr, debug, log_err, set_log_level_state,\n};\nuse nix::fcntl::OFlag;\nuse std::collections::BTreeMap;\nuse std::ffi::c_char;\nuse std::io::BufReader;\n\n#[derive(FromArgs, Default)]\nstruct ResetProp {\n    #[argh(switch, short = 'v')]\n    verbose: bool,\n    #[argh(switch, short = 'w', long = none)]\n    wait_mode: bool,\n    #[argh(switch, short = 'p', long = none)]\n    persist: bool,\n    #[argh(switch, short = 'P', long = none)]\n    persist_only: bool,\n    #[argh(switch, short = 'Z', long = none)]\n    context: bool,\n    #[argh(switch, short = 'n', long = none)]\n    skip_svc: bool,\n    #[argh(option, short = 'f')]\n    file: Option<Utf8CString>,\n    #[argh(option, short = 'd', long = \"delete\")]\n    delete_key: Option<Utf8CString>,\n    #[argh(positional, greedy = true)]\n    args: Vec<Utf8CString>,\n}\n\nfn print_usage(cmd: &str) {\n    eprintln!(\n        r#\"resetprop - System Property Manipulation Tool\n\nUsage: {cmd} [flags] [arguments...]\n\nRead mode arguments:\n   (no arguments)    print all properties\n   NAME              get property of NAME\n\nWrite mode arguments:\n   NAME VALUE        set property NAME as VALUE\n   -f,--file   FILE  load and set properties from FILE\n   -d,--delete NAME  delete property\n\nWait mode arguments (toggled with -w):\n    NAME             wait until property NAME changes\n    NAME OLD_VALUE   if value of property NAME is not OLD_VALUE, get value\n                     or else wait until property NAME changes\n\nGeneral flags:\n   -h,--help         show this message\n   -v,--verbose      print verbose output to stderr\n   -w                switch to wait mode\n\nRead mode flags:\n   -p      also read persistent properties from storage\n   -P      only read persistent properties from storage\n   -Z      get property context instead of value\n\nWrite mode flags:\n   -n      set properties bypassing property_service\n   -p      always write persistent prop changes to storage\n\"#\n    );\n}\n\nimpl ResetProp {\n    fn get(&self, key: &Utf8CStr) -> Option<String> {\n        if self.context {\n            return Some(SYS_PROP.get_context(key).to_string());\n        }\n\n        let mut val = if !self.persist_only {\n            SYS_PROP.find(key).map(|info| {\n                let mut v = String::new();\n                info.read(&mut PropReader::Value(&mut v));\n                debug!(\"resetprop: get prop [{key}]=[{v}]\");\n                v\n            })\n        } else {\n            None\n        };\n\n        if val.is_none() && (self.persist || self.persist_only) && key.starts_with(\"persist.\") {\n            val = persist_get_prop(key).ok();\n        }\n\n        if val.is_none() {\n            debug!(\"resetprop: prop [{key}] does not exist\");\n        }\n\n        val\n    }\n\n    fn print_all(&self) {\n        let mut map: BTreeMap<String, String> = BTreeMap::new();\n        if !self.persist_only {\n            SYS_PROP.for_each(&mut PropReader::List(&mut map));\n        }\n        if self.persist || self.persist_only {\n            persist_get_all_props(&mut PropReader::List(&mut map)).log_ok();\n        }\n        for (mut k, v) in map.into_iter() {\n            if self.context {\n                println!(\n                    \"[{k}]: [{}]\",\n                    SYS_PROP.get_context(Utf8CStr::from_string(&mut k))\n                );\n            } else {\n                println!(\"[{k}]: [{v}]\");\n            }\n        }\n    }\n\n    fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {\n        let mut skip_svc = self.skip_svc;\n        let mut info = SYS_PROP.find_mut(key);\n\n        // Delete existing read-only properties if they are or will be long properties,\n        // which cannot directly go through __system_property_update\n        if key.starts_with(\"ro.\") {\n            skip_svc = true;\n            if let Some(pi) = &info\n                && (pi.is_long() || val.len() >= PROP_VALUE_MAX as usize)\n            {\n                // Skip pruning nodes as we will add it back ASAP\n                SYS_PROP.delete(key, false);\n                info = None;\n            }\n        }\n\n        #[allow(unused_variables)]\n        let msg = if skip_svc {\n            \"direct modification\"\n        } else {\n            \"property_service\"\n        };\n\n        if let Some(pi) = info {\n            if skip_svc {\n                pi.update(val);\n            } else {\n                SYS_PROP.set(key, val);\n            }\n            debug!(\"resetprop: update prop [{key}]=[{val}] by {msg}\");\n        } else {\n            if skip_svc {\n                SYS_PROP.add(key, val);\n            } else {\n                SYS_PROP.set(key, val);\n            }\n            debug!(\"resetprop: create prop [{key}]=[{val}] by {msg}\");\n        }\n\n        // When bypassing property_service, persistent props won't be stored in storage.\n        // Explicitly handle this situation.\n        if skip_svc && self.persist && key.starts_with(\"persist.\") {\n            persist_set_prop(key, val).log_ok();\n        }\n    }\n\n    fn delete(&self, key: &Utf8CStr) -> bool {\n        debug!(\"resetprop: delete prop [{key}]\");\n        let mut ret = false;\n        ret |= SYS_PROP.delete(key, true);\n        if self.persist && key.starts_with(\"persist.\") {\n            ret |= persist_delete_prop(key).is_ok()\n        }\n        ret\n    }\n\n    fn wait(&self) {\n        let key = &self.args[0];\n        let val = self.args.get(1).map(|s| &**s);\n\n        // Find PropInfo\n        let info: &PropInfo;\n        loop {\n            let i = SYS_PROP.find(key);\n            if let Some(i) = i {\n                info = i;\n                break;\n            } else {\n                debug!(\"resetprop: waiting for prop [{key}] to exist\");\n                let mut serial = SYS_PROP.area_serial();\n                SYS_PROP.wait(None, serial, &mut serial);\n            }\n        }\n\n        if let Some(val) = val {\n            let mut curr_val = String::new();\n            let mut serial = 0;\n            loop {\n                let mut r = PropReader::ValueSerial(&mut curr_val, &mut serial);\n                SYS_PROP.read(info, &mut r);\n                if *val != *curr_val {\n                    debug!(\"resetprop: get prop [{key}]=[{curr_val}]\");\n                    break;\n                }\n                debug!(\"resetprop: waiting for prop [{key}]!=[{val}]\");\n                SYS_PROP.wait(Some(info), serial, &mut serial);\n            }\n        }\n    }\n\n    fn load_file(&self, file: &Utf8CStr) -> LoggedResult<()> {\n        let fd = file.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;\n        let mut key = cstr::buf::dynamic(128);\n        let mut val = cstr::buf::dynamic(128);\n        BufReader::new(fd).for_each_prop(|k, v| {\n            key.clear();\n            val.clear();\n            key.push_str(k);\n            val.push_str(v);\n            self.set(&key, &val);\n            true\n        });\n        Ok(())\n    }\n\n    fn run(self) -> LoggedResult<()> {\n        if self.wait_mode {\n            self.wait();\n        } else if let Some(file) = &self.file {\n            self.load_file(file)?;\n        } else if let Some(key) = &self.delete_key {\n            if !self.delete(key) {\n                return log_err!();\n            }\n        } else {\n            match self.args.len() {\n                0 => self.print_all(),\n                1 => {\n                    if let Some(val) = self.get(&self.args[0]) {\n                        println!(\"{val}\");\n                    } else {\n                        return log_err!();\n                    }\n                }\n                2 => self.set(&self.args[0], &self.args[1]),\n                _ => unreachable!(),\n            }\n        }\n        Ok(())\n    }\n}\n\npub fn resetprop_main(argc: i32, argv: *mut *mut c_char) -> i32 {\n    set_log_level_state(LogLevel::Debug, false);\n    let cmds = CmdArgs::new(argc, argv.cast());\n    let cmds = cmds.as_slice();\n\n    let cli = ResetProp::from_args(&[cmds[0]], &cmds[1..])\n        .and_then(|cli| {\n            let mut special_mode = 0;\n            if cli.wait_mode {\n                if cli.args.is_empty() {\n                    let mut missing = MissingRequirements::default();\n                    missing.missing_positional_arg(\"NAME\");\n                    missing.err_on_any()?;\n                }\n                special_mode += 1;\n            }\n            if cli.file.is_some() {\n                special_mode += 1;\n            }\n            if cli.delete_key.is_some() {\n                special_mode += 1;\n            }\n            if special_mode > 1 {\n                return Err(EarlyExit::from(\n                    \"Multiple operation mode detected!\\n\".to_string(),\n                ));\n            }\n            if cli.args.len() > 2 {\n                return Err(EarlyExit::from(format!(\n                    \"Unrecognized argument: {}\\n\",\n                    cli.args[2]\n                )));\n            }\n            Ok(cli)\n        })\n        .on_early_exit(|| print_usage(cmds[0]));\n\n    if cli.verbose {\n        set_log_level_state(LogLevel::Debug, true);\n    }\n\n    if cli.run().is_ok() { 0 } else { 1 }\n}\n\n// Magisk's own helper functions\n\npub fn set_prop(key: &Utf8CStr, val: &Utf8CStr) {\n    let prop = ResetProp {\n        // All Magisk's internal usage should skip property_service\n        skip_svc: true,\n        ..Default::default()\n    };\n    prop.set(key, val);\n}\n\npub fn load_prop_file(file: &Utf8CStr) {\n    let prop = ResetProp {\n        // All Magisk's internal usage should skip property_service\n        skip_svc: true,\n        ..Default::default()\n    };\n    prop.load_file(file).ok();\n}\n\npub fn get_prop(key: &Utf8CStr) -> String {\n    let prop = ResetProp {\n        persist: key.starts_with(\"persist.\"),\n        ..Default::default()\n    };\n    prop.get(key).unwrap_or_default()\n}\n"
  },
  {
    "path": "native/src/core/resetprop/mod.rs",
    "content": "use base::libc::c_char;\nuse base::{Utf8CStr, libc};\npub use cli::{get_prop, load_prop_file, resetprop_main, set_prop};\nuse libc::timespec;\nuse std::collections::BTreeMap;\nuse std::ffi::CStr;\nuse std::ptr;\nuse std::sync::LazyLock;\n\nmod cli;\nmod persist;\nmod proto;\n\nstatic SYS_PROP: LazyLock<SysProp> = LazyLock::new(|| unsafe { get_sys_prop() });\n\n#[repr(C)]\nstruct PropInfo {\n    _private: cxx::private::Opaque,\n}\n\ntype CharPtr = *const c_char;\ntype ReadCallback = unsafe extern \"C\" fn(&mut PropReader, CharPtr, CharPtr, u32);\ntype ForEachCallback = unsafe extern \"C\" fn(&PropInfo, &mut PropReader);\n\nenum PropReader<'a> {\n    Value(&'a mut String),\n    ValueSerial(&'a mut String, &'a mut u32),\n    List(&'a mut BTreeMap<String, String>),\n}\n\nimpl PropReader<'_> {\n    fn put_cstr(&mut self, key: CharPtr, val: CharPtr, serial: u32) {\n        let key = unsafe { CStr::from_ptr(key) };\n        let val = unsafe { CStr::from_ptr(val) };\n        match self {\n            PropReader::Value(v) => {\n                **v = String::from_utf8_lossy(val.to_bytes()).into_owned();\n            }\n            PropReader::ValueSerial(v, s) => {\n                **v = String::from_utf8_lossy(val.to_bytes()).into_owned();\n                **s = serial;\n            }\n            PropReader::List(map) => {\n                map.insert(\n                    String::from_utf8_lossy(key.to_bytes()).into_owned(),\n                    String::from_utf8_lossy(val.to_bytes()).into_owned(),\n                );\n            }\n        }\n    }\n\n    fn put_str(&mut self, key: String, val: String, serial: u32) {\n        match self {\n            PropReader::Value(v) => {\n                **v = val;\n            }\n            PropReader::ValueSerial(v, s) => {\n                **v = val;\n                **s = serial;\n            }\n            PropReader::List(map) => {\n                map.insert(key, val);\n            }\n        }\n    }\n}\n\nunsafe extern \"C\" {\n    // SAFETY: the improper_ctypes warning is about PropReader. We only pass PropReader\n    // to C functions as raw pointers, and all actual usage happens on the Rust side.\n    #[allow(improper_ctypes)]\n    fn get_sys_prop() -> SysProp;\n\n    fn prop_info_is_long(info: &PropInfo) -> bool;\n    #[link_name = \"__system_property_find2\"]\n    fn sys_prop_find(key: CharPtr) -> Option<&'static mut PropInfo>;\n    #[link_name = \"__system_property_update2\"]\n    fn sys_prop_update(info: &mut PropInfo, val: CharPtr, val_len: u32) -> i32;\n    #[link_name = \"__system_property_add2\"]\n    fn sys_prop_add(key: CharPtr, key_len: u32, val: CharPtr, val_len: u32) -> i32;\n    #[link_name = \"__system_property_delete\"]\n    fn sys_prop_delete(key: CharPtr, prune: bool) -> i32;\n    #[link_name = \"__system_property_get_context\"]\n    fn sys_prop_get_context(key: CharPtr) -> CharPtr;\n    #[link_name = \"__system_property_area_serial2\"]\n    fn sys_prop_area_serial() -> u32;\n}\n\n#[repr(C)]\nstruct SysProp {\n    set: unsafe extern \"C\" fn(CharPtr, CharPtr) -> i32,\n    find: unsafe extern \"C\" fn(CharPtr) -> Option<&'static PropInfo>,\n    read_callback: unsafe extern \"C\" fn(&PropInfo, ReadCallback, &mut PropReader) -> i32,\n    foreach: unsafe extern \"C\" fn(ForEachCallback, &mut PropReader) -> i32,\n    wait: unsafe extern \"C\" fn(Option<&PropInfo>, u32, &mut u32, *const timespec) -> i32,\n}\n\n// Safe abstractions over raw C APIs\n\nimpl PropInfo {\n    fn read(&self, reader: &mut PropReader) {\n        SYS_PROP.read(self, reader);\n    }\n\n    fn update(&mut self, val: &Utf8CStr) {\n        SYS_PROP.update(self, val);\n    }\n\n    fn is_long(&self) -> bool {\n        unsafe { prop_info_is_long(self) }\n    }\n}\n\nimpl SysProp {\n    fn read(&self, info: &PropInfo, reader: &mut PropReader) {\n        unsafe extern \"C\" fn read_fn(r: &mut PropReader, key: CharPtr, val: CharPtr, serial: u32) {\n            r.put_cstr(key, val, serial);\n        }\n        unsafe {\n            (self.read_callback)(info, read_fn, reader);\n        }\n    }\n\n    fn find(&self, key: &Utf8CStr) -> Option<&'static PropInfo> {\n        unsafe { (self.find)(key.as_ptr()) }\n    }\n\n    fn find_mut(&self, key: &Utf8CStr) -> Option<&'static mut PropInfo> {\n        unsafe { sys_prop_find(key.as_ptr()) }\n    }\n\n    fn set(&self, key: &Utf8CStr, val: &Utf8CStr) {\n        unsafe {\n            (self.set)(key.as_ptr(), val.as_ptr());\n        }\n    }\n\n    fn add(&self, key: &Utf8CStr, val: &Utf8CStr) {\n        unsafe {\n            sys_prop_add(\n                key.as_ptr(),\n                key.len() as u32,\n                val.as_ptr(),\n                val.len() as u32,\n            );\n        }\n    }\n\n    fn update(&self, info: &mut PropInfo, val: &Utf8CStr) {\n        unsafe {\n            sys_prop_update(info, val.as_ptr(), val.len() as u32);\n        }\n    }\n\n    fn delete(&self, key: &Utf8CStr, prune: bool) -> bool {\n        unsafe { sys_prop_delete(key.as_ptr(), prune) == 0 }\n    }\n\n    fn for_each(&self, reader: &mut PropReader) {\n        unsafe extern \"C\" fn for_each_fn(info: &PropInfo, vals: &mut PropReader) {\n            SYS_PROP.read(info, vals);\n        }\n        unsafe {\n            (self.foreach)(for_each_fn, reader);\n        }\n    }\n\n    fn wait(&self, info: Option<&PropInfo>, old_serial: u32, new_serial: &mut u32) {\n        unsafe {\n            (self.wait)(info, old_serial, new_serial, ptr::null());\n        }\n    }\n\n    fn get_context(&self, key: &Utf8CStr) -> &'static Utf8CStr {\n        unsafe { Utf8CStr::from_ptr_unchecked(sys_prop_get_context(key.as_ptr())) }\n    }\n\n    fn area_serial(&self) -> u32 {\n        unsafe { sys_prop_area_serial() }\n    }\n}\n"
  },
  {
    "path": "native/src/core/resetprop/persist.rs",
    "content": "use nix::fcntl::OFlag;\nuse quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer};\nuse std::fs::File;\nuse std::io::{BufWriter, Read, Write};\nuse std::os::fd::FromRawFd;\n\nuse crate::resetprop::PropReader;\nuse crate::resetprop::proto::persistent_properties::PersistentProperties;\nuse crate::resetprop::proto::persistent_properties::mod_PersistentProperties::PersistentPropertyRecord;\nuse base::const_format::concatcp;\nuse base::libc::mkstemp;\nuse base::{\n    Directory, FsPathBuilder, LibcReturn, LoggedResult, MappedFile, SilentLogExt, Utf8CStr,\n    Utf8CStrBuf, WalkResult, clone_attr, cstr, debug, log_err,\n};\n\nconst PERSIST_PROP_DIR: &str = \"/data/property\";\nconst PERSIST_PROP: &str = concatcp!(PERSIST_PROP_DIR, \"/persistent_properties\");\n\ntrait PropExt {\n    fn find_index(&self, name: &Utf8CStr) -> Result<usize, usize>;\n    fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord>;\n}\n\nimpl PropExt for PersistentProperties {\n    fn find_index(&self, name: &Utf8CStr) -> Result<usize, usize> {\n        self.properties\n            .binary_search_by(|p| p.name.as_deref().cmp(&Some(name.as_str())))\n    }\n\n    fn find(self, name: &Utf8CStr) -> Option<PersistentPropertyRecord> {\n        let idx = self.find_index(name).ok()?;\n        self.properties.into_iter().nth(idx)\n    }\n}\n\nfn check_proto() -> bool {\n    cstr!(PERSIST_PROP).exists()\n}\n\nfn file_get_prop(name: &Utf8CStr) -> LoggedResult<String> {\n    let path = cstr::buf::default()\n        .join_path(PERSIST_PROP_DIR)\n        .join_path(name);\n    let mut file = path.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC).silent()?;\n    debug!(\"resetprop: read prop from [{}]\", path);\n    let mut s = String::new();\n    file.read_to_string(&mut s)?;\n    Ok(s)\n}\n\nfn file_set_prop(name: &Utf8CStr, value: Option<&Utf8CStr>) -> LoggedResult<()> {\n    let path = cstr::buf::default()\n        .join_path(PERSIST_PROP_DIR)\n        .join_path(name);\n    if let Some(value) = value {\n        let mut tmp = cstr::buf::default()\n            .join_path(PERSIST_PROP_DIR)\n            .join_path(\"prop.XXXXXX\");\n        {\n            let mut f = unsafe {\n                mkstemp(tmp.as_mut_ptr())\n                    .into_os_result(\"mkstemp\", None, None)\n                    .map(|fd| File::from_raw_fd(fd))?\n            };\n            f.write_all(value.as_bytes())?;\n        }\n        debug!(\"resetprop: write prop to [{}]\", tmp);\n        tmp.rename_to(&path)?\n    } else {\n        path.remove().silent()?;\n        debug!(\"resetprop: unlink [{}]\", path);\n    }\n    Ok(())\n}\n\nfn proto_read_props() -> LoggedResult<PersistentProperties> {\n    debug!(\"resetprop: decode with protobuf [{}]\", PERSIST_PROP);\n    let m = MappedFile::open(cstr!(PERSIST_PROP))?;\n    let m = m.as_ref();\n    let mut r = BytesReader::from_bytes(m);\n    let mut props = PersistentProperties::from_reader(&mut r, m)?;\n    // Keep the list sorted for binary search\n    props\n        .properties\n        .sort_unstable_by(|a, b| a.name.cmp(&b.name));\n    Ok(props)\n}\n\nfn proto_write_props(props: &PersistentProperties) -> LoggedResult<()> {\n    let mut tmp = cstr::buf::default().join_path(concatcp!(PERSIST_PROP, \".XXXXXX\"));\n    {\n        let f = unsafe {\n            mkstemp(tmp.as_mut_ptr())\n                .into_os_result(\"mkstemp\", None, None)\n                .map(|fd| File::from_raw_fd(fd))?\n        };\n        debug!(\"resetprop: encode with protobuf [{}]\", tmp);\n        props.write_message(&mut Writer::new(BufWriter::new(f)))?;\n    }\n    clone_attr(cstr!(PERSIST_PROP), &tmp)?;\n    tmp.rename_to(cstr!(PERSIST_PROP))?;\n    Ok(())\n}\n\npub(super) fn persist_get_prop(key: &Utf8CStr) -> LoggedResult<String> {\n    if check_proto() {\n        let props = proto_read_props()?;\n        let prop = props.find(key).silent()?;\n        if let PersistentPropertyRecord {\n            name: Some(_),\n            value: Some(v),\n        } = prop\n        {\n            return Ok(v);\n        }\n    } else {\n        let value = file_get_prop(key)?;\n        debug!(\"resetprop: get persist prop [{}]=[{}]\", key, value);\n        return Ok(value);\n    }\n    log_err!()\n}\n\npub(super) fn persist_get_all_props(reader: &mut PropReader) -> LoggedResult<()> {\n    if check_proto() {\n        let props = proto_read_props()?;\n        props.properties.into_iter().for_each(|prop| {\n            if let PersistentPropertyRecord {\n                name: Some(n),\n                value: Some(v),\n            } = prop\n            {\n                reader.put_str(n, v, 0);\n            }\n        });\n    } else {\n        let mut dir = Directory::open(cstr!(PERSIST_PROP_DIR))?;\n        dir.pre_order_walk(|e| {\n            if e.is_file()\n                && let Ok(value) = file_get_prop(e.name())\n            {\n                reader.put_str(e.name().to_string(), value, 0);\n            }\n            // Do not traverse recursively\n            Ok(WalkResult::Skip)\n        })?;\n    }\n    Ok(())\n}\n\npub(super) fn persist_delete_prop(key: &Utf8CStr) -> LoggedResult<()> {\n    if check_proto() {\n        let mut props = proto_read_props()?;\n        let idx = props.find_index(key).silent()?;\n        props.properties.remove(idx);\n        proto_write_props(&props)?;\n    } else {\n        file_set_prop(key, None)?;\n    }\n    Ok(())\n}\n\npub(super) fn persist_set_prop(key: &Utf8CStr, val: &Utf8CStr) -> LoggedResult<()> {\n    if check_proto() {\n        let mut props = proto_read_props()?;\n        match props.find_index(key) {\n            Ok(idx) => props.properties[idx].value = Some(val.to_string()),\n            Err(idx) => props.properties.insert(\n                idx,\n                PersistentPropertyRecord {\n                    name: Some(key.to_string()),\n                    value: Some(val.to_string()),\n                },\n            ),\n        }\n        proto_write_props(&props)?;\n    } else {\n        file_set_prop(key, Some(val))?;\n    }\n    Ok(())\n}\n"
  },
  {
    "path": "native/src/core/resetprop/proto/persistent_properties.proto",
    "content": "/*\n * Copyright (C) 2017 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nsyntax = \"proto2\";\noption optimize_for = LITE_RUNTIME;\nmessage PersistentProperties {\n  message PersistentPropertyRecord {\n    optional string name = 1;\n    optional string value = 2;\n  }\n  repeated PersistentPropertyRecord properties = 1;\n}\n"
  },
  {
    "path": "native/src/core/resetprop/sys.cpp",
    "content": "#include <dlfcn.h>\n\n#include <base.hpp>\n#include <core.hpp>\n\n#include <api/system_properties.h>\n#include <system_properties/prop_info.h>\n\nusing namespace std;\n\n// This has to keep in sync with SysProp in mod.rs\nstruct SysProp {\n    int (*set)(const char*, const char*);\n    const prop_info *(*find)(const char*);\n    void (*read_callback)(const prop_info*, void (*)(void*, const char*, const char*, uint32_t), void*);\n    int (*foreach)(void (*)(const prop_info*, void*), void*);\n    bool (*wait)(const prop_info*, uint32_t, uint32_t*, const timespec*);\n};\n\nextern \"C\" bool prop_info_is_long(const prop_info &info) {\n    return info.is_long();\n}\n\nextern \"C\" SysProp get_sys_prop() {\n    SysProp prop{};\n#ifdef APPLET_STUB_MAIN\n    // Use internal implementation\n    prop.set = __system_property_set;\n    prop.find = __system_property_find;\n    prop.read_callback = __system_property_read_callback;\n    prop.foreach = __system_property_foreach;\n    prop.wait = __system_property_wait;\n#else\n#define DLOAD(name) (*(void **) &prop.name = dlsym(RTLD_DEFAULT, \"__system_property_\" #name))\n    // Dynamic load platform implementation\n    DLOAD(set);\n    DLOAD(find);\n    DLOAD(read_callback);\n    DLOAD(foreach);\n    DLOAD(wait);\n#undef DLOAD\n    if (prop.wait == nullptr) {\n        // This platform API only exist on API 26+\n        prop.wait = __system_property_wait;\n    }\n    if (prop.read_callback == nullptr) {\n        // This platform API only exist on API 26+\n        prop.read_callback = __system_property_read_callback;\n    }\n#endif\n    if (__system_properties_init()) {\n        LOGE(\"resetprop: __system_properties_init error\\n\");\n    }\n    return prop;\n}\n"
  },
  {
    "path": "native/src/core/scripting.cpp",
    "content": "#include <string>\n#include <vector>\n#include <sys/wait.h>\n\n#include <consts.hpp>\n#include <base.hpp>\n#include <core.hpp>\n\nusing namespace std;\n\n#define BBEXEC_CMD bbpath(), \"sh\"\n\nstatic const char *bbpath() {\n    static string path;\n    path = get_magisk_tmp();\n    path += \"/\" BBPATH \"/busybox\";\n    if (access(path.data(), X_OK) != 0) {\n        path = DATABIN \"/busybox\";\n    }\n    return path.data();\n}\n\nstatic void set_script_env() {\n    setenv(\"ASH_STANDALONE\", \"1\", 1);\n    char new_path[4096];\n    ssprintf(new_path, sizeof(new_path), \"%s:%s\", getenv(\"PATH\"), get_magisk_tmp());\n    setenv(\"PATH\", new_path, 1);\n    if (MagiskD::Get().zygisk_enabled())\n        setenv(\"ZYGISK_ENABLED\", \"1\", 1);\n};\n\nvoid exec_script(Utf8CStr script) {\n    exec_t exec {\n        .pre_exec = set_script_env,\n        .fork = fork_no_orphan\n    };\n    exec_command_sync(exec, BBEXEC_CMD, script.c_str());\n}\n\nstatic timespec pfs_timeout;\n\n#define PFS_SETUP() \\\nif (pfs) { \\\n    if (int pid = xfork()) { \\\n        if (pid < 0) \\\n            return; \\\n        /* In parent process, simply wait for child to finish */ \\\n        waitpid(pid, nullptr, 0); \\\n        return; \\\n    } \\\n    timer_pid = xfork(); \\\n    if (timer_pid == 0) { \\\n        /* In timer process, count down */ \\\n        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &pfs_timeout, nullptr); \\\n        exit(0); \\\n    } \\\n}\n\n#define PFS_WAIT() \\\nif (pfs) { \\\n    /* If we ran out of time, don't block */ \\\n    if (timer_pid < 0) \\\n        continue; \\\n    if (int pid = waitpid(-1, nullptr, 0); pid == timer_pid) { \\\n        LOGW(\"* post-fs-data scripts blocking phase timeout\\n\"); \\\n        timer_pid = -1; \\\n    } \\\n}\n\n#define PFS_DONE() \\\nif (pfs) { \\\n    if (timer_pid > 0) \\\n        kill(timer_pid, SIGKILL); \\\n    exit(0); \\\n}\n\nvoid exec_common_scripts(Utf8CStr stage) {\n    LOGI(\"* Running %s.d scripts\\n\", stage.c_str());\n    char path[4096];\n    char *name = path + sprintf(path, SECURE_DIR \"/%s.d\", stage.c_str());\n    auto dir = xopen_dir(path);\n    if (!dir) return;\n\n    bool pfs = stage == \"post-fs-data\"sv;\n    int timer_pid = -1;\n    if (pfs) {\n        // Setup timer\n        clock_gettime(CLOCK_MONOTONIC, &pfs_timeout);\n        pfs_timeout.tv_sec += POST_FS_DATA_SCRIPT_MAX_TIME;\n    }\n    PFS_SETUP()\n\n    *(name++) = '/';\n    int dfd = dirfd(dir.get());\n    for (dirent *entry; (entry = xreaddir(dir.get()));) {\n        if (entry->d_type == DT_REG) {\n            if (faccessat(dfd, entry->d_name, X_OK, 0) != 0)\n                continue;\n            LOGI(\"%s.d: exec [%s]\\n\", stage.c_str(), entry->d_name);\n            strcpy(name, entry->d_name);\n            exec_t exec {\n                .pre_exec = set_script_env,\n                .fork = pfs ? xfork : fork_dont_care\n            };\n            exec_command(exec, BBEXEC_CMD, path);\n            PFS_WAIT()\n        }\n    }\n\n    PFS_DONE()\n}\n\nstatic bool operator>(const timespec &a, const timespec &b) {\n    if (a.tv_sec != b.tv_sec)\n        return a.tv_sec > b.tv_sec;\n    return a.tv_nsec > b.tv_nsec;\n}\n\nvoid exec_module_scripts(Utf8CStr stage, const rust::Vec<ModuleInfo> &module_list) {\n    LOGI(\"* Running module %s scripts\\n\", stage.c_str());\n    if (module_list.empty())\n        return;\n\n    bool pfs = stage == \"post-fs-data\";\n    if (pfs) {\n        timespec now{};\n        clock_gettime(CLOCK_MONOTONIC, &now);\n        // If we had already timed out, treat it as service mode\n        if (now > pfs_timeout)\n            pfs = false;\n    }\n    int timer_pid = -1;\n    PFS_SETUP()\n\n    char path[4096];\n    for (auto &m : module_list) {\n        sprintf(path, MODULEROOT \"/%.*s/%s.sh\", (int) m.name.size(), m.name.data(), stage.c_str());\n        if (access(path, F_OK) == -1)\n            continue;\n        LOGI(\"%.*s: exec [%s.sh]\\n\", (int) m.name.size(), m.name.data(), stage.c_str());\n        exec_t exec {\n            .pre_exec = set_script_env,\n            .fork = pfs ? xfork : fork_dont_care\n        };\n        exec_command(exec, BBEXEC_CMD, path);\n        PFS_WAIT()\n    }\n\n    PFS_DONE()\n}\n\nconstexpr char install_script[] = R\"EOF(\nAPK=%s\nlog -t Magisk \"pm_install: $APK\"\nlog -t Magisk \"pm_install: $(pm install -g -r $APK 2>&1)\"\nappops set %s REQUEST_INSTALL_PACKAGES allow\nrm -f $APK\n)EOF\";\n\nvoid install_apk(Utf8CStr apk) {\n    setfilecon(apk.c_str(), MAGISK_FILE_CON);\n    char cmds[sizeof(install_script) + 4096];\n    ssprintf(cmds, sizeof(cmds), install_script, apk.c_str(), JAVA_PACKAGE_NAME);\n    exec_command_async(\"/system/bin/sh\", \"-c\", cmds);\n}\n\nconstexpr char uninstall_script[] = R\"EOF(\nPKG=%s\nlog -t Magisk \"pm_uninstall: $PKG\"\nlog -t Magisk \"pm_uninstall: $(pm uninstall $PKG 2>&1)\"\n)EOF\";\n\nvoid uninstall_pkg(Utf8CStr pkg) {\n    char cmds[sizeof(uninstall_script) + 256];\n    ssprintf(cmds, sizeof(cmds), uninstall_script, pkg.c_str());\n    exec_command_async(\"/system/bin/sh\", \"-c\", cmds);\n}\n\nconstexpr char clear_script[] = R\"EOF(\nPKG=%s\nUSER=%d\nlog -t Magisk \"pm_clear: $PKG (user=$USER)\"\nlog -t Magisk \"pm_clear: $(pm clear --user $USER $PKG 2>&1)\"\n)EOF\";\n\nvoid clear_pkg(const char *pkg, int user_id) {\n    char cmds[sizeof(clear_script) + 288];\n    ssprintf(cmds, sizeof(cmds), clear_script, pkg, user_id);\n    exec_command_async(\"/system/bin/sh\", \"-c\", cmds);\n}\n\n[[noreturn]] __printflike(2, 3)\nstatic void abort(FILE *fp, const char *fmt, ...) {\n    va_list valist;\n    va_start(valist, fmt);\n    vfprintf(fp, fmt, valist);\n    fprintf(fp, \"\\n\\n\");\n    va_end(valist);\n    exit(1);\n}\n\nconstexpr char install_module_script[] = R\"EOF(\n. /data/adb/magisk/util_functions.sh\ninstall_module\nexit 0\n)EOF\";\n\nvoid install_module(Utf8CStr file) {\n    if (getuid() != 0)\n        abort(stderr, \"Run this command with root\");\n    if (access(DATABIN, F_OK) ||\n        access(bbpath(), X_OK) ||\n        access(DATABIN \"/util_functions.sh\", F_OK))\n        abort(stderr, \"Incomplete Magisk install\");\n    if (access(file.c_str(), F_OK))\n        abort(stderr, \"'%s' does not exist\", file.c_str());\n\n    char *zip = realpath(file.c_str(), nullptr);\n    setenv(\"OUTFD\", \"1\", 1);\n    setenv(\"ZIPFILE\", zip, 1);\n    setenv(\"ASH_STANDALONE\", \"1\", 1);\n    setenv(\"MAGISKTMP\", get_magisk_tmp(), 0);\n    free(zip);\n\n    int fd = xopen(\"/dev/null\", O_RDONLY);\n    xdup2(fd, STDERR_FILENO);\n    close(fd);\n\n    const char *argv[] = { BBEXEC_CMD, \"-c\", install_module_script, nullptr };\n    execve(argv[0], (char **) argv, environ);\n    abort(stdout, \"Failed to execute BusyBox shell\");\n}\n"
  },
  {
    "path": "native/src/core/selinux.rs",
    "content": "use crate::consts::{DATABIN, LOG_PIPE, MAGISK_LOG_CON, MAGISKDB, MODULEROOT, SECURE_DIR};\nuse crate::ffi::get_magisk_tmp;\nuse base::{Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CStrBuf, cstr, libc};\nuse nix::fcntl::OFlag;\nuse std::io::Write;\n\nconst UNLABEL_CON: &Utf8CStr = cstr!(\"u:object_r:unlabeled:s0\");\nconst SYSTEM_CON: &Utf8CStr = cstr!(\"u:object_r:system_file:s0\");\nconst ADB_CON: &Utf8CStr = cstr!(\"u:object_r:adb_data_file:s0\");\nconst ROOT_CON: &Utf8CStr = cstr!(\"u:object_r:rootfs:s0\");\n\nfn restore_syscon_from_unlabeled(\n    path: &mut dyn Utf8CStrBuf,\n    con: &mut dyn Utf8CStrBuf,\n) -> LoggedResult<()> {\n    let dir_path_len = path.len();\n    if path.get_secontext(con).log().is_ok() && con.as_str() == UNLABEL_CON {\n        path.set_secontext(SYSTEM_CON)?;\n    }\n    let mut dir = Directory::open(path)?;\n    while let Some(ref e) = dir.read()? {\n        path.truncate(dir_path_len);\n        path.append_path(e.name());\n        if e.is_dir() {\n            restore_syscon_from_unlabeled(path, con)?;\n        } else if (e.is_file() || e.is_symlink())\n            && path.get_secontext(con).log().is_ok()\n            && con.as_str() == UNLABEL_CON\n        {\n            path.set_secontext(SYSTEM_CON)?;\n        }\n    }\n    Ok(())\n}\n\nfn restore_syscon(path: &mut dyn Utf8CStrBuf) -> LoggedResult<()> {\n    let dir_path_len = path.len();\n    path.set_secontext(SYSTEM_CON)?;\n    unsafe { libc::lchown(path.as_ptr(), 0, 0) };\n    let mut dir = Directory::open(path)?;\n    while let Some(ref e) = dir.read()? {\n        path.truncate(dir_path_len);\n        path.append_path(e.name());\n        if e.is_dir() {\n            restore_syscon(path)?;\n        } else if e.is_file() || e.is_symlink() {\n            path.set_secontext(SYSTEM_CON)?;\n            unsafe { libc::lchown(path.as_ptr(), 0, 0) };\n        }\n    }\n    Ok(())\n}\n\npub(crate) fn restorecon() {\n    if let Ok(mut file) = cstr!(\"/sys/fs/selinux/context\")\n        .open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)\n        .log()\n        && file.write_all(ADB_CON.as_bytes_with_nul()).is_ok()\n    {\n        cstr!(SECURE_DIR).set_secontext(ADB_CON).log_ok();\n    }\n\n    let mut path = cstr::buf::default();\n    let mut con = cstr::buf::new::<1024>();\n    path.push_str(MODULEROOT);\n    path.set_secontext(SYSTEM_CON).log_ok();\n    restore_syscon_from_unlabeled(&mut path, &mut con).log_ok();\n\n    path.clear();\n    path.push_str(DATABIN);\n    restore_syscon(&mut path).log_ok();\n    unsafe { libc::chmod(cstr!(MAGISKDB).as_ptr(), 0o000) };\n}\n\npub(crate) fn restore_tmpcon() -> LoggedResult<()> {\n    let tmp = get_magisk_tmp();\n    if tmp == \"/sbin\" {\n        tmp.set_secontext(ROOT_CON)?;\n    } else {\n        unsafe { libc::chmod(tmp.as_ptr(), 0o711) };\n    }\n\n    let mut path = cstr::buf::default();\n    let mut dir = Directory::open(tmp)?;\n    while let Some(ref e) = dir.read()? {\n        if !e.is_symlink() {\n            e.resolve_path(&mut path)?;\n            path.set_secontext(SYSTEM_CON).log_ok();\n        }\n    }\n\n    path.clear();\n    path.append_path(tmp).append_path(LOG_PIPE);\n    path.set_secontext(cstr!(MAGISK_LOG_CON))?;\n\n    Ok(())\n}\n\npub(crate) fn lgetfilecon(path: &Utf8CStr, con: &mut [u8]) -> bool {\n    let mut con = cstr::buf::wrap(con);\n    path.get_secontext(&mut con).is_ok()\n}\n\npub(crate) fn setfilecon(path: &Utf8CStr, con: &Utf8CStr) -> bool {\n    path.follow_link().set_secontext(con).is_ok()\n}\n"
  },
  {
    "path": "native/src/core/socket.rs",
    "content": "use base::{ReadExt, ResultExt, WriteExt, libc, warn};\nuse bytemuck::{Zeroable, bytes_of, bytes_of_mut};\nuse std::io;\nuse std::io::{ErrorKind, IoSlice, IoSliceMut, Read, Write};\nuse std::mem::ManuallyDrop;\nuse std::os::fd::{FromRawFd, IntoRawFd, OwnedFd, RawFd};\nuse std::os::unix::net::{AncillaryData, SocketAncillary, UnixStream};\n\npub trait Encodable {\n    fn encode(&self, w: &mut impl Write) -> io::Result<()>;\n}\n\npub trait Decodable: Sized + Encodable {\n    fn decode(r: &mut impl Read) -> io::Result<Self>;\n}\n\nmacro_rules! impl_pod_encodable {\n    ($($t:ty)*) => ($(\n        impl Encodable for $t {\n            #[inline(always)]\n            fn encode(&self, w: &mut impl Write) -> io::Result<()> {\n                w.write_pod(self)\n            }\n        }\n        impl Decodable for $t {\n            #[inline(always)]\n            fn decode(r: &mut impl Read) -> io::Result<Self> {\n                let mut val = Self::zeroed();\n                r.read_pod(&mut val)?;\n                Ok(val)\n            }\n        }\n    )*)\n}\n\nimpl_pod_encodable! { u8 u32 i32 usize }\n\nimpl Encodable for bool {\n    #[inline(always)]\n    fn encode(&self, w: &mut impl Write) -> io::Result<()> {\n        match *self {\n            true => 1u8.encode(w),\n            false => 0u8.encode(w),\n        }\n    }\n}\n\nimpl Decodable for bool {\n    #[inline(always)]\n    fn decode(r: &mut impl Read) -> io::Result<Self> {\n        Ok(u8::decode(r)? != 0)\n    }\n}\n\n// impl<E: Encodable, T: AsRef<E>> Encodable for T\nmacro_rules! impl_encodable_as_ref {\n    ($( ($t:ty, $e:ty, $($g:tt)*) )*) => ($(\n        impl<$($g)*> Encodable for $t {\n            #[inline(always)]\n            fn encode(&self, w: &mut impl Write) -> io::Result<()> {\n                AsRef::<$e>::as_ref(self).encode(w)\n            }\n        }\n    )*)\n}\n\nimpl_encodable_as_ref! {\n    (String, str,)\n    (Vec<T>, [T], T: Encodable)\n}\n\nimpl<T: Encodable> Encodable for [T] {\n    fn encode(&self, w: &mut impl Write) -> io::Result<()> {\n        (self.len() as i32).encode(w)?;\n        self.iter().try_for_each(|e| e.encode(w))\n    }\n}\n\nimpl<T: Decodable> Decodable for Vec<T> {\n    fn decode(r: &mut impl Read) -> io::Result<Self> {\n        let len = i32::decode(r)?;\n        let mut val = Vec::with_capacity(len as usize);\n        for _ in 0..len {\n            val.push(T::decode(r)?);\n        }\n        Ok(val)\n    }\n}\n\nimpl Encodable for str {\n    fn encode(&self, w: &mut impl Write) -> io::Result<()> {\n        (self.len() as i32).encode(w)?;\n        w.write_all(self.as_bytes())\n    }\n}\n\nimpl Decodable for String {\n    fn decode(r: &mut impl Read) -> io::Result<String> {\n        let len = i32::decode(r)?;\n        let mut val = String::with_capacity(len as usize);\n        r.take(len as u64).read_to_string(&mut val)?;\n        Ok(val)\n    }\n}\n\npub trait IpcRead {\n    fn read_decodable<E: Decodable>(&mut self) -> io::Result<E>;\n}\n\nimpl<T: Read> IpcRead for T {\n    #[inline(always)]\n    fn read_decodable<E: Decodable>(&mut self) -> io::Result<E> {\n        E::decode(self)\n    }\n}\n\npub trait IpcWrite {\n    fn write_encodable<E: Encodable + ?Sized>(&mut self, val: &E) -> io::Result<()>;\n}\n\nimpl<T: Write> IpcWrite for T {\n    #[inline(always)]\n    fn write_encodable<E: Encodable + ?Sized>(&mut self, val: &E) -> io::Result<()> {\n        val.encode(self)\n    }\n}\n\npub trait UnixSocketExt {\n    fn send_fds(&mut self, fd: &[RawFd]) -> io::Result<()>;\n    fn recv_fd(&mut self) -> io::Result<Option<OwnedFd>>;\n    fn recv_fds(&mut self) -> io::Result<Vec<OwnedFd>>;\n}\n\nimpl UnixSocketExt for UnixStream {\n    fn send_fds(&mut self, fds: &[RawFd]) -> io::Result<()> {\n        match fds.len() {\n            0 => self.write_pod(&0)?,\n            len => {\n                // 4k buffer is reasonable enough\n                let mut buf = [0u8; 4096];\n                let mut ancillary = SocketAncillary::new(&mut buf);\n                if !ancillary.add_fds(fds) {\n                    return Err(ErrorKind::OutOfMemory.into());\n                }\n                let fd_count = len as i32;\n                let iov = IoSlice::new(bytes_of(&fd_count));\n                self.send_vectored_with_ancillary(&[iov], &mut ancillary)?;\n            }\n        };\n        Ok(())\n    }\n\n    fn recv_fd(&mut self) -> io::Result<Option<OwnedFd>> {\n        let mut fd_count = 0;\n        self.peek(bytes_of_mut(&mut fd_count))?;\n        if fd_count < 1 {\n            // Actually consume the data\n            self.read_pod(&mut fd_count)?;\n            return Ok(None);\n        }\n        if fd_count > 1 {\n            warn!(\n                \"Received unexpected number of fds: expected=1 actual={}\",\n                fd_count\n            );\n        }\n\n        // 4k buffer is reasonable enough\n        let mut buf = [0u8; 4096];\n        let mut ancillary = SocketAncillary::new(&mut buf);\n        let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count));\n        self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?;\n        for msg in ancillary.messages().flatten() {\n            if let AncillaryData::ScmRights(mut scm_rights) = msg {\n                // We only want the first one\n                let fd = if let Some(fd) = scm_rights.next() {\n                    unsafe { OwnedFd::from_raw_fd(fd) }\n                } else {\n                    return Ok(None);\n                };\n                // Close all others\n                for fd in scm_rights {\n                    unsafe { libc::close(fd) };\n                }\n                return Ok(Some(fd));\n            }\n        }\n        Ok(None)\n    }\n\n    fn recv_fds(&mut self) -> io::Result<Vec<OwnedFd>> {\n        let mut fd_count = 0;\n        // 4k buffer is reasonable enough\n        let mut buf = [0u8; 4096];\n        let mut ancillary = SocketAncillary::new(&mut buf);\n        let iov = IoSliceMut::new(bytes_of_mut(&mut fd_count));\n        self.recv_vectored_with_ancillary(&mut [iov], &mut ancillary)?;\n        let mut fds: Vec<OwnedFd> = Vec::new();\n        for msg in ancillary.messages().flatten() {\n            if let AncillaryData::ScmRights(scm_rights) = msg {\n                fds = scm_rights\n                    .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })\n                    .collect();\n            }\n        }\n        if fd_count as usize != fds.len() {\n            warn!(\n                \"Received unexpected number of fds: expected={} actual={}\",\n                fd_count,\n                fds.len()\n            );\n        }\n        Ok(fds)\n    }\n}\n\npub fn send_fd(socket: RawFd, fd: RawFd) -> bool {\n    let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });\n    if fd < 0 {\n        socket.send_fds(&[]).log().is_ok()\n    } else {\n        socket.send_fds(&[fd]).log().is_ok()\n    }\n}\n\npub fn recv_fd(socket: RawFd) -> RawFd {\n    let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });\n    socket\n        .recv_fd()\n        .log()\n        .unwrap_or(None)\n        .map_or(-1, IntoRawFd::into_raw_fd)\n}\n\npub fn recv_fds(socket: RawFd) -> Vec<RawFd> {\n    let mut socket = ManuallyDrop::new(unsafe { UnixStream::from_raw_fd(socket) });\n    let fds = socket.recv_fds().log().unwrap_or(Vec::new());\n    // SAFETY: OwnedFd and RawFd has the same layout\n    unsafe { std::mem::transmute(fds) }\n}\n"
  },
  {
    "path": "native/src/core/sqlite.cpp",
    "content": "#include <dlfcn.h>\n\n#include <consts.hpp>\n#include <base.hpp>\n#include <sqlite.hpp>\n\nusing namespace std;\n\n#define DB_VERSION     12\n#define DB_VERSION_STR \"12\"\n\n// SQLite APIs\n\nstatic int (*sqlite3_open_v2)(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs);\nstatic int (*sqlite3_close)(sqlite3 *db);\nconst char *(*sqlite3_errstr)(int);\nstatic int (*sqlite3_prepare_v2)(sqlite3 *db, const char *zSql, int nByte, sqlite3_stmt **ppStmt, const char **pzTail);\nstatic int (*sqlite3_bind_parameter_count)(sqlite3_stmt*);\nstatic int (*sqlite3_bind_int64)(sqlite3_stmt*, int, int64_t);\nstatic int (*sqlite3_bind_text)(sqlite3_stmt*,int,const char*,int,void(*)(void*));\nstatic int (*sqlite3_column_count)(sqlite3_stmt *pStmt);\nstatic const char *(*sqlite3_column_name)(sqlite3_stmt*, int N);\nstatic const char *(*sqlite3_column_text)(sqlite3_stmt*, int iCol);\nstatic int (*sqlite3_column_int)(sqlite3_stmt*, int iCol);\nstatic int (*sqlite3_step)(sqlite3_stmt*);\nstatic int (*sqlite3_finalize)(sqlite3_stmt *pStmt);\n\n// Internal Android linker APIs\n\nstatic void (*android_get_LD_LIBRARY_PATH)(char *buffer, size_t buffer_size);\nstatic void (*android_update_LD_LIBRARY_PATH)(const char *ld_library_path);\n\n#define DLERR(ptr) if (!(ptr)) { \\\n    LOGE(\"db: %s\\n\", dlerror()); \\\n    return false; \\\n}\n\n#define DLOAD(handle, arg) {\\\n    auto f = dlsym(handle, #arg); \\\n    DLERR(f) \\\n    *(void **) &(arg) = f; \\\n}\n\n#ifdef __LP64__\nconstexpr char apex_path[] = \"/apex/com.android.runtime/lib64:/apex/com.android.art/lib64:/apex/com.android.i18n/lib64:\";\n#else\nconstexpr char apex_path[] = \"/apex/com.android.runtime/lib:/apex/com.android.art/lib:/apex/com.android.i18n/lib:\";\n#endif\n\nstatic bool load_sqlite() {\n    static int dl_init = 0;\n    if (dl_init)\n        return dl_init > 0;\n    dl_init = -1;\n\n    auto sqlite = dlopen(\"libsqlite.so\", RTLD_LAZY);\n    if (!sqlite) {\n        // Should only happen on Android 10+\n        auto dl = dlopen(\"libdl_android.so\", RTLD_LAZY);\n        DLERR(dl);\n\n        DLOAD(dl, android_get_LD_LIBRARY_PATH);\n        DLOAD(dl, android_update_LD_LIBRARY_PATH);\n\n        // Inject APEX into LD_LIBRARY_PATH\n        char ld_path[4096];\n        memcpy(ld_path, apex_path, sizeof(apex_path));\n        constexpr int len = sizeof(apex_path) - 1;\n        android_get_LD_LIBRARY_PATH(ld_path + len, sizeof(ld_path) - len);\n        android_update_LD_LIBRARY_PATH(ld_path);\n        sqlite = dlopen(\"libsqlite.so\", RTLD_LAZY);\n\n        // Revert LD_LIBRARY_PATH just in case\n        android_update_LD_LIBRARY_PATH(ld_path + len);\n    }\n    DLERR(sqlite);\n\n    DLOAD(sqlite, sqlite3_open_v2);\n    DLOAD(sqlite, sqlite3_close);\n    DLOAD(sqlite, sqlite3_errstr);\n    DLOAD(sqlite, sqlite3_prepare_v2);\n    DLOAD(sqlite, sqlite3_bind_parameter_count);\n    DLOAD(sqlite, sqlite3_bind_int64);\n    DLOAD(sqlite, sqlite3_bind_text);\n    DLOAD(sqlite, sqlite3_step);\n    DLOAD(sqlite, sqlite3_column_count);\n    DLOAD(sqlite, sqlite3_column_name);\n    DLOAD(sqlite, sqlite3_column_text);\n    DLOAD(sqlite, sqlite3_column_int);\n    DLOAD(sqlite, sqlite3_finalize);\n\n    dl_init = 1;\n    return true;\n}\n\nusing StringVec = rust::Vec<rust::String>;\nusing sql_bind_callback_real = int(*)(void*, int, sqlite3_stmt*);\nusing sql_exec_callback_real = void(*)(void*, StringSlice, sqlite3_stmt*);\n\n#define sql_chk(fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) return rc\n\n// Exports to Rust\nextern \"C\" int sql_exec_impl(\n        sqlite3 *db, rust::Str zSql,\n        sql_bind_callback bind_cb = nullptr, void *bind_cookie = nullptr,\n        sql_exec_callback exec_cb = nullptr, void *exec_cookie = nullptr) {\n    const char *sql = zSql.begin();\n    unique_ptr<sqlite3_stmt, decltype(sqlite3_finalize)> stmt(nullptr, sqlite3_finalize);\n\n    while (sql != zSql.end()) {\n        // Step 1: prepare statement\n        {\n            sqlite3_stmt *st = nullptr;\n            sql_chk(sqlite3_prepare_v2, db, sql, zSql.end() - sql, &st, &sql);\n            if (st == nullptr) continue;\n            stmt.reset(st);\n        }\n\n        // Step 2: bind arguments\n        if (bind_cb) {\n            if (int count = sqlite3_bind_parameter_count(stmt.get())) {\n                auto real_cb = reinterpret_cast<sql_bind_callback_real>(bind_cb);\n                for (int i = 1; i <= count; ++i) {\n                    sql_chk(real_cb, bind_cookie, i, stmt.get());\n                }\n            }\n        }\n\n        // Step 3: execute\n        bool first = true;\n        StringVec columns;\n        for (;;) {\n            int rc = sqlite3_step(stmt.get());\n            if (rc == SQLITE_DONE) break;\n            if (rc != SQLITE_ROW) return rc;\n            if (exec_cb == nullptr) continue;\n            if (first) {\n                int count = sqlite3_column_count(stmt.get());\n                for (int i = 0; i < count; ++i) {\n                    columns.emplace_back(sqlite3_column_name(stmt.get(), i));\n                }\n                first = false;\n            }\n            auto real_cb = reinterpret_cast<sql_exec_callback_real>(exec_cb);\n            real_cb(exec_cookie, StringSlice(columns), stmt.get());\n        }\n    }\n\n    return SQLITE_OK;\n}\n\nint DbValues::get_int(int index) const {\n    return sqlite3_column_int((sqlite3_stmt*) this, index);\n}\n\nconst char *DbValues::get_text(int index) const {\n    return sqlite3_column_text((sqlite3_stmt*) this, index);\n}\n\nint DbStatement::bind_int64(int index, int64_t val) {\n    return sqlite3_bind_int64(reinterpret_cast<sqlite3_stmt*>(this), index, val);\n}\n\nint DbStatement::bind_text(int index, rust::Str val) {\n    return sqlite3_bind_text(reinterpret_cast<sqlite3_stmt*>(this), index, val.data(), val.size(), nullptr);\n}\n\n#define sql_chk_log_ret(ret, fn, ...) if (int rc = fn(__VA_ARGS__); rc != SQLITE_OK) { \\\n    LOGE(\"sqlite3(line:%d): %s\\n\", __LINE__, sqlite3_errstr(rc));                      \\\n    return ret;                                                                        \\\n}\n\n#define sql_chk_log(fn, ...) sql_chk_log_ret(nullptr, fn, __VA_ARGS__)\n\nsqlite3 *open_and_init_db() {\n    if (!load_sqlite()) {\n        LOGE(\"sqlite3: Cannot load libsqlite.so\\n\");\n        return nullptr;\n    }\n\n    unique_ptr<sqlite3, decltype(sqlite3_close)> db(nullptr, sqlite3_close);\n    {\n        sqlite3 *sql;\n        // We open the connection with SQLITE_OPEN_NOMUTEX because we are guarding it ourselves\n        sql_chk_log(sqlite3_open_v2, MAGISKDB, &sql,\n                    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr);\n        db.reset(sql);\n    }\n\n    int ver = 0;\n    bool upgrade = false;\n    auto ver_cb = [](void *ver, auto, const DbValues &values) {\n        *static_cast<int *>(ver) = values.get_int(0);\n    };\n    sql_chk_log(sql_exec_impl, db.get(), \"PRAGMA user_version\", nullptr, nullptr, ver_cb, &ver);\n    if (ver > DB_VERSION) {\n        // Don't support downgrading database, delete and retry\n        LOGE(\"sqlite3: Downgrading database is not supported\\n\");\n        unlink(MAGISKDB);\n        return open_and_init_db();\n    }\n\n    auto create_policy = [&] {\n        return sql_exec_impl(db.get(),\n                \"CREATE TABLE IF NOT EXISTS policies \"\n                \"(uid INT, policy INT, until INT, logging INT, \"\n                \"notification INT, PRIMARY KEY(uid))\");\n    };\n    auto create_settings = [&] {\n        return sql_exec_impl(db.get(),\n                \"CREATE TABLE IF NOT EXISTS settings \"\n                \"(key TEXT, value INT, PRIMARY KEY(key))\");\n    };\n    auto create_strings = [&] {\n        return sql_exec_impl(db.get(),\n                \"CREATE TABLE IF NOT EXISTS strings \"\n                \"(key TEXT, value TEXT, PRIMARY KEY(key))\");\n    };\n    auto create_denylist = [&] {\n        return sql_exec_impl(db.get(),\n                \"CREATE TABLE IF NOT EXISTS denylist \"\n                \"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process))\");\n    };\n\n    // Database changelog:\n    //\n    // 0 - 6: DB stored in app private data. There are no longer any code in the project to\n    //        migrate these data, so no need to take any of these versions into consideration.\n    // 7 : create table `hidelist` (process TEXT, PRIMARY KEY(process))\n    // 8 : add new column (package_name TEXT) to table `hidelist`\n    // 9 : rebuild table `hidelist` to change primary key (PRIMARY KEY(package_name, process))\n    // 10: remove table `logs`\n    // 11: remove table `hidelist` and create table `denylist` (same data structure)\n    // 12: rebuild table `policies` to drop column `package_name`\n\n    if (/* 0, 1, 2, 3, 4, 5, 6 */ ver <= 6) {\n        sql_chk_log(create_policy);\n        sql_chk_log(create_settings);\n        sql_chk_log(create_strings);\n        sql_chk_log(create_denylist);\n\n        // Directly jump to latest\n        ver = DB_VERSION;\n        upgrade = true;\n    }\n    if (ver == 7) {\n        sql_chk_log(sql_exec_impl, db.get(),\n                \"BEGIN TRANSACTION;\"\n                \"ALTER TABLE hidelist RENAME TO hidelist_tmp;\"\n                \"CREATE TABLE IF NOT EXISTS hidelist \"\n                \"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));\"\n                \"INSERT INTO hidelist SELECT process as package_name, process FROM hidelist_tmp;\"\n                \"DROP TABLE hidelist_tmp;\"\n                \"COMMIT;\");\n        // Directly jump to version 9\n        ver = 9;\n        upgrade = true;\n    }\n    if (ver == 8) {\n        sql_chk_log(sql_exec_impl, db.get(),\n                \"BEGIN TRANSACTION;\"\n                \"ALTER TABLE hidelist RENAME TO hidelist_tmp;\"\n                \"CREATE TABLE IF NOT EXISTS hidelist \"\n                \"(package_name TEXT, process TEXT, PRIMARY KEY(package_name, process));\"\n                \"INSERT INTO hidelist SELECT * FROM hidelist_tmp;\"\n                \"DROP TABLE hidelist_tmp;\"\n                \"COMMIT;\");\n        ver = 9;\n        upgrade = true;\n    }\n    if (ver == 9) {\n        sql_chk_log(sql_exec_impl, db.get(), \"DROP TABLE IF EXISTS logs\", nullptr, nullptr);\n        ver = 10;\n        upgrade = true;\n    }\n    if (ver == 10) {\n        sql_chk_log(sql_exec_impl, db.get(),\n                \"DROP TABLE IF EXISTS hidelist;\"\n                \"DELETE FROM settings WHERE key='magiskhide';\");\n        sql_chk_log(create_denylist);\n        ver = 11;\n        upgrade = true;\n    }\n    if (ver == 11) {\n        sql_chk_log(sql_exec_impl, db.get(),\n                \"BEGIN TRANSACTION;\"\n                \"ALTER TABLE policies RENAME TO policies_tmp;\"\n                \"CREATE TABLE IF NOT EXISTS policies \"\n                \"(uid INT, policy INT, until INT, logging INT, \"\n                \"notification INT, PRIMARY KEY(uid));\"\n                \"INSERT INTO policies \"\n                \"SELECT uid, policy, until, logging, notification FROM policies_tmp;\"\n                \"DROP TABLE policies_tmp;\"\n                \"COMMIT;\");\n        ver = 12;\n        upgrade = true;\n    }\n\n    if (upgrade) {\n        // Set version\n        sql_chk_log(sql_exec_impl, db.get(), \"PRAGMA user_version=\" DB_VERSION_STR);\n    }\n\n    return db.release();\n}\n\n// Exported from Rust\nextern \"C\" int sql_exec_rs(\n        rust::Str zSql,\n        sql_bind_callback bind_cb, void *bind_cookie,\n        sql_exec_callback exec_cb, void *exec_cookie);\n\nbool db_exec(const char *sql, DbArgs args, db_exec_callback exec_fn) {\n    using db_bind_callback = std::function<int(int, DbStatement&)>;\n\n    db_bind_callback bind_fn = {};\n    sql_bind_callback bind_cb = nullptr;\n    if (!args.empty()) {\n        bind_fn = std::ref(args);\n        bind_cb = [](void *v, int index, DbStatement &stmt) -> int {\n            auto fn = static_cast<db_bind_callback*>(v);\n            return fn->operator()(index, stmt);\n        };\n    }\n    sql_exec_callback exec_cb = nullptr;\n    if (exec_fn) {\n        exec_cb = [](void *v, StringSlice columns, const DbValues &values) {\n            auto fn = static_cast<db_exec_callback*>(v);\n            fn->operator()(columns, values);\n        };\n    }\n    sql_chk_log_ret(false, sql_exec_rs, sql, bind_cb, &bind_fn, exec_cb, &exec_fn);\n    return true;\n}\n\nint DbArgs::operator()(int index, DbStatement &stmt) {\n    if (curr < args.size()) {\n        const auto &arg = args[curr++];\n        switch (arg.type) {\n            case DbArg::INT:\n                return stmt.bind_int64(index, arg.int_val);\n            case DbArg::TEXT:\n                return stmt.bind_text(index, arg.str_val);\n        }\n    }\n    return SQLITE_OK;\n}\n"
  },
  {
    "path": "native/src/core/su/connect.rs",
    "content": "use super::SuInfo;\nuse super::db::RootSettings;\nuse crate::consts::{INTERNAL_DIR, MAGISK_FILE_CON};\nuse crate::daemon::to_user_id;\nuse crate::ffi::{SuPolicy, SuRequest, get_magisk_tmp};\nuse crate::socket::IpcRead;\nuse ExtraVal::{Bool, Int, IntList, Str};\nuse base::{\n    BytesExt, FileAttr, LibcReturn, LoggedResult, ResultExt, Utf8CStrBuf, cstr, fork_dont_care,\n};\nuse nix::fcntl::OFlag;\nuse nix::poll::{PollFd, PollFlags, PollTimeout};\nuse num_traits::AsPrimitive;\nuse std::fmt::Write;\nuse std::fs::File;\nuse std::os::fd::AsFd;\nuse std::os::unix::net::UCred;\nuse std::process::{Command, exit};\n\nstruct Extra<'a> {\n    key: &'static str,\n    value: ExtraVal<'a>,\n}\n\nenum ExtraVal<'a> {\n    Int(i32),\n    Bool(bool),\n    Str(&'a str),\n    IntList(&'a [u32]),\n}\n\nimpl Extra<'_> {\n    fn add_intent(&self, cmd: &mut Command) {\n        match self.value {\n            Int(i) => {\n                cmd.args([\"--ei\", self.key, &i.to_string()]);\n            }\n            Bool(b) => {\n                cmd.args([\"--ez\", self.key, &b.to_string()]);\n            }\n            Str(s) => {\n                cmd.args([\"--es\", self.key, s]);\n            }\n            IntList(list) => {\n                cmd.args([\"--es\", self.key]);\n                let mut tmp = String::new();\n                list.iter().for_each(|i| {\n                    write!(&mut tmp, \"{i},\").ok();\n                });\n                tmp.pop();\n                cmd.arg(&tmp);\n            }\n        }\n    }\n\n    fn add_bind(&self, cmd: &mut Command) {\n        let mut tmp: String;\n        match self.value {\n            Int(i) => {\n                tmp = format!(\"{}:i:{}\", self.key, i);\n            }\n            Bool(b) => {\n                tmp = format!(\"{}:b:{}\", self.key, b);\n            }\n            Str(s) => {\n                let s = s.replace(\"\\\\\", \"\\\\\\\\\").replace(\":\", \"\\\\:\");\n                tmp = format!(\"{}:s:{}\", self.key, s);\n            }\n            IntList(list) => {\n                tmp = format!(\"{}:s:\", self.key);\n                if !list.is_empty() {\n                    list.iter().for_each(|i| {\n                        write!(&mut tmp, \"{i},\").ok();\n                    });\n                    tmp.pop();\n                }\n            }\n        }\n        cmd.args([\"--extra\", &tmp]);\n    }\n\n    fn add_bind_legacy(&self, cmd: &mut Command) {\n        match self.value {\n            Str(s) => {\n                let tmp = format!(\"{}:s:{}\", self.key, s);\n                cmd.args([\"--extra\", &tmp]);\n            }\n            _ => self.add_bind(cmd),\n        }\n    }\n}\n\npub(super) struct SuAppContext<'a> {\n    pub(super) cred: UCred,\n    pub(super) request: &'a SuRequest,\n    pub(super) info: &'a SuInfo,\n    pub(super) settings: &'a mut RootSettings,\n    pub(super) sdk_int: i32,\n}\n\nimpl SuAppContext<'_> {\n    fn exec_cmd(&self, action: &'static str, extras: &[Extra], use_provider: bool) {\n        let user = to_user_id(self.info.eval_uid);\n        let user = user.to_string();\n\n        if use_provider {\n            let provider = format!(\"content://{}.provider\", self.info.mgr_pkg);\n            let mut cmd = Command::new(\"/system/bin/app_process\");\n            cmd.args([\n                \"/system/bin\",\n                \"com.android.commands.content.Content\",\n                \"call\",\n                \"--uri\",\n                &provider,\n                \"--user\",\n                &user,\n                \"--method\",\n                action,\n            ]);\n            if self.sdk_int >= 30 {\n                extras.iter().for_each(|e| e.add_bind(&mut cmd))\n            } else {\n                extras.iter().for_each(|e| e.add_bind_legacy(&mut cmd))\n            }\n            cmd.env(\"CLASSPATH\", \"/system/framework/content.jar\");\n\n            if let Ok(output) = cmd.output()\n                && !output.stderr.contains(b\"Error\")\n                && !output.stdout.contains(b\"Error\")\n            {\n                // The provider call succeed\n                return;\n            }\n        }\n\n        let mut cmd = Command::new(\"/system/bin/app_process\");\n        cmd.args([\n            \"/system/bin\",\n            \"com.android.commands.am.Am\",\n            \"start\",\n            \"-p\",\n            &self.info.mgr_pkg,\n            \"--user\",\n            &user,\n            \"-a\",\n            \"android.intent.action.VIEW\",\n            \"-f\",\n            // FLAG_ACTIVITY_NEW_TASK|FLAG_ACTIVITY_MULTIPLE_TASK|\n            // FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|FLAG_INCLUDE_STOPPED_PACKAGES\n            \"0x18800020\",\n            \"--es\",\n            \"action\",\n            action,\n        ]);\n        extras.iter().for_each(|e| e.add_intent(&mut cmd));\n        cmd.env(\"CLASSPATH\", \"/system/framework/am.jar\");\n\n        // Sometimes `am start` will fail, keep trying until it works\n        loop {\n            if let Ok(output) = cmd.output()\n                && !output.stdout.is_empty()\n            {\n                break;\n            }\n        }\n    }\n\n    fn app_request(&mut self) {\n        let mut fifo = cstr::buf::new::<64>();\n        fifo.write_fmt(format_args!(\n            \"{}/{}/su_request_{}\",\n            get_magisk_tmp(),\n            INTERNAL_DIR,\n            self.cred.pid.unwrap_or(-1)\n        ))\n        .ok();\n\n        let fd = || -> LoggedResult<File> {\n            let mut attr = FileAttr::new();\n            attr.st.st_mode = 0o600;\n            attr.st.st_uid = self.info.mgr_uid.as_();\n            attr.st.st_gid = self.info.mgr_uid.as_();\n            attr.con.push_str(MAGISK_FILE_CON);\n\n            fifo.mkfifo(0o600)?;\n            fifo.set_attr(&attr)?;\n\n            let extras = [\n                Extra {\n                    key: \"fifo\",\n                    value: Str(&fifo),\n                },\n                Extra {\n                    key: \"uid\",\n                    value: Int(self.info.eval_uid),\n                },\n                Extra {\n                    key: \"pid\",\n                    value: Int(self.cred.pid.unwrap_or(-1)),\n                },\n            ];\n            self.exec_cmd(\"request\", &extras, false);\n\n            // Open with O_RDWR to prevent FIFO open block\n            let fd = fifo.open(OFlag::O_RDWR | OFlag::O_CLOEXEC)?;\n            let mut pfd = [PollFd::new(fd.as_fd(), PollFlags::POLLIN)];\n\n            // Wait for data input for at most 70 seconds\n            nix::poll::poll(\n                &mut pfd,\n                PollTimeout::try_from(70 * 1000).unwrap_or(PollTimeout::NONE),\n            )\n            .check_os_err(\"poll\", None, None)?;\n            Ok(fd)\n        }();\n\n        fifo.remove().log_ok();\n\n        if let Ok(mut fd) = fd {\n            self.settings.policy = SuPolicy {\n                repr: fd\n                    .read_decodable::<i32>()\n                    .log()\n                    .map(i32::from_be)\n                    .unwrap_or(SuPolicy::Deny.repr),\n            };\n        } else {\n            self.settings.policy = SuPolicy::Deny;\n        };\n    }\n\n    fn app_notify(&self) {\n        let extras = [\n            Extra {\n                key: \"from.uid\",\n                value: Int(self.cred.uid.as_()),\n            },\n            Extra {\n                key: \"pid\",\n                value: Int(self.cred.pid.unwrap_or(-1).as_()),\n            },\n            Extra {\n                key: \"policy\",\n                value: Int(self.settings.policy.repr),\n            },\n        ];\n        self.exec_cmd(\"notify\", &extras, true);\n    }\n\n    fn app_log(&self) {\n        let command = if self.request.command.is_empty() {\n            &self.request.shell\n        } else {\n            &self.request.command\n        };\n        let extras = [\n            Extra {\n                key: \"from.uid\",\n                value: Int(self.cred.uid.as_()),\n            },\n            Extra {\n                key: \"to.uid\",\n                value: Int(self.request.target_uid),\n            },\n            Extra {\n                key: \"pid\",\n                value: Int(self.cred.pid.unwrap_or(-1).as_()),\n            },\n            Extra {\n                key: \"policy\",\n                value: Int(self.settings.policy.repr),\n            },\n            Extra {\n                key: \"target\",\n                value: Int(self.request.target_pid),\n            },\n            Extra {\n                key: \"context\",\n                value: Str(&self.request.context),\n            },\n            Extra {\n                key: \"gids\",\n                value: IntList(&self.request.gids),\n            },\n            Extra {\n                key: \"command\",\n                value: Str(command),\n            },\n            Extra {\n                key: \"notify\",\n                value: Bool(self.settings.notify),\n            },\n        ];\n        self.exec_cmd(\"log\", &extras, true);\n    }\n\n    pub(super) fn connect_app(&mut self) {\n        // If policy is undetermined, show dialog for user consent\n        if self.settings.policy == SuPolicy::Query {\n            self.app_request();\n        }\n\n        if !self.settings.log && !self.settings.notify {\n            return;\n        }\n\n        if fork_dont_care() != 0 {\n            return;\n        }\n\n        // Notify su usage to application\n        if self.settings.log {\n            self.app_log();\n        } else if self.settings.notify {\n            self.app_notify();\n        }\n\n        exit(0);\n    }\n}\n"
  },
  {
    "path": "native/src/core/su/daemon.rs",
    "content": "use super::connect::SuAppContext;\nuse super::db::RootSettings;\nuse crate::daemon::{AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id};\nuse crate::db::{DbSettings, MultiuserMode, RootAccess};\nuse crate::ffi::{SuPolicy, SuRequest, exec_root_shell};\nuse crate::socket::IpcRead;\nuse base::{LoggedResult, ResultExt, WriteExt, debug, error, exit_on_error, libc, warn};\nuse std::os::fd::IntoRawFd;\nuse std::os::unix::net::{UCred, UnixStream};\nuse std::sync::Arc;\nuse std::time::{Duration, Instant};\n\n#[allow(unused_imports)]\nuse std::os::fd::AsRawFd;\nuse std::sync::nonpoison::Mutex;\n\nconst DEFAULT_SHELL: &str = \"/system/bin/sh\";\n\nimpl Default for SuRequest {\n    fn default() -> Self {\n        SuRequest {\n            target_uid: AID_ROOT,\n            target_pid: -1,\n            login: false,\n            keep_env: false,\n            drop_cap: false,\n            shell: DEFAULT_SHELL.to_string(),\n            command: \"\".to_string(),\n            context: \"\".to_string(),\n            gids: vec![],\n        }\n    }\n}\n\npub struct SuInfo {\n    pub(super) uid: i32,\n    pub(super) eval_uid: i32,\n    pub(super) mgr_pkg: String,\n    pub(super) mgr_uid: i32,\n    cfg: DbSettings,\n    access: Mutex<AccessInfo>,\n}\n\nstruct AccessInfo {\n    settings: RootSettings,\n    timestamp: Instant,\n}\n\nimpl Default for SuInfo {\n    fn default() -> Self {\n        SuInfo {\n            uid: -1,\n            eval_uid: -1,\n            cfg: Default::default(),\n            mgr_pkg: Default::default(),\n            mgr_uid: -1,\n            access: Default::default(),\n        }\n    }\n}\n\nimpl Default for AccessInfo {\n    fn default() -> Self {\n        AccessInfo {\n            settings: Default::default(),\n            timestamp: Instant::now(),\n        }\n    }\n}\n\nimpl SuInfo {\n    fn allow(uid: i32) -> SuInfo {\n        let access = RootSettings {\n            policy: SuPolicy::Allow,\n            log: false,\n            notify: false,\n        };\n        SuInfo {\n            uid,\n            access: Mutex::new(AccessInfo::new(access)),\n            ..Default::default()\n        }\n    }\n\n    fn deny(uid: i32) -> SuInfo {\n        let access = RootSettings {\n            policy: SuPolicy::Deny,\n            log: false,\n            notify: false,\n        };\n        SuInfo {\n            uid,\n            access: Mutex::new(AccessInfo::new(access)),\n            ..Default::default()\n        }\n    }\n}\n\nimpl AccessInfo {\n    fn new(settings: RootSettings) -> AccessInfo {\n        AccessInfo {\n            settings,\n            timestamp: Instant::now(),\n        }\n    }\n\n    fn is_fresh(&self) -> bool {\n        self.timestamp.elapsed() < Duration::from_secs(3)\n    }\n\n    fn refresh(&mut self) {\n        self.timestamp = Instant::now();\n    }\n}\n\nimpl MagiskD {\n    pub fn su_daemon_handler(&self, mut client: UnixStream, cred: UCred) {\n        debug!(\n            \"su: request from uid=[{}], pid=[{}], client=[{}]\",\n            cred.uid,\n            cred.pid.unwrap_or(-1),\n            client.as_raw_fd()\n        );\n\n        let mut req = match client.read_decodable::<SuRequest>().log() {\n            Ok(req) => req,\n            Err(_) => {\n                warn!(\"su: remote process probably died, abort\");\n                client.write_pod(&SuPolicy::Deny.repr).ok();\n                return;\n            }\n        };\n\n        let info = self.get_su_info(cred.uid as i32);\n        {\n            let mut access = info.access.lock();\n\n            // Talk to su manager\n            let mut app = SuAppContext {\n                cred,\n                request: &req,\n                info: &info,\n                settings: &mut access.settings,\n                sdk_int: self.sdk_int(),\n            };\n            app.connect_app();\n\n            // Before unlocking, refresh the timestamp\n            access.refresh();\n\n            if access.settings.policy == SuPolicy::Restrict {\n                req.drop_cap = true;\n            }\n\n            if access.settings.policy == SuPolicy::Deny {\n                warn!(\"su: request rejected ({})\", info.uid);\n                client.write_pod(&SuPolicy::Deny.repr).ok();\n                return;\n            }\n        }\n\n        // At this point, the root access is granted.\n        // Fork a child root process and monitor its exit value.\n        let child = unsafe { libc::fork() };\n        if child == 0 {\n            debug!(\"su: fork handler\");\n\n            // Abort upon any error occurred\n            exit_on_error(true);\n\n            // ack\n            client.write_pod(&0).ok();\n\n            exec_root_shell(\n                client.into_raw_fd(),\n                cred.pid.unwrap_or(-1),\n                &mut req,\n                info.cfg.mnt_ns,\n            );\n            return;\n        }\n        if child < 0 {\n            error!(\"su: fork failed, abort\");\n            return;\n        }\n\n        // Wait result\n        debug!(\"su: waiting child pid=[{}]\", child);\n        let mut status = 0;\n        let code = unsafe {\n            if libc::waitpid(child, &mut status, 0) > 0 {\n                libc::WEXITSTATUS(status)\n            } else {\n                -1\n            }\n        };\n        debug!(\"su: return code=[{}]\", code);\n        client.write_pod(&code).ok();\n    }\n\n    fn get_su_info(&self, uid: i32) -> Arc<SuInfo> {\n        if uid == AID_ROOT {\n            return Arc::new(SuInfo::allow(AID_ROOT));\n        }\n\n        let cached = self.cached_su_info.load();\n        if cached.uid == uid && cached.access.lock().is_fresh() {\n            return cached;\n        }\n\n        let info = self.build_su_info(uid);\n        self.cached_su_info.store(info.clone());\n        info\n    }\n\n    #[cfg(feature = \"su-check-db\")]\n    fn build_su_info(&self, uid: i32) -> Arc<SuInfo> {\n        let result = || -> LoggedResult<Arc<SuInfo>> {\n            let cfg = self.get_db_settings()?;\n\n            // Check multiuser settings\n            let eval_uid = match cfg.multiuser_mode {\n                MultiuserMode::OwnerOnly => {\n                    if to_user_id(uid) != 0 {\n                        return Ok(Arc::new(SuInfo::deny(uid)));\n                    }\n                    uid\n                }\n                MultiuserMode::OwnerManaged => to_app_id(uid),\n                _ => uid,\n            };\n\n            let mut access = RootSettings::default();\n            self.get_root_settings(eval_uid, &mut access)?;\n\n            // We need to talk to the manager, get the app info\n            let (mgr_uid, mgr_pkg) =\n                if access.policy == SuPolicy::Query || access.log || access.notify {\n                    self.get_manager(to_user_id(eval_uid), true)\n                } else {\n                    (-1, String::new())\n                };\n\n            // If it's the manager, allow it silently\n            if to_app_id(uid) == to_app_id(mgr_uid) {\n                return Ok(Arc::new(SuInfo::allow(uid)));\n            }\n\n            // Check su access settings\n            match cfg.root_access {\n                RootAccess::Disabled => {\n                    warn!(\"Root access is disabled!\");\n                    return Ok(Arc::new(SuInfo::deny(uid)));\n                }\n                RootAccess::AdbOnly => {\n                    if uid != AID_SHELL {\n                        warn!(\"Root access limited to ADB only!\");\n                        return Ok(Arc::new(SuInfo::deny(uid)));\n                    }\n                }\n                RootAccess::AppsOnly => {\n                    if uid == AID_SHELL {\n                        warn!(\"Root access is disabled for ADB!\");\n                        return Ok(Arc::new(SuInfo::deny(uid)));\n                    }\n                }\n                _ => {}\n            };\n\n            // If still not determined, check if manager exists\n            if access.policy == SuPolicy::Query && mgr_uid < 0 {\n                return Ok(Arc::new(SuInfo::deny(uid)));\n            }\n\n            // Finally, the SuInfo\n            Ok(Arc::new(SuInfo {\n                uid,\n                eval_uid,\n                mgr_pkg,\n                mgr_uid,\n                cfg,\n                access: Mutex::new(AccessInfo::new(access)),\n            }))\n        }();\n\n        result.unwrap_or(Arc::new(SuInfo::deny(uid)))\n    }\n\n    #[cfg(not(feature = \"su-check-db\"))]\n    fn build_su_info(&self, uid: i32) -> Arc<SuInfo> {\n        Arc::new(SuInfo::allow(uid))\n    }\n}\n"
  },
  {
    "path": "native/src/core/su/db.rs",
    "content": "use crate::daemon::{\n    AID_APP_END, AID_APP_START, AID_ROOT, AID_SHELL, MagiskD, to_app_id, to_user_id,\n};\nuse crate::db::DbArg::Integer;\nuse crate::db::{MultiuserMode, RootAccess, SqlTable, SqliteResult, SqliteReturn};\nuse crate::ffi::{DbValues, SuPolicy};\nuse base::ResultExt;\n\nimpl Default for SuPolicy {\n    fn default() -> Self {\n        SuPolicy::Query\n    }\n}\n\n#[derive(Default)]\npub struct RootSettings {\n    pub policy: SuPolicy,\n    pub log: bool,\n    pub notify: bool,\n}\n\nimpl SqlTable for RootSettings {\n    fn on_row(&mut self, columns: &[String], values: &DbValues) {\n        for (i, column) in columns.iter().enumerate() {\n            let val = values.get_int(i as i32);\n            match column.as_str() {\n                \"policy\" => self.policy.repr = val,\n                \"logging\" => self.log = val != 0,\n                \"notification\" => self.notify = val != 0,\n                _ => {}\n            }\n        }\n    }\n}\n\nstruct UidList(Vec<i32>);\n\nimpl SqlTable for UidList {\n    fn on_row(&mut self, _: &[String], values: &DbValues) {\n        self.0.push(values.get_int(0));\n    }\n}\n\nimpl MagiskD {\n    pub fn get_root_settings(&self, uid: i32, settings: &mut RootSettings) -> SqliteResult<()> {\n        self.db_exec_with_rows(\n            \"SELECT policy, logging, notification FROM policies \\\n             WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))\",\n            &[Integer(uid as i64)],\n            settings,\n        )\n        .sql_result()\n    }\n\n    pub fn prune_su_access(&self) {\n        let mut list = UidList(Vec::new());\n        if self\n            .db_exec_with_rows(\"SELECT uid FROM policies\", &[], &mut list)\n            .sql_result()\n            .log()\n            .is_err()\n        {\n            return;\n        }\n\n        let app_list = self.get_app_no_list();\n        let mut rm_uids = Vec::new();\n\n        for uid in list.0 {\n            let app_id = to_app_id(uid);\n            if (AID_APP_START..=AID_APP_END).contains(&app_id) {\n                let app_no = app_id - AID_APP_START;\n                if !app_list.contains(app_no as usize) {\n                    // The app_id is no longer installed\n                    rm_uids.push(uid);\n                }\n            }\n        }\n\n        for uid in rm_uids {\n            self.db_exec(\"DELETE FROM policies WHERE uid=?\", &[Integer(uid as i64)]);\n        }\n    }\n\n    pub fn uid_granted_root(&self, mut uid: i32) -> bool {\n        if uid == AID_ROOT {\n            return true;\n        }\n\n        let cfg = match self.get_db_settings().log() {\n            Ok(cfg) => cfg,\n            Err(_) => return false,\n        };\n\n        // Check user root access settings\n        match cfg.root_access {\n            RootAccess::Disabled => return false,\n            RootAccess::AppsOnly => {\n                if uid == AID_SHELL {\n                    return false;\n                }\n            }\n            RootAccess::AdbOnly => {\n                if uid != AID_SHELL {\n                    return false;\n                }\n            }\n            _ => {}\n        }\n\n        // Check multiuser settings\n        match cfg.multiuser_mode {\n            MultiuserMode::OwnerOnly => {\n                if to_user_id(uid) != 0 {\n                    return false;\n                }\n            }\n            MultiuserMode::OwnerManaged => uid = to_app_id(uid),\n            _ => {}\n        }\n\n        let mut granted = false;\n        let mut output_fn =\n            |_: &[String], values: &DbValues| granted = values.get_int(0) == SuPolicy::Allow.repr;\n        self.db_exec_with_rows(\n            \"SELECT policy FROM policies WHERE uid=? AND (until=0 OR until>strftime('%s', 'now'))\",\n            &[Integer(uid as i64)],\n            &mut output_fn,\n        );\n\n        granted\n    }\n}\n"
  },
  {
    "path": "native/src/core/su/mod.rs",
    "content": "mod connect;\nmod daemon;\nmod db;\nmod pts;\n\npub use daemon::SuInfo;\npub use pts::{get_pty_num, pump_tty};\n"
  },
  {
    "path": "native/src/core/su/pts.rs",
    "content": "use base::{FileOrStd, LibcReturn, LoggedResult, OsResult, ResultExt, libc, warn};\nuse libc::{STDIN_FILENO, TIOCGWINSZ, TIOCSWINSZ, c_int, winsize};\nuse nix::fcntl::{OFlag, SpliceFFlags};\nuse nix::poll::{PollFd, PollFlags, PollTimeout, poll};\nuse nix::sys::signal::{SigSet, Signal, raise};\nuse nix::sys::signalfd::{SfdFlags, SignalFd};\nuse nix::sys::termios::{SetArg, Termios, cfmakeraw, tcgetattr, tcsetattr};\nuse nix::unistd::pipe2;\nuse std::fs::File;\nuse std::io::{Read, Write};\nuse std::mem::MaybeUninit;\nuse std::os::fd::{AsFd, AsRawFd, FromRawFd, RawFd};\nuse std::sync::atomic::{AtomicBool, Ordering};\n\nstatic SHOULD_USE_SPLICE: AtomicBool = AtomicBool::new(true);\nconst TIOCGPTN: u32 = 0x80045430;\n\nunsafe extern \"C\" {\n    // Don't use the declaration from the libc crate as request should be u32 not i32\n    fn ioctl(fd: c_int, request: u32, ...) -> i32;\n}\n\npub fn get_pty_num(fd: i32) -> i32 {\n    let mut pty_num = -1i32;\n    if unsafe { ioctl(fd, TIOCGPTN, &mut pty_num) } != 0 {\n        warn!(\"Failed to get pty number\");\n    }\n    pty_num\n}\n\nfn sync_winsize(ptmx: i32) {\n    let mut ws: winsize = unsafe { std::mem::zeroed() };\n    if unsafe { ioctl(STDIN_FILENO, TIOCGWINSZ as u32, &mut ws) } >= 0 {\n        unsafe { ioctl(ptmx, TIOCSWINSZ as u32, &ws) };\n    }\n}\n\nfn splice(fd_in: impl AsFd, fd_out: impl AsFd, len: usize) -> OsResult<'static, usize> {\n    nix::fcntl::splice(fd_in, None, fd_out, None, len, SpliceFFlags::empty())\n        .into_os_result(\"splice\", None, None)\n}\n\nfn pump_via_copy(mut fd_in: &File, mut fd_out: &File) -> LoggedResult<()> {\n    let mut buf = MaybeUninit::<[u8; 4096]>::uninit();\n    let buf = unsafe { buf.assume_init_mut() };\n    let len = fd_in.read(buf)?;\n    fd_out.write_all(&buf[..len])?;\n    Ok(())\n}\n\nfn pump_via_splice(fd_in: &File, fd_out: &File, pipe: &(File, File)) -> LoggedResult<()> {\n    if !SHOULD_USE_SPLICE.load(Ordering::Relaxed) {\n        return pump_via_copy(fd_in, fd_out);\n    }\n\n    // The pipe capacity is by default 16 pages, let's just use 65536\n    let Ok(len) = splice(fd_in, &pipe.1, 65536) else {\n        // If splice failed, stop using splice and fallback to userspace copy\n        SHOULD_USE_SPLICE.store(false, Ordering::Relaxed);\n        return pump_via_copy(fd_in, fd_out);\n    };\n    if len == 0 {\n        return Ok(());\n    }\n    if splice(&pipe.0, fd_out, len).is_err() {\n        // If splice failed, stop using splice and fallback to userspace copy\n        SHOULD_USE_SPLICE.store(false, Ordering::Relaxed);\n        return pump_via_copy(&pipe.0, fd_out);\n    }\n    Ok(())\n}\n\nfn set_stdin_raw() -> LoggedResult<Termios> {\n    let mut term = tcgetattr(FileOrStd::StdIn.as_file())?;\n    let old_term = term.clone();\n\n    let old_output_flags = old_term.output_flags;\n    cfmakeraw(&mut term);\n\n    // Preserve output_flags, since we are not setting stdout raw\n    term.output_flags = old_output_flags;\n\n    tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term)\n        .or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term))\n        .check_os_err(\"tcsetattr\", None, None)\n        .log_with_msg(|w| w.write_str(\"Failed to set terminal attributes\"))?;\n\n    Ok(old_term)\n}\n\nfn restore_stdin(term: Termios) -> LoggedResult<()> {\n    tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSAFLUSH, &term)\n        .or_else(|_| tcsetattr(FileOrStd::StdIn.as_file(), SetArg::TCSADRAIN, &term))\n        .check_os_err(\"tcsetattr\", None, None)\n        .log_with_msg(|w| w.write_str(\"Failed to restore terminal attributes\"))\n}\n\nfn pump_tty_impl(ptmx: File, pump_stdin: bool) -> LoggedResult<()> {\n    let mut signal_fd: Option<SignalFd> = None;\n\n    let raw_ptmx = ptmx.as_raw_fd();\n\n    let mut poll_fds = Vec::with_capacity(3);\n    poll_fds.push(PollFd::new(ptmx.as_fd(), PollFlags::POLLIN));\n    if pump_stdin {\n        // If stdin is tty, we need to monitor SIGWINCH\n        let mut set = SigSet::empty();\n        set.add(Signal::SIGWINCH);\n        set.thread_block()\n            .check_os_err(\"pthread_sigmask\", None, None)?;\n        let sig = SignalFd::with_flags(&set, SfdFlags::SFD_CLOEXEC)\n            .into_os_result(\"signalfd\", None, None)?;\n        signal_fd = Some(sig);\n        unsafe {\n            // SAFETY: signal_fd is always Some\n            poll_fds.push(PollFd::new(\n                signal_fd.as_ref().unwrap_unchecked().as_fd(),\n                PollFlags::POLLIN,\n            ));\n        }\n\n        // We also need to pump stdin to ptmx\n        poll_fds.push(PollFd::new(\n            FileOrStd::StdIn.as_file().as_fd(),\n            PollFlags::POLLIN,\n        ));\n    }\n\n    // Any flag in this list indicates stop polling\n    let stop_flags = PollFlags::POLLERR | PollFlags::POLLHUP | PollFlags::POLLNVAL;\n\n    // Open a pipe to bypass userspace copy with splice\n    let pipe_fd = pipe2(OFlag::O_CLOEXEC).into_os_result(\"pipe2\", None, None)?;\n    let pipe_fd = (File::from(pipe_fd.0), File::from(pipe_fd.1));\n\n    'poll: loop {\n        // Wait for event\n        poll(&mut poll_fds, PollTimeout::NONE).check_os_err(\"poll\", None, None)?;\n        for pfd in &poll_fds {\n            if pfd.all().unwrap_or(false) {\n                let raw_fd = pfd.as_fd().as_raw_fd();\n                if raw_fd == STDIN_FILENO {\n                    pump_via_splice(FileOrStd::StdIn.as_file(), &ptmx, &pipe_fd)?;\n                } else if raw_fd == raw_ptmx {\n                    pump_via_splice(&ptmx, FileOrStd::StdOut.as_file(), &pipe_fd)?;\n                } else if let Some(sig) = &signal_fd\n                    && raw_fd == sig.as_raw_fd()\n                {\n                    sync_winsize(raw_ptmx);\n                    sig.read_signal()?;\n                }\n            } else if pfd\n                .revents()\n                .unwrap_or(PollFlags::POLLHUP)\n                .intersects(stop_flags)\n            {\n                // If revents is None or contains any err_flags, stop polling\n                break 'poll;\n            }\n        }\n    }\n    Ok(())\n}\n\npub fn pump_tty(ptmx: RawFd, pump_stdin: bool) {\n    let old_term = if pump_stdin {\n        sync_winsize(ptmx);\n        set_stdin_raw().ok()\n    } else {\n        None\n    };\n\n    let ptmx = unsafe { File::from_raw_fd(ptmx) };\n    pump_tty_impl(ptmx, pump_stdin).ok();\n\n    if let Some(term) = old_term {\n        restore_stdin(term).ok();\n    }\n    raise(Signal::SIGWINCH).ok();\n}\n"
  },
  {
    "path": "native/src/core/su/su.cpp",
    "content": "/*\n * Copyright 2017 - 2025, John Wu (@topjohnwu)\n * Copyright 2015, Pierre-Hugues Husson <phh@phh.me>\n * Copyright 2010, Adam Shanks (@ChainsDD)\n * Copyright 2008, Zinx Verituse (@zinxv)\n */\n\n#include <unistd.h>\n#include <getopt.h>\n#include <fcntl.h>\n#include <pwd.h>\n#include <linux/securebits.h>\n#include <sys/capability.h>\n#include <sys/prctl.h>\n#include <sched.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/mount.h>\n\n#include <algorithm>\n\n#include <consts.hpp>\n#include <base.hpp>\n#include <flags.h>\n#include <core.hpp>\n\nusing namespace std;\n\n#define DEFAULT_SHELL \"/system/bin/sh\"\n\n// Constants for atty\n#define ATTY_IN    (1 << 0)\n#define ATTY_OUT   (1 << 1)\n#define ATTY_ERR   (1 << 2)\n\nint quit_signals[] = { SIGALRM, SIGABRT, SIGHUP, SIGPIPE, SIGQUIT, SIGTERM, SIGINT, 0 };\n\n[[noreturn]] static void usage(int status) {\n    FILE *stream = (status == EXIT_SUCCESS) ? stdout : stderr;\n\n    fprintf(stream,\n    \"MagiskSU\\n\\n\"\n    \"Usage: su [options] [-] [user [argument...]]\\n\\n\"\n    \"Options:\\n\"\n    \"  -c, --command COMMAND         Pass COMMAND to the invoked shell\\n\"\n    \"  -i, --interactive             Force pseudo-terminal allocation when using -c\\n\"\n    \"  -g, --group GROUP             Specify the primary group\\n\"\n    \"  -G, --supp-group GROUP        Specify a supplementary group\\n\"\n    \"                                The first specified supplementary group is also used\\n\"\n    \"                                as a primary group if the option -g is not specified\\n\"\n    \"  -Z, --context CONTEXT         Change SELinux context\\n\"\n    \"  -t, --target PID              PID to take mount namespace from\\n\"\n    \"  -d, --drop-cap                Drop all Linux capabilities\\n\"\n    \"  -h, --help                    Display this help message and exit\\n\"\n    \"  -, -l, --login                Pretend the shell to be a login shell\\n\"\n    \"  -m, -p,\\n\"\n    \"  --preserve-environment        Preserve the entire environment\\n\"\n    \"  -s, --shell SHELL             Use SHELL instead of the default \" DEFAULT_SHELL \"\\n\"\n    \"  -v, --version                 Display version number and exit\\n\"\n    \"  -V                            Display version code and exit\\n\"\n    \"  -mm, -M,\\n\"\n    \"  --mount-master                Force run in the global mount namespace\\n\\n\");\n    exit(status);\n}\n\nstatic void sighandler(int sig) {\n    // Close all standard I/O to cause the pumps to exit\n    // so we can continue and retrieve the exit code.\n    close(STDIN_FILENO);\n    close(STDOUT_FILENO);\n    close(STDERR_FILENO);\n\n    // Put back all the default handlers\n    struct sigaction act{};\n    act.sa_handler = SIG_DFL;\n    for (int i = 0; quit_signals[i]; ++i) {\n        sigaction(quit_signals[i], &act, nullptr);\n    }\n}\n\nstatic void setup_sighandlers(void (*handler)(int)) {\n    struct sigaction act{};\n    act.sa_handler = handler;\n    for (int i = 0; quit_signals[i]; ++i) {\n        sigaction(quit_signals[i], &act, nullptr);\n    }\n}\n\nint su_client_main(int argc, char *argv[]) {\n    option long_opts[] = {\n            { \"command\",                required_argument,  nullptr, 'c' },\n            { \"help\",                   no_argument,        nullptr, 'h' },\n            { \"login\",                  no_argument,        nullptr, 'l' },\n            { \"preserve-environment\",   no_argument,        nullptr, 'p' },\n            { \"shell\",                  required_argument,  nullptr, 's' },\n            { \"version\",                no_argument,        nullptr, 'v' },\n            { \"context\",                required_argument,  nullptr, 'Z' },\n            { \"mount-master\",           no_argument,        nullptr, 'M' },\n            { \"target\",                 required_argument,  nullptr, 't' },\n            { \"group\",                  required_argument,  nullptr, 'g' },\n            { \"supp-group\",             required_argument,  nullptr, 'G' },\n            { \"interactive\",            no_argument,        nullptr, 'i' },\n            { \"drop-cap\",               no_argument,        nullptr, 'd' },\n            { nullptr, 0, nullptr, 0 },\n    };\n\n    auto req = SuRequest::New();\n\n    for (int i = 0; i < argc; i++) {\n        // Replace -cn and -z with -Z for backwards compatibility\n        if (strcmp(argv[i], \"-cn\") == 0 || strcmp(argv[i], \"-z\") == 0)\n            strcpy(argv[i], \"-Z\");\n        // Replace -mm with -M for supporting getopt_long\n        else if (strcmp(argv[i], \"-mm\") == 0)\n            strcpy(argv[i], \"-M\");\n    }\n\n    bool interactive = false;\n\n    int c;\n    while ((c = getopt_long(argc, argv, \"c:hlimpds:VvuZ:Mt:g:G:\", long_opts, nullptr)) != -1) {\n        switch (c) {\n            case 'c': {\n                string command;\n                for (int i = optind - 1; i < argc; ++i) {\n                    if (!command.empty())\n                        command += ' ';\n                    command += argv[i];\n                }\n                req.command = command;\n                optind = argc;\n                break;\n            }\n            case 'h':\n                usage(EXIT_SUCCESS);\n            case 'i':\n                interactive = true;\n                break;\n            case 'l':\n                req.login = true;\n                break;\n            case 'm':\n            case 'p':\n                req.keep_env = true;\n                break;\n            case 'd':\n                req.drop_cap = true;\n                break;\n            case 's':\n                req.shell = optarg;\n                break;\n            case 'V':\n                printf(\"%d\\n\", MAGISK_VER_CODE);\n                exit(EXIT_SUCCESS);\n            case 'v':\n                printf(\"%s\\n\", MAGISK_VERSION \":MAGISKSU\");\n                exit(EXIT_SUCCESS);\n            case 'Z':\n                req.context = optarg;\n                break;\n            case 'M':\n            case 't':\n                if (req.target_pid != -1) {\n                    fprintf(stderr, \"Can't use -M and -t at the same time\\n\");\n                    usage(EXIT_FAILURE);\n                }\n                if (optarg == nullptr) {\n                    req.target_pid = 0;\n                } else {\n                    req.target_pid = parse_int(optarg);\n                    if (*optarg == '-' || req.target_pid == -1) {\n                        fprintf(stderr, \"Invalid PID: %s\\n\", optarg);\n                        usage(EXIT_FAILURE);\n                    }\n                }\n                break;\n            case 'g':\n            case 'G': {\n                vector<gid_t> gids;\n                if (int gid = parse_int(optarg); gid >= 0) {\n                    gids.insert(c == 'g' ? gids.begin() : gids.end(), gid);\n                } else {\n                    fprintf(stderr, \"Invalid GID: %s\\n\", optarg);\n                    usage(EXIT_FAILURE);\n                }\n                ranges::copy(gids, std::back_inserter(req.gids));\n                break;\n            }\n            default:\n                /* Bionic getopt_long doesn't terminate its error output by newline */\n                fprintf(stderr, \"\\n\");\n                usage(2);\n        }\n    }\n\n    if (optind < argc && strcmp(argv[optind], \"-\") == 0) {\n        req.login = true;\n        optind++;\n    }\n    /* username or uid */\n    if (optind < argc) {\n        if (const passwd *pw = getpwnam(argv[optind]))\n            req.target_uid = pw->pw_uid;\n        else\n            req.target_uid = parse_int(argv[optind]);\n        optind++;\n    }\n\n    // Connect to client\n    owned_fd fd = connect_daemon(RequestCode::SUPERUSER);\n\n    // Send request\n    req.write_to_fd(fd);\n\n    // Wait for ack from daemon\n    if (read_int(fd)) {\n        // Fast fail\n        fprintf(stderr, \"%s\\n\", strerror(EACCES));\n        return EACCES;\n    }\n\n    // Determine which one of our streams are attached to a TTY\n    interactive |= req.command.empty();\n    int atty = 0;\n    if (isatty(STDIN_FILENO) && interactive)  atty |= ATTY_IN;\n    if (isatty(STDOUT_FILENO) && interactive) atty |= ATTY_OUT;\n    if (isatty(STDERR_FILENO) && interactive) atty |= ATTY_ERR;\n\n    // Send stdin\n    send_fd(fd, (atty & ATTY_IN) ? -1 : STDIN_FILENO);\n    // Send stdout\n    send_fd(fd, (atty & ATTY_OUT) ? -1 : STDOUT_FILENO);\n    // Send stderr\n    send_fd(fd, (atty & ATTY_ERR) ? -1 : STDERR_FILENO);\n\n    if (atty) {\n        // We need a PTY. Get one.\n        int ptmx = recv_fd(fd);\n        setup_sighandlers(sighandler);\n        // If stdin is not a tty, and if we pump to ptmx, our process may intercept the input to ptmx and\n        // output to stdout, which cause the target process lost input.\n        pump_tty(ptmx, atty & ATTY_IN);\n    }\n\n    // Get the exit code\n    return read_int(fd);\n}\n\nstatic void drop_caps() {\n    static auto last_valid_cap = []() {\n        uint32_t cap = CAP_WAKE_ALARM;\n        while (prctl(PR_CAPBSET_READ, cap) >= 0) {\n            cap++;\n        }\n        return cap - 1;\n    }();\n    // Drop bounding set\n    for (uint32_t cap = 0; cap <= last_valid_cap; cap++) {\n        if (cap != CAP_SETUID) {\n            prctl(PR_CAPBSET_DROP, cap);\n        }\n    }\n    // Clean inheritable set\n    __user_cap_header_struct header = {.version = _LINUX_CAPABILITY_VERSION_3};\n    __user_cap_data_struct data[_LINUX_CAPABILITY_U32S_3] = {};\n    if (capget(&header, &data[0]) == 0) {\n        for (size_t i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {\n            data[i].inheritable = 0;\n        }\n        capset(&header, &data[0]);\n    }\n    // All capabilities will be lost after exec\n    prctl(PR_SET_SECUREBITS, SECBIT_NOROOT);\n    // Except CAP_SETUID in bounding set, it is a marker for restricted process\n}\n\nstatic bool proc_is_restricted(pid_t pid) {\n    char buf[32] = {};\n    auto bnd = \"CapBnd:\"sv;\n    uint32_t data[_LINUX_CAPABILITY_U32S_3] = {};\n    ssprintf(buf, sizeof(buf), \"/proc/%d/status\", pid);\n    owned_fd status_fd = xopen(buf, O_RDONLY | O_CLOEXEC);\n    file_readline(status_fd, [&](Utf8CStr s) -> bool {\n        string_view line = s;\n        if (line.starts_with(bnd)) {\n            auto p = line.begin();\n            advance(p, bnd.size());\n            while (isspace(*p)) advance(p, 1);\n            line.remove_prefix(distance(line.begin(), p));\n            for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {\n                auto cap = line.substr((_LINUX_CAPABILITY_U32S_3 - 1 - i) * 8, 8);\n                data[i] = parse_uint32_hex(cap);\n            }\n            return false;\n        }\n        return true;\n    });\n\n    bool equal = true;\n    for (int i = 0; i < _LINUX_CAPABILITY_U32S_3; i++) {\n        if (i == CAP_TO_INDEX(CAP_SETUID)) {\n            if (data[i] != CAP_TO_MASK(CAP_SETUID)) equal = false;\n        } else {\n            if (data[i] != 0) equal = false;\n        }\n    }\n    return equal;\n}\n\nstatic void set_identity(int uid, const rust::Vec<gid_t> &groups) {\n    gid_t gid;\n    if (!groups.empty()) {\n        if (setgroups(groups.size(), groups.data())) {\n            PLOGE(\"setgroups\");\n        }\n        gid = groups[0];\n    } else {\n        gid = uid;\n    }\n    if (setresgid(gid, gid, gid)) {\n        PLOGE(\"setresgid (%u)\", uid);\n    }\n    if (setresuid(uid, uid, uid)) {\n        PLOGE(\"setresuid (%u)\", uid);\n    }\n}\n\nvoid exec_root_shell(int client, int pid, SuRequest &req, MntNsMode mode) {\n    // Become session leader\n    xsetsid();\n\n    // The FDs for each of the streams\n    int infd = recv_fd(client);\n    int outfd = recv_fd(client);\n    int errfd = recv_fd(client);\n    int ptsfd = -1;\n\n    // App need a PTY\n    if (infd < 0 || outfd < 0 || errfd < 0) {\n        string pts;\n        string ptmx;\n        auto magiskpts = get_magisk_tmp() + \"/\"s SHELLPTS;\n        if (access(magiskpts.data(), F_OK)) {\n            pts = \"/dev/pts\";\n            ptmx = \"/dev/ptmx\";\n        } else {\n            pts = magiskpts;\n            ptmx = magiskpts + \"/ptmx\";\n        }\n        int ptmx_fd = xopen(ptmx.data(), O_RDWR);\n        grantpt(ptmx_fd);\n        unlockpt(ptmx_fd);\n        int pty_num = get_pty_num(ptmx_fd);\n        if (pty_num < 0) {\n            // Kernel issue? Fallback to /dev/pts\n            close(ptmx_fd);\n            pts = \"/dev/pts\";\n            ptmx_fd = xopen(\"/dev/ptmx\", O_RDWR);\n            grantpt(ptmx_fd);\n            unlockpt(ptmx_fd);\n            pty_num = get_pty_num(ptmx_fd);\n        }\n        send_fd(client, ptmx_fd);\n        close(ptmx_fd);\n\n        string pts_slave = pts + \"/\" + to_string(pty_num);\n        LOGD(\"su: pts_slave=[%s]\\n\", pts_slave.data());\n\n        // Opening the TTY has to occur after the\n        // fork() and setsid() so that it becomes\n        // our controlling TTY and not the daemon's\n        ptsfd = xopen(pts_slave.data(), O_RDWR);\n    }\n\n    // Swap out stdin, stdout, stderr\n    xdup2(infd < 0 ? ptsfd : infd, STDIN_FILENO);\n    xdup2(outfd < 0 ? ptsfd : outfd, STDOUT_FILENO);\n    xdup2(errfd < 0 ? ptsfd : errfd, STDERR_FILENO);\n\n    close(infd);\n    close(outfd);\n    close(errfd);\n    close(ptsfd);\n    close(client);\n\n    // Handle namespaces\n    if (req.target_pid == -1)\n        req.target_pid = pid;\n    else if (req.target_pid == 0)\n        mode = MntNsMode::Global;\n    else if (mode == MntNsMode::Global)\n        mode = MntNsMode::Requester;\n\n    switch (mode) {\n        case MntNsMode::Global:\n            LOGD(\"su: use global namespace\\n\");\n            break;\n        case MntNsMode::Requester:\n            LOGD(\"su: use namespace of pid=[%d]\\n\", req.target_pid);\n            switch_mnt_ns(req.target_pid);\n            break;\n        case MntNsMode::Isolate:\n            LOGD(\"su: use new isolated namespace\\n\");\n            switch_mnt_ns(req.target_pid);\n            xunshare(CLONE_NEWNS);\n            xmount(nullptr, \"/\", nullptr, MS_PRIVATE | MS_REC, nullptr);\n            break;\n    }\n\n    const char *argv[4] = { nullptr };\n\n    argv[0] = req.login ? \"-\" : req.shell.c_str();\n\n    if (!req.command.empty()) {\n        argv[1] = \"-c\";\n        argv[2] = req.command.c_str();\n    }\n\n    // Setup environment\n    umask(022);\n    char path[32];\n    ssprintf(path, sizeof(path), \"/proc/%d/cwd\", pid);\n    char cwd[4096];\n    if (canonical_path(path, cwd, sizeof(cwd)) > 0)\n        chdir(cwd);\n    ssprintf(path, sizeof(path), \"/proc/%d/environ\", pid);\n    auto env = full_read(path);\n    clearenv();\n    for (size_t pos = 0; pos < env.size(); ++pos) {\n        putenv(env.data() + pos);\n        pos = env.find_first_of('\\0', pos);\n        if (pos == std::string::npos)\n            break;\n    }\n    if (!req.keep_env) {\n        struct passwd *pw;\n        pw = getpwuid(req.target_uid);\n        if (pw) {\n            setenv(\"HOME\", pw->pw_dir, 1);\n            setenv(\"USER\", pw->pw_name, 1);\n            setenv(\"LOGNAME\", pw->pw_name, 1);\n            setenv(\"SHELL\", req.shell.c_str(), 1);\n        }\n    }\n\n    // Config privileges\n    if (!req.context.empty()) {\n        auto f = xopen_file(\"/proc/self/attr/exec\", \"we\");\n        if (f) fprintf(f.get(), \"%s\", req.context.c_str());\n    }\n    if (req.drop_cap || proc_is_restricted(pid))\n        drop_caps();\n    if (req.target_uid != AID_ROOT || req.gids.size() > 0)\n        set_identity(req.target_uid, req.gids);\n\n    // Unblock all signals\n    sigset_t block_set;\n    sigemptyset(&block_set);\n    sigprocmask(SIG_SETMASK, &block_set, nullptr);\n\n    execvp(req.shell.c_str(), (char **) argv);\n    fprintf(stderr, \"Cannot execute %s: %s\\n\", req.shell.c_str(), strerror(errno));\n    PLOGE(\"exec\");\n}\n"
  },
  {
    "path": "native/src/core/thread.rs",
    "content": "use base::{ResultExt, new_daemon_thread};\nuse nix::sys::signal::SigSet;\nuse nix::unistd::{getpid, gettid};\nuse std::sync::LazyLock;\nuse std::sync::nonpoison::{Condvar, Mutex};\nuse std::time::Duration;\n\nstatic THREAD_POOL: LazyLock<ThreadPool> = LazyLock::new(ThreadPool::default);\n\nconst THREAD_IDLE_MAX_SEC: u64 = 60;\nconst CORE_POOL_SIZE: i32 = 3;\n\n#[derive(Default)]\npub struct ThreadPool {\n    task_is_some: Condvar,\n    task_is_none: Condvar,\n    info: Mutex<PoolInfo>,\n}\n\n#[derive(Default)]\nstruct PoolInfo {\n    idle_threads: i32,\n    total_threads: i32,\n    task: Option<Box<dyn FnOnce() + Send>>,\n}\n\nimpl ThreadPool {\n    fn pool_loop(&self, is_core_pool: bool) {\n        let mask = SigSet::all();\n\n        loop {\n            // Always restore the sigmask to block all signals\n            mask.thread_set_mask().log_ok();\n\n            let task: Option<Box<dyn FnOnce() + Send>>;\n            {\n                let mut info = self.info.lock();\n                info.idle_threads += 1;\n                if info.task.is_none() {\n                    if is_core_pool {\n                        // Core pool never closes, wait forever.\n                        self.task_is_some.wait(&mut info);\n                    } else {\n                        let dur = Duration::from_secs(THREAD_IDLE_MAX_SEC);\n                        if self.task_is_some.wait_timeout(&mut info, dur).timed_out() {\n                            // Terminate thread after timeout\n                            info.idle_threads -= 1;\n                            info.total_threads -= 1;\n                            return;\n                        }\n                    }\n                }\n                task = info.task.take();\n                self.task_is_none.notify_one();\n                info.idle_threads -= 1;\n            }\n            if let Some(task) = task {\n                task();\n            }\n            if getpid() == gettid() {\n                // This meant the current thread forked and became the main thread, exit\n                std::process::exit(0);\n            }\n        }\n    }\n\n    fn exec_task_impl(&self, f: impl FnOnce() + Send + 'static) {\n        extern \"C\" fn pool_loop_raw(arg: usize) -> usize {\n            let is_core_pool = arg != 0;\n            THREAD_POOL.pool_loop(is_core_pool);\n            0\n        }\n\n        let mut info = self.info.lock();\n        while info.task.is_some() {\n            // Wait until task is none\n            self.task_is_none.wait(&mut info);\n        }\n        info.task = Some(Box::new(f));\n        if info.idle_threads == 0 {\n            info.total_threads += 1;\n            let is_core_thread = if info.total_threads <= CORE_POOL_SIZE {\n                1_usize\n            } else {\n                0_usize\n            };\n            unsafe {\n                new_daemon_thread(pool_loop_raw, is_core_thread);\n            }\n        } else {\n            self.task_is_some.notify_one();\n        }\n    }\n\n    pub fn exec_task(f: impl FnOnce() + Send + 'static) {\n        THREAD_POOL.exec_task_impl(f);\n    }\n}\n"
  },
  {
    "path": "native/src/core/utils.cpp",
    "content": "#include <csignal>\n#include <libgen.h>\n#include <sys/mount.h>\n#include <sys/sysmacros.h>\n#include <linux/input.h>\n#include <map>\n\n#include <consts.hpp>\n#include <base.hpp>\n#include <core.hpp>\n\nusing namespace std;\n\nbool read_string(int fd, std::string &str) {\n    str.clear();\n    int len = read_int(fd);\n    str.resize(len);\n    return xxread(fd, str.data(), len) == len;\n}\n\nstring read_string(int fd) {\n    string str;\n    read_string(fd, str);\n    return str;\n}\n\nvoid write_string(int fd, string_view str) {\n    if (fd < 0) return;\n    write_int(fd, str.size());\n    xwrite(fd, str.data(), str.size());\n}\n\nconst char *get_magisk_tmp() {\n    static const char *path = nullptr;\n    if (path == nullptr) {\n        if (access(\"/debug_ramdisk/\" INTLROOT, F_OK) == 0) {\n            path = \"/debug_ramdisk\";\n        } else if (access(\"/sbin/\" INTLROOT, F_OK) == 0) {\n            path = \"/sbin\";\n        } else {\n            path = \"\";\n        }\n    }\n    return path;\n}\n\nvoid unlock_blocks() {\n    int fd, dev, OFF = 0;\n\n    auto dir = xopen_dir(\"/dev/block\");\n    if (!dir)\n        return;\n    dev = dirfd(dir.get());\n\n    for (dirent *entry; (entry = readdir(dir.get()));) {\n        if (entry->d_type == DT_BLK) {\n            if ((fd = openat(dev, entry->d_name, O_RDONLY | O_CLOEXEC)) < 0)\n                continue;\n            if (ioctl(fd, BLKROSET, &OFF) < 0)\n                PLOGE(\"unlock %s\", entry->d_name);\n            close(fd);\n        }\n    }\n}\n\n#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))\n\nbool check_key_combo() {\n    uint8_t bitmask[(KEY_MAX + 1) / 8];\n    vector<owned_fd> events;\n    constexpr char name[] = \"/dev/.ev\";\n\n    // First collect candidate events that accepts volume down\n    for (int minor = 64; minor < 96; ++minor) {\n        if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))\n            continue;\n        int fd = open(name, O_RDONLY | O_CLOEXEC);\n        unlink(name);\n        if (fd < 0)\n            continue;\n        memset(bitmask, 0, sizeof(bitmask));\n        ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);\n        if (test_bit(KEY_VOLUMEDOWN, bitmask))\n            events.emplace_back(fd);\n        else\n            close(fd);\n    }\n    if (events.empty())\n        return false;\n\n    // Check if volume down key is held continuously for more than 3 seconds\n    for (int i = 0; i < 300; ++i) {\n        bool pressed = false;\n        for (int fd : events) {\n            memset(bitmask, 0, sizeof(bitmask));\n            ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);\n            if (test_bit(KEY_VOLUMEDOWN, bitmask)) {\n                pressed = true;\n                break;\n            }\n        }\n        if (!pressed)\n            return false;\n        // Check every 10ms\n        usleep(10000);\n    }\n    LOGD(\"KEY_VOLUMEDOWN detected: enter safe mode\\n\");\n    return true;\n}\n"
  },
  {
    "path": "native/src/core/zygisk/api.hpp",
    "content": "/* Copyright 2022-2023 John \"topjohnwu\" Wu\n *\n * Permission to use, copy, modify, and/or distribute this software for any\n * purpose with or without fee is hereby granted.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n * PERFORMANCE OF THIS SOFTWARE.\n */\n\n// This is the public API for Zygisk modules.\n// DO NOT MODIFY ANY CODE IN THIS HEADER.\n\n// WARNING: this file may contain changes that are not finalized.\n// Always use the following published header for development:\n// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp\n\n#pragma once\n\n#include <jni.h>\n\n#define ZYGISK_API_VERSION 5\n\n/*\n\n***************\n* Introduction\n***************\n\nOn Android, all app processes are forked from a special daemon called \"Zygote\".\nFor each new app process, zygote will fork a new process and perform \"specialization\".\nThis specialization operation enforces the Android security sandbox on the newly forked\nprocess to make sure that 3rd party application code is only loaded after it is being\nrestricted within a sandbox.\n\nOn Android, there is also this special process called \"system_server\". This single\nprocess hosts a significant portion of system services, which controls how the\nAndroid operating system and apps interact with each other.\n\nThe Zygisk framework provides a way to allow developers to build modules and run custom\ncode before and after system_server and any app processes' specialization.\nThis enable developers to inject code and alter the behavior of system_server and app processes.\n\nPlease note that modules will only be loaded after zygote has forked the child process.\nTHIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!\n\n*********************\n* Development Guide\n*********************\n\nDefine a class and inherit zygisk::ModuleBase to implement the functionality of your module.\nUse the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.\n\nExample code:\n\nstatic jint (*orig_logger_entry_max)(JNIEnv *env);\nstatic jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }\n\nclass ExampleModule : public zygisk::ModuleBase {\npublic:\n    void onLoad(zygisk::Api *api, JNIEnv *env) override {\n        this->api = api;\n        this->env = env;\n    }\n    void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {\n        JNINativeMethod methods[] = {\n            { \"logger_entry_max_payload_native\", \"()I\", (void*) my_logger_entry_max },\n        };\n        api->hookJniNativeMethods(env, \"android/util/Log\", methods, 1);\n        *(void **) &orig_logger_entry_max = methods[0].fnPtr;\n    }\nprivate:\n    zygisk::Api *api;\n    JNIEnv *env;\n};\n\nREGISTER_ZYGISK_MODULE(ExampleModule)\n\n-----------------------------------------------------------------------------------------\n\nSince your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,\nor runs in the sandbox of the target process in post[XXX]Specialize, the code in your class\nnever runs in a true superuser environment.\n\nIf your module require access to superuser permissions, you can create and register\na root companion handler function. This function runs in a separate root companion\ndaemon process, and an Unix domain socket is provided to allow you to perform IPC between\nyour target process and the root companion process.\n\nExample code:\n\nstatic void example_handler(int socket) { ... }\n\nREGISTER_ZYGISK_COMPANION(example_handler)\n\n*/\n\nnamespace zygisk {\n\nstruct Api;\nstruct AppSpecializeArgs;\nstruct ServerSpecializeArgs;\n\nclass ModuleBase {\npublic:\n\n    // This method is called as soon as the module is loaded into the target process.\n    // A Zygisk API handle will be passed as an argument.\n    virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}\n\n    // This method is called before the app process is specialized.\n    // At this point, the process just got forked from zygote, but no app specific specialization\n    // is applied. This means that the process does not have any sandbox restrictions and\n    // still runs with the same privilege of zygote.\n    //\n    // All the arguments that will be sent and used for app specialization is passed as a single\n    // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app\n    // process will be specialized.\n    //\n    // If you need to run some operations as superuser, you can call Api::connectCompanion() to\n    // get a socket to do IPC calls with a root companion process.\n    // See Api::connectCompanion() for more info.\n    virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}\n\n    // This method is called after the app process is specialized.\n    // At this point, the process has all sandbox restrictions enabled for this application.\n    // This means that this method runs with the same privilege of the app's own code.\n    virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}\n\n    // This method is called before the system server process is specialized.\n    // See preAppSpecialize(args) for more info.\n    virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}\n\n    // This method is called after the system server process is specialized.\n    // At this point, the process runs with the privilege of system_server.\n    virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}\n};\n\nstruct AppSpecializeArgs {\n    // Required arguments. These arguments are guaranteed to exist on all Android versions.\n    jint &uid;\n    jint &gid;\n    jintArray &gids;\n    jint &runtime_flags;\n    jobjectArray &rlimits;\n    jint &mount_external;\n    jstring &se_info;\n    jstring &nice_name;\n    jstring &instruction_set;\n    jstring &app_data_dir;\n\n    // Optional arguments. Please check whether the pointer is null before de-referencing\n    jintArray *const fds_to_ignore;\n    jboolean *const is_child_zygote;\n    jboolean *const is_top_app;\n    jobjectArray *const pkg_data_info_list;\n    jobjectArray *const whitelisted_data_info_list;\n    jboolean *const mount_data_dirs;\n    jboolean *const mount_storage_dirs;\n    jboolean *const mount_sysprop_overrides;\n\n    AppSpecializeArgs() = delete;\n};\n\nstruct ServerSpecializeArgs {\n    jint &uid;\n    jint &gid;\n    jintArray &gids;\n    jint &runtime_flags;\n    jlong &permitted_capabilities;\n    jlong &effective_capabilities;\n\n    ServerSpecializeArgs() = delete;\n};\n\nnamespace internal {\nstruct api_table;\ntemplate <class T> void entry_impl(api_table *, JNIEnv *);\n}\n\n// These values are used in Api::setOption(Option)\nenum Option : int {\n    // Force Magisk's denylist unmount routines to run on this process.\n    //\n    // Setting this option only makes sense in preAppSpecialize.\n    // The actual unmounting happens during app process specialization.\n    //\n    // Set this option to force all Magisk and modules' files to be unmounted from the\n    // mount namespace of the process, regardless of the denylist enforcement status.\n    FORCE_DENYLIST_UNMOUNT = 0,\n\n    // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.\n    // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.\n    // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.\n    DLCLOSE_MODULE_LIBRARY = 1,\n};\n\n// Bit masks of the return value of Api::getFlags()\nenum StateFlag : uint32_t {\n    // The user has granted root access to the current process\n    PROCESS_GRANTED_ROOT = (1u << 0),\n\n    // The current process was added on the denylist\n    PROCESS_ON_DENYLIST = (1u << 1),\n};\n\n// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded\n// from the specialized process afterwards.\nstruct Api {\n\n    // Connect to a root companion process and get a Unix domain socket for IPC.\n    //\n    // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.\n    //\n    // The pre[XXX]Specialize methods run with the same privilege of zygote.\n    // If you would like to do some operations with superuser permissions, register a handler\n    // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).\n    // Another good use case for a companion process is that if you want to share some resources\n    // across multiple processes, hold the resources in the companion process and pass it over.\n    //\n    // The root companion process is ABI aware; that is, when calling this method from a 32-bit\n    // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.\n    //\n    // Returns a file descriptor to a socket that is connected to the socket passed to your\n    // module's companion request handler. Returns -1 if the connection attempt failed.\n    int connectCompanion();\n\n    // Get the file descriptor of the root folder of the current module.\n    //\n    // This API only works in the pre[XXX]Specialize methods.\n    // Accessing the directory returned is only possible in the pre[XXX]Specialize methods\n    // or in the root companion process (assuming that you sent the fd over the socket).\n    // Both restrictions are due to SELinux and UID.\n    //\n    // Module should also make sure zygote is allowed to read module dir (e.g. module dir has\n    // system_file context) due to SELinux restrictions on socket messages.\n    //\n    // Returns -1 if errors occurred.\n    int getModuleDir();\n\n    // Set various options for your module.\n    // Please note that this method accepts one single option at a time.\n    // Check zygisk::Option for the full list of options available.\n    void setOption(Option opt);\n\n    // Get information about the current process.\n    // Returns bitwise-or'd zygisk::StateFlag values.\n    uint32_t getFlags();\n\n    // Exempt the provided file descriptor from being automatically closed.\n    //\n    // This API only make sense in preAppSpecialize; calling this method in any other situation\n    // is either a no-op (returns true) or an error (returns false).\n    //\n    // When false is returned, the provided file descriptor will eventually be closed by zygote.\n    bool exemptFd(int fd);\n\n    // Hook JNI native methods for a class\n    //\n    // Lookup all registered JNI native methods and replace it with your own methods.\n    // The original function pointer will be saved in each JNINativeMethod's fnPtr.\n    // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr\n    // will be set to nullptr.\n    void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);\n\n    // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.\n    //\n    // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:\n    //\n    //       <address>       <perms>  <offset>   <dev>  <inode>           <pathname>\n    // 56b4346000-56b4347000  r-xp    00002000   fe:00    235       /system/bin/app_process64\n    // (More details: https://man7.org/linux/man-pages/man5/proc.5.html)\n    //\n    // The `dev` and `inode` pair uniquely identifies a file being mapped into memory.\n    // For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.\n    // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.\n    void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);\n\n    // Commit all the hooks that was previously registered.\n    // Returns false if an error occurred.\n    bool pltHookCommit();\n\nprivate:\n    internal::api_table *tbl;\n    template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);\n};\n\n// Register a class as a Zygisk module\n\n#define REGISTER_ZYGISK_MODULE(clazz) \\\nvoid zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \\\n    zygisk::internal::entry_impl<clazz>(table, env);                        \\\n}\n\n// Register a root companion request handler function for your module\n//\n// The function runs in a superuser daemon process and handles a root companion request from\n// your module running in a target process. The function has to accept an integer value,\n// which is a Unix domain socket that is connected to the target process.\n// See Api::connectCompanion() for more info.\n//\n// NOTE: the function can run concurrently on multiple threads.\n// Be aware of race conditions if you have globally shared resources.\n\n#define REGISTER_ZYGISK_COMPANION(func) \\\nvoid zygisk_companion_entry(int client) { func(client); }\n\n/*********************************************************\n * The following is internal ABI implementation detail.\n * You do not have to understand what it is doing.\n *********************************************************/\n\nnamespace internal {\n\nstruct module_abi {\n    long api_version;\n    ModuleBase *impl;\n\n    void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);\n    void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);\n    void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);\n    void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);\n\n    module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {\n        preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };\n        postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };\n        preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };\n        postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };\n    }\n};\n\nstruct api_table {\n    // Base\n    void *impl;\n    bool (*registerModule)(api_table *, module_abi *);\n\n    void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);\n    void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);\n    bool (*exemptFd)(int);\n    bool (*pltHookCommit)();\n    int  (*connectCompanion)(void * /* impl */);\n    void (*setOption)(void * /* impl */, Option);\n    int  (*getModuleDir)(void * /* impl */);\n    uint32_t (*getFlags)(void * /* impl */);\n};\n\ntemplate <class T>\nvoid entry_impl(api_table *table, JNIEnv *env) {\n    static Api api;\n    api.tbl = table;\n    static T module;\n    ModuleBase *m = &module;\n    static module_abi abi(m);\n    if (!table->registerModule(table, &abi)) return;\n    m->onLoad(&api, env);\n}\n\n} // namespace internal\n\ninline int Api::connectCompanion() {\n    return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;\n}\ninline int Api::getModuleDir() {\n    return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;\n}\ninline void Api::setOption(Option opt) {\n    if (tbl->setOption) tbl->setOption(tbl->impl, opt);\n}\ninline uint32_t Api::getFlags() {\n    return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;\n}\ninline bool Api::exemptFd(int fd) {\n    return tbl->exemptFd != nullptr && tbl->exemptFd(fd);\n}\ninline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {\n    if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);\n}\ninline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {\n    if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);\n}\ninline bool Api::pltHookCommit() {\n    return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();\n}\n\n} // namespace zygisk\n\nextern \"C\" {\n\n[[gnu::visibility(\"default\"), maybe_unused]]\nvoid zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);\n\n[[gnu::visibility(\"default\"), maybe_unused]]\nvoid zygisk_companion_entry(int);\n\n} // extern \"C\"\n"
  },
  {
    "path": "native/src/core/zygisk/daemon.rs",
    "content": "use crate::consts::MODULEROOT;\nuse crate::daemon::{MagiskD, to_user_id};\nuse crate::ffi::{ZygiskRequest, ZygiskStateFlags, get_magisk_tmp, update_deny_flags};\nuse crate::resetprop::{get_prop, set_prop};\nuse crate::socket::{IpcRead, UnixSocketExt};\nuse base::libc::STDOUT_FILENO;\nuse base::{\n    Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, WriteExt, cstr, fork_dont_care,\n    libc, log_err, raw_cstr, warn,\n};\nuse nix::fcntl::OFlag;\nuse std::fmt::Write;\nuse std::os::fd::{AsRawFd, RawFd};\nuse std::os::unix::net::UnixStream;\nuse std::ptr;\nuse std::sync::atomic::Ordering;\n\nconst NBPROP: &Utf8CStr = cstr!(\"ro.dalvik.vm.native.bridge\");\nconst ZYGISKLDR: &str = \"libzygisk.so\";\nconst UNMOUNT_MASK: u32 =\n    ZygiskStateFlags::ProcessOnDenyList.repr | ZygiskStateFlags::DenyListEnforced.repr;\n\npub fn zygisk_should_load_module(flags: u32) -> bool {\n    flags & UNMOUNT_MASK != UNMOUNT_MASK && flags & ZygiskStateFlags::ProcessIsMagiskApp.repr == 0\n}\n\n#[allow(unused_variables)]\nfn exec_zygiskd(is_64_bit: bool, remote: UnixStream) {\n    // This fd has to survive exec\n    unsafe {\n        libc::fcntl(remote.as_raw_fd(), libc::F_SETFD, 0);\n    }\n\n    // Start building the exec arguments\n\n    #[cfg(target_pointer_width = \"64\")]\n    let magisk = if is_64_bit { \"magisk\" } else { \"magisk32\" };\n\n    #[cfg(target_pointer_width = \"32\")]\n    let magisk = \"magisk\";\n\n    let exe = cstr::buf::new::<64>()\n        .join_path(get_magisk_tmp())\n        .join_path(magisk);\n\n    let mut fd_str = cstr::buf::new::<16>();\n    write!(fd_str, \"{}\", remote.as_raw_fd()).ok();\n    unsafe {\n        libc::execl(\n            exe.as_ptr(),\n            raw_cstr!(\"\"),\n            raw_cstr!(\"zygisk\"),\n            raw_cstr!(\"companion\"),\n            fd_str.as_ptr(),\n            ptr::null() as *const libc::c_char,\n        );\n        libc::exit(-1);\n    }\n}\n\n#[derive(Default)]\npub struct ZygiskState {\n    pub lib_name: String,\n    sockets: (Option<UnixStream>, Option<UnixStream>),\n    start_count: u32 = 1,\n}\n\nimpl ZygiskState {\n    fn connect_zygiskd(&mut self, mut client: UnixStream, daemon: &MagiskD) -> LoggedResult<()> {\n        let is_64_bit: bool = client.read_decodable()?;\n        let socket = if is_64_bit {\n            &mut self.sockets.1\n        } else {\n            &mut self.sockets.0\n        };\n\n        if let Some(fd) = socket {\n            // Make sure the socket is still valid\n            let mut pfd = libc::pollfd {\n                fd: fd.as_raw_fd(),\n                events: 0,\n                revents: 0,\n            };\n            if unsafe { libc::poll(&mut pfd, 1, 0) } != 0 || pfd.revents != 0 {\n                // Any revent means error\n                *socket = None;\n            }\n        }\n\n        if let Some(fd) = socket {\n            fd.send_fds(&[client.as_raw_fd()])?;\n        } else {\n            // Create a new socket pair and fork zygiskd process\n            let (mut local, remote) = UnixStream::pair()?;\n            if fork_dont_care() == 0 {\n                exec_zygiskd(is_64_bit, remote);\n            }\n            if let Some(module_fds) = daemon.get_module_fds(is_64_bit) {\n                local.send_fds(&module_fds)?;\n            }\n            if local.read_decodable::<i32>()? != 0 {\n                return log_err!();\n            }\n            local.send_fds(&[client.as_raw_fd()])?;\n            *socket = Some(local);\n        }\n        Ok(())\n    }\n\n    pub fn reset(&mut self, mut restore: bool) {\n        if restore {\n            self.start_count = 1;\n        } else {\n            self.sockets = (None, None);\n            self.start_count += 1;\n            if self.start_count > 3 {\n                warn!(\"zygote crashed too many times, rolling-back\");\n                restore = true;\n            }\n        }\n\n        if restore {\n            self.restore_prop();\n        } else {\n            self.set_prop();\n        }\n    }\n\n    pub fn set_prop(&mut self) {\n        if !self.lib_name.is_empty() {\n            return;\n        }\n        let orig = get_prop(NBPROP);\n        self.lib_name = if orig.is_empty() || orig == \"0\" {\n            ZYGISKLDR.to_string()\n        } else {\n            ZYGISKLDR.to_string() + &orig\n        };\n        set_prop(NBPROP, Utf8CStr::from_string(&mut self.lib_name));\n        // Whether Huawei's Maple compiler is enabled.\n        // If so, system server will be created by a special Zygote which ignores the native bridge\n        // and make system server out of our control. Avoid it by disabling.\n        if get_prop(cstr!(\"ro.maple.enable\")) == \"1\" {\n            set_prop(cstr!(\"ro.maple.enable\"), cstr!(\"0\"));\n        }\n    }\n\n    pub fn restore_prop(&mut self) {\n        let mut orig = \"0\".to_string();\n        if self.lib_name.len() > ZYGISKLDR.len() {\n            orig = self.lib_name[ZYGISKLDR.len()..].to_string();\n        }\n        set_prop(NBPROP, Utf8CStr::from_string(&mut orig));\n        self.lib_name.clear();\n    }\n}\n\nimpl MagiskD {\n    pub fn zygisk_handler(&self, mut client: UnixStream) {\n        let _ = || -> LoggedResult<()> {\n            let code = ZygiskRequest {\n                repr: client.read_decodable()?,\n            };\n            match code {\n                ZygiskRequest::GetInfo => self.get_process_info(client)?,\n                ZygiskRequest::ConnectCompanion => self\n                    .zygisk\n                    .lock()\n                    .connect_zygiskd(client, self)\n                    .log_with_msg(|w| w.write_str(\"zygiskd startup error\"))?,\n                ZygiskRequest::GetModDir => self.get_mod_dir(client)?,\n                _ => {}\n            }\n            Ok(())\n        }();\n    }\n\n    fn get_module_fds(&self, is_64_bit: bool) -> Option<Vec<RawFd>> {\n        self.module_list.get().map(|module_list| {\n            module_list\n                .iter()\n                .map(|m| if is_64_bit { m.z64 } else { m.z32 })\n                // All fds passed over sockets have to be valid file descriptors.\n                // To work around this issue, send over STDOUT_FILENO as an indicator of an\n                // invalid fd as it will always be /dev/null in magiskd.\n                .map(|fd| if fd < 0 { STDOUT_FILENO } else { fd })\n                .collect()\n        })\n    }\n\n    fn get_process_info(&self, mut client: UnixStream) -> LoggedResult<()> {\n        let uid: i32 = client.read_decodable()?;\n        let process: String = client.read_decodable()?;\n        let is_64_bit: bool = client.read_decodable()?;\n        let mut flags: u32 = 0;\n        update_deny_flags(uid, &process, &mut flags);\n        if self.get_manager_uid(to_user_id(uid)) == uid {\n            flags |= ZygiskStateFlags::ProcessIsMagiskApp.repr\n        }\n        if self.uid_granted_root(uid) {\n            flags |= ZygiskStateFlags::ProcessGrantedRoot.repr\n        }\n\n        // First send flags\n        client.write_pod(&flags)?;\n\n        // Next send modules\n        if zygisk_should_load_module(flags)\n            && let Some(module_fds) = self.get_module_fds(is_64_bit)\n        {\n            client.send_fds(&module_fds)?;\n        }\n\n        // If we're not in system_server, we are done\n        if uid != 1000 || process != \"system_server\" {\n            return Ok(());\n        }\n\n        // Read all failed modules\n        let failed_ids: Vec<i32> = client.read_decodable()?;\n        if let Some(module_list) = self.module_list.get() {\n            for id in failed_ids {\n                let Some(module) = module_list.get(id as usize) else {\n                    continue;\n                };\n                let path = cstr::buf::default()\n                    .join_path(MODULEROOT)\n                    .join_path(&module.name)\n                    .join_path(\"zygisk\");\n                // Create the unloaded marker file\n                if let Ok(dir) = Directory::open(&path) {\n                    dir.open_as_file_at(cstr!(\"unloaded\"), OFlag::O_CREAT | OFlag::O_RDONLY, 0o644)\n                        .log()\n                        .ok();\n                }\n            }\n        }\n\n        Ok(())\n    }\n\n    fn get_mod_dir(&self, mut client: UnixStream) -> LoggedResult<()> {\n        let id: i32 = client.read_decodable()?;\n        let Some(module) = self\n            .module_list\n            .get()\n            .and_then(|list| list.get(id as usize))\n        else {\n            return Ok(());\n        };\n        let dir = cstr::buf::default()\n            .join_path(MODULEROOT)\n            .join_path(&module.name);\n        let fd = dir.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;\n        client.send_fds(&[fd.as_raw_fd()])?;\n        Ok(())\n    }\n}\n\n// FFI to C++\nimpl MagiskD {\n    pub fn zygisk_enabled(&self) -> bool {\n        self.zygisk_enabled.load(Ordering::Acquire)\n    }\n}\n"
  },
  {
    "path": "native/src/core/zygisk/entry.cpp",
    "content": "#include <sys/mount.h>\n#include <android/dlext.h>\n#include <dlfcn.h>\n#include <poll.h>\n\n#include <base.hpp>\n#include <core.hpp>\n\n#include \"zygisk.hpp\"\n\nusing namespace std;\n\nusing comp_entry = void(*)(int);\nextern \"C\" void exec_companion_entry(int, comp_entry);\n\nstatic void zygiskd(int socket) {\n    if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)\n        exit(-1);\n\n#if defined(__LP64__)\n    set_nice_name(\"zygiskd64\");\n    LOGI(\"* Launching zygiskd64\\n\");\n#else\n    set_nice_name(\"zygiskd32\");\n    LOGI(\"* Launching zygiskd32\\n\");\n#endif\n\n    // Load modules\n    vector<comp_entry> modules;\n    {\n        auto module_fds = recv_fds(socket);\n        for (int fd : module_fds) {\n            comp_entry entry = nullptr;\n            struct stat s{};\n            if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) {\n                android_dlextinfo info {\n                    .flags = ANDROID_DLEXT_USE_LIBRARY_FD,\n                    .library_fd = fd,\n                };\n                if (void *h = android_dlopen_ext(\"/jit-cache\", RTLD_LAZY, &info)) {\n                    *(void **) &entry = dlsym(h, \"zygisk_companion_entry\");\n                } else {\n                    LOGW(\"Failed to dlopen zygisk module: %s\\n\", dlerror());\n                }\n            }\n            modules.push_back(entry);\n            close(fd);\n        }\n    }\n\n    // ack\n    write_int(socket, 0);\n\n    // Start accepting requests\n    pollfd pfd = { socket, POLLIN, 0 };\n    for (;;) {\n        poll(&pfd, 1, -1);\n        if (pfd.revents && !(pfd.revents & POLLIN)) {\n            // Something bad happened in magiskd, terminate zygiskd\n            exit(0);\n        }\n        int client = recv_fd(socket);\n        if (client < 0) {\n            // Something bad happened in magiskd, terminate zygiskd\n            exit(0);\n        }\n        int module_id = read_int(client);\n        if (module_id >= 0 && module_id < modules.size() && modules[module_id]) {\n            exec_companion_entry(client, modules[module_id]);\n        } else {\n            close(client);\n        }\n    }\n}\n\n// Entrypoint where we need to re-exec ourselves\n// This should only ever be called internally\nint zygisk_main(int argc, char *argv[]) {\n    android_logging();\n    if (argc == 3 && argv[1] == \"companion\"sv) {\n        zygiskd(parse_int(argv[2]));\n    }\n    return 0;\n}\n\n// Entrypoint of code injection\nextern \"C\" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf {\n    .version = 2,\n    .padding = {},\n    .isCompatibleWith = [](auto) {\n        zygisk_logging();\n        hook_entry();\n        ZLOGD(\"load success\\n\");\n        return false;\n    },\n};\n"
  },
  {
    "path": "native/src/core/zygisk/gen_jni_hooks.py",
    "content": "#!/usr/bin/env python3\n\nprimitives = [\"jint\", \"jboolean\", \"jlong\"]\n\n\nclass JType:\n    def __init__(self, cpp: str, jni: str):\n        self.cpp = cpp\n        self.jni = jni\n\n\nclass JArray(JType):\n    def __init__(self, type: JType):\n        if type.cpp in primitives:\n            name = type.cpp + \"Array\"\n        else:\n            name = \"jobjectArray\"\n        super().__init__(name, \"[\" + type.jni)\n\n\nclass Argument:\n    def __init__(self, name: str, type: JType, set_arg=False):\n        self.name = name\n        self.type = type\n        self.set_arg = set_arg\n\n    def cpp(self) -> str:\n        return f\"{self.type.cpp} {self.name}\"\n\n\n# Args we don't care, give it an auto generated name\nclass Anon(Argument):\n    cnt = 0\n\n    def __init__(self, type: JType):\n        super().__init__(f\"_{Anon.cnt}\", type)\n        Anon.cnt += 1\n\n\nclass Return:\n    def __init__(self, value: str, type: JType):\n        self.value = value\n        self.type = type\n\n\nclass JNIMethod:\n    def __init__(self, name: str, ret: Return, args: list[Argument]):\n        self.name = name\n        self.ret = ret\n        self.args = args\n\n    def arg_list_name(self) -> str:\n        return \"env, clazz, \" + \", \".join(map(lambda x: x.name, self.args))\n\n    def arg_list_cpp(self) -> str:\n        return \"JNIEnv *env, jclass clazz, \" + \", \".join(\n            map(lambda x: x.cpp(), self.args)\n        )\n\n    def cpp_fn_type(self) -> str:\n        return f\"{self.ret.type.cpp}(*)({self.arg_list_cpp()}\"\n\n    def cpp_lambda_sig(self) -> str:\n        return f\"[] [[clang::no_stack_protector]] ({self.arg_list_cpp()}) static -> {self.ret.type.cpp}\"\n\n    def jni_sig(self):\n        args = \"\".join(map(lambda x: x.type.jni, self.args))\n        return f\"({args}){self.ret.type.jni}\"\n\n\nclass JNIHook(JNIMethod):\n    def __init__(self, ver: str, ret: Return, args: list[Argument]):\n        name = f\"{self.hook_target()}_{ver}\"\n        super().__init__(name, ret, args)\n\n    def hook_target(self):\n        return \"\"\n\n    def body(self, orig_fn_ptr: str):\n        return \"\"\n\n\ndef ind(i):\n    return \"\\n\" + \"    \" * i\n\n\n# Common types\njint = JType(\"jint\", \"I\")\njintArray = JArray(jint)\njstring = JType(\"jstring\", \"Ljava/lang/String;\")\njboolean = JType(\"jboolean\", \"Z\")\njlong = JType(\"jlong\", \"J\")\nvoid = JType(\"void\", \"V\")\n\n\nclass ForkApp(JNIHook):\n    def __init__(self, ver, args):\n        super().__init__(ver, Return(\"ctx.pid\", jint), args)\n\n    def hook_target(self):\n        return \"nativeForkAndSpecialize\"\n\n    def init_args(self):\n        return \"AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\"\n\n    def body(self, orig_fn_ptr: str):\n        decl = \"\"\n        decl += ind(3) + self.init_args()\n        for a in self.args:\n            if a.set_arg:\n                decl += ind(3) + f\"args.{a.name} = &{a.name};\"\n        decl += ind(3) + \"ZygiskContext ctx(env, &args);\"\n        decl += ind(3) + f\"ctx.{self.hook_target()}_pre();\"\n        decl += ind(3) + f\"reinterpret_cast<{self.cpp_fn_type()})>({orig_fn_ptr})(\"\n        decl += ind(4) + self.arg_list_name()\n        decl += ind(3) + \");\"\n        decl += ind(3) + f\"ctx.{self.hook_target()}_post();\"\n        if self.ret.value:\n            decl += ind(3) + f\"return {self.ret.value};\"\n        return decl\n\n\nclass SpecializeApp(ForkApp):\n    def __init__(self, ver: str, args: list[Argument]):\n        super().__init__(ver, args)\n        self.ret = Return(\"\", void)\n\n    def hook_target(self):\n        return \"nativeSpecializeAppProcess\"\n\n\nclass ForkServer(ForkApp):\n    def hook_target(self):\n        return \"nativeForkSystemServer\"\n\n    def init_args(self):\n        return \"ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);\"\n\n\n# Common args\nuid = Argument(\"uid\", jint)\ngid = Argument(\"gid\", jint)\ngids = Argument(\"gids\", jintArray)\nruntime_flags = Argument(\"runtime_flags\", jint)\nrlimits = Argument(\"rlimits\", JArray(jintArray))\nmount_external = Argument(\"mount_external\", jint)\nse_info = Argument(\"se_info\", jstring)\nnice_name = Argument(\"nice_name\", jstring)\nfds_to_close = Argument(\"fds_to_close\", jintArray)\ninstruction_set = Argument(\"instruction_set\", jstring)\napp_data_dir = Argument(\"app_data_dir\", jstring)\n\n# o\nfds_to_ignore = Argument(\"fds_to_ignore\", jintArray, True)\n\n# p\nis_child_zygote = Argument(\"is_child_zygote\", jboolean, True)\n\n# q_alt\nis_top_app = Argument(\"is_top_app\", jboolean, True)\n\n# q running on xr\nis_perception_app = Argument(\"is_perception_app\", jboolean, False)\n\n# r\npkg_data_info_list = Argument(\"pkg_data_info_list\", JArray(jstring), True)\nwhitelisted_data_info_list = Argument(\n    \"whitelisted_data_info_list\", JArray(jstring), True\n)\nmount_data_dirs = Argument(\"mount_data_dirs\", jboolean, True)\nmount_storage_dirs = Argument(\"mount_storage_dirs\", jboolean, True)\n\n# u qpr2\nmount_sysprop_overrides = Argument(\"mount_sysprop_overrides\", jboolean, True)\n\n# b qpr2\nuse_fifo_ui = Argument(\"use_fifo_ui\", jboolean, False)\n\n# server\npermitted_capabilities = Argument(\"permitted_capabilities\", jlong)\neffective_capabilities = Argument(\"effective_capabilities\", jlong)\n\n# Method definitions\nfas_l = ForkApp(\n    \"l\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_o = ForkApp(\n    \"o\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_p = ForkApp(\n    \"p\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_q_alt = ForkApp(\n    \"q_alt\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n    ],\n)\n\nfas_r = ForkApp(\n    \"r\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n    ],\n)\n\nfas_u = ForkApp(\n    \"u\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n        mount_sysprop_overrides,\n    ],\n)\n\nfas_b = ForkApp(\n    \"b\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        use_fifo_ui,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n        mount_sysprop_overrides,\n    ],\n)\n\nfas_samsung_m = ForkApp(\n    \"samsung_m\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        Anon(jint),\n        Anon(jint),\n        nice_name,\n        fds_to_close,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_samsung_n = ForkApp(\n    \"samsung_n\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        Anon(jint),\n        Anon(jint),\n        nice_name,\n        fds_to_close,\n        instruction_set,\n        app_data_dir,\n        Anon(jint),\n    ],\n)\n\nfas_samsung_o = ForkApp(\n    \"samsung_o\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        Anon(jint),\n        Anon(jint),\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_samsung_p = ForkApp(\n    \"samsung_p\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        Anon(jint),\n        Anon(jint),\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nfas_nubia_u = ForkApp(\n    \"nubia_u\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        fds_to_close,\n        fds_to_ignore,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        use_fifo_ui,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n        mount_sysprop_overrides,\n        Anon(jstring),\n    ],\n)\n\nspec_q = SpecializeApp(\n    \"q\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nspec_q_alt = SpecializeApp(\n    \"q_alt\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n    ],\n)\n\nspec_r = SpecializeApp(\n    \"r\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n    ],\n)\n\nspec_u = SpecializeApp(\n    \"u\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n        mount_sysprop_overrides,\n    ],\n)\n\nspec_xr_u = SpecializeApp(\n    \"xr_u\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        is_perception_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n    ],\n)\n\nspec_samsung_q = SpecializeApp(\n    \"samsung_q\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        Anon(jint),\n        Anon(jint),\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n    ],\n)\n\nspec_nubia_u = SpecializeApp(\n    \"nubia_u\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        mount_external,\n        se_info,\n        nice_name,\n        is_child_zygote,\n        instruction_set,\n        app_data_dir,\n        is_top_app,\n        pkg_data_info_list,\n        whitelisted_data_info_list,\n        mount_data_dirs,\n        mount_storage_dirs,\n        mount_sysprop_overrides,\n        Anon(jstring),\n    ],\n)\n\nserver_l = ForkServer(\n    \"l\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        rlimits,\n        permitted_capabilities,\n        effective_capabilities,\n    ],\n)\n\nserver_samsung_q = ForkServer(\n    \"samsung_q\",\n    [\n        uid,\n        gid,\n        gids,\n        runtime_flags,\n        Anon(jint),\n        Anon(jint),\n        rlimits,\n        permitted_capabilities,\n        effective_capabilities,\n    ],\n)\n\n\ndef gen_jni_def(field: str, methods: list[JNIHook]):\n    decl = \"\"\n    decl += ind(0) + f\"std::array<JNINativeMethod, {len(methods)}> {field} = {{{{\"\n    for i, m in enumerate(methods):\n        decl += ind(1) + f\"// {m.name}\"\n        decl += ind(1) + \"{\"\n        decl += ind(2) + f'\"{m.hook_target()}\",'\n        decl += ind(2) + f'\"{m.jni_sig()}\",'\n        decl += ind(2) + f\"(void *) +{m.cpp_lambda_sig()} {{\"\n        orig_fn_ptr = f\"get_defs()->{field}[{i}].fnPtr\"\n        decl += m.body(orig_fn_ptr)\n        decl += ind(2) + \"}\"\n        decl += ind(1) + \"},\"\n    decl += ind(0) + \"}};\"\n    decl += ind(0)\n\n    return decl\n\n\nwith open(\"jni_hooks.hpp\", \"w\") as f:\n    f.write(\"// Generated by gen_jni_hooks.py\\n\")\n    f.write(\"#pragma once\\n\\n\")\n    f.write(\"struct JniHookDefinitions;\\n\")\n    f.write(\"static JniHookDefinitions *get_defs();\\n\\n\")\n    f.write(\"struct JniHookDefinitions {\\n\")\n    f.write(\n        gen_jni_def(\n            \"fork_app_methods\",\n            [\n                fas_l,\n                fas_o,\n                fas_p,\n                fas_q_alt,\n                fas_r,\n                fas_u,\n                fas_b,\n                fas_samsung_m,\n                fas_samsung_n,\n                fas_samsung_o,\n                fas_samsung_p,\n                fas_nubia_u,\n            ],\n        )\n    )\n\n    f.write(\n        gen_jni_def(\n            \"specialize_app_methods\",\n            [spec_q, spec_q_alt, spec_r, spec_u, spec_xr_u, spec_samsung_q, spec_nubia_u],\n        )\n    )\n\n    f.write(gen_jni_def(\"fork_server_methods\", [server_l, server_samsung_q]))\n\n    f.write(\"\\n};\\n\")\n"
  },
  {
    "path": "native/src/core/zygisk/hook.cpp",
    "content": "#include <sys/mman.h>\n#include <sys/mount.h>\n#include <sys/resource.h>\n#include <dlfcn.h>\n#include <unwind.h>\n#include <span>\n\n#include <lsplt.hpp>\n\n#include <base.hpp>\n\n#include \"zygisk.hpp\"\n#include \"module.hpp\"\n#include \"jni_hooks.hpp\"\n\nusing namespace std;\n\n// *********************\n// Zygisk Bootstrapping\n// *********************\n//\n// Zygisk's lifecycle is driven by several PLT function hooks in libandroid_runtime, libart, and\n// libnative_bridge. As Zygote is starting up, these carefully selected functions will call into\n// the respective lifecycle callbacks in Zygisk to drive the progress forward.\n//\n// The entire bootstrap process is shown in the graph below.\n// Arrows represent control flow, and the blocks are sorted chronologically from top to bottom.\n//\n// libnative_bridge       libandroid_runtime                zygisk                 libart\n//\n//                            ┌───────┐\n//                            │ start │\n//                            └───┬─┬─┘\n//                                │ │                                         ┌────────────────┐\n//                                │ └────────────────────────────────────────►│LoadNativeBridge│\n//                                │                                           └───────┬────────┘\n// ┌────────────────┐             │                                                   │\n// │LoadNativeBridge│◄────────────┼───────────────────────────────────────────────────┘\n// └───────┬────┬───┘             │\n//         │    │                 │                     ┌───────────────┐\n//         │    └─────────────────┼────────────────────►│NativeBridgeItf│\n//         │                      │                     └──────┬────────┘\n//         │                      │                            │\n//         │                      │                            ▼\n//         │                      │                        ┌────────┐\n//         │                      │                        │hook_plt│\n//         ▼                      │                        └────────┘\n//     ┌───────┐                  │\n//     │dlclose│                  │\n//     └───┬───┘                  │\n//         │                      │\n//         │                      │                 ┌───────────────────────┐\n//         └──────────────────────┼────────────────►│post_native_bridge_load│\n//                                │                 └───────────────────────┘\n//                                ▼\n//                    ┌──────────────────────┐\n//                    │ strdup(\"ZygoteInit\") │\n//                    └───────────┬────┬─────┘\n//                                │    │                ┌───────────────┐\n//                                │    └───────────────►│hook_zygote_jni│\n//                                │                     └───────────────┘       ┌─────────┐\n//                                │                                             │         │\n//                                └────────────────────────────────────────────►│   JVM   │\n//                                                                              │         │\n//                                                                              └──┬─┬────┘\n//                      ┌───────────────────┐                                      │ │\n//                      │nativeXXXSpecialize│◄─────────────────────────────────────┘ │\n//                      └─────────────┬─────┘                                        │\n//                                    │                 ┌─────────────┐              │\n//                                    └────────────────►│ZygiskContext│              │\n//                                                      └─────────────┘              ▼\n//                                                                         ┌────────────────────┐\n//                                                                         │pthread_attr_destroy│\n//                                                                         └─────────┬──────────┘\n//                                                     ┌────────────────┐            │\n//                                                     │restore_plt_hook│◄───────────┘\n//                                                     └────────────────┘\n//\n// Some notes regarding the important functions/symbols during bootstrap:\n//\n// * NativeBridgeItf: this symbol is the entry point for android::LoadNativeBridge\n// * HookContext::hook_plt(): hook functions like |dlclose| and |strdup|\n// * dlclose: the final step in android::LoadNativeBridge. In this function, we unwind the call\n//   stack to load the real native bridge if necessary, and fetch NativeBridgeRuntimeCallbacks.\n// * strdup: called in AndroidRuntime::start before calling ZygoteInit#main(...)\n// * HookContext::hook_zygote_jni(): replace the process specialization functions registered\n//   with register_jni_procs. This marks the final step of the code injection bootstrap process.\n// * pthread_attr_destroy: called whenever the JVM tries to setup threads for itself. We use\n//   this method to cleanup and unload Zygisk from the process.\n\nconstexpr const char *kZygoteInit = \"com.android.internal.os.ZygoteInit\";\nconstexpr const char *kZygote = \"com/android/internal/os/Zygote\";\nconstexpr const char *kForkApp = \"nativeForkAndSpecialize\";\nconstexpr const char *kSpecializeApp = \"nativeSpecializeAppProcess\";\nconstexpr const char *kForkServer = \"nativeForkSystemServer\";\n\nusing JNIMethods = std::span<JNINativeMethod>;\nusing JNIMethodsDyn = std::pair<unique_ptr<JNINativeMethod[]>, size_t>;\n\nstruct HookContext : JniHookDefinitions {\n\n    vector<tuple<dev_t, ino_t, const char *, void **>> plt_backup;\n    const NativeBridgeRuntimeCallbacks *runtime_callbacks = nullptr;\n    void *self_handle = nullptr;\n    bool should_unmap = false;\n\n    void hook_plt();\n    void hook_unloader();\n    void restore_plt_hook();\n    void hook_zygote_jni();\n    void restore_zygote_hook(JNIEnv *env);\n    void hook_jni_methods(JNIEnv *env, const char *clz, JNIMethods methods) const;\n    void post_native_bridge_load(void *handle);\n\nprivate:\n    void register_hook(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func);\n    int hook_jni_methods(JNIEnv *env, jclass clazz, JNIMethods methods) const;\n    JNIMethodsDyn get_jni_methods(JNIEnv *env, jclass clazz) const;\n};\n\n// -----------------------------------------------------------------\n\n// Global contexts:\n//\n// HookContext lives as long as Zygisk is loaded in memory. It tracks the process's function\n// hooking state and bootstraps code injection until we replace the process specialization methods.\n//\n// ZygiskContext lives during the process specialization process. It implements Zygisk\n// features, such as loading modules and customizing process fork/specialization.\n\nZygiskContext *g_ctx;\nstatic HookContext *g_hook;\n\nstatic JniHookDefinitions *get_defs() {\n    return g_hook;\n}\n\n// -----------------------------------------------------------------\n\n#define DCL_HOOK_FUNC(ret, func, ...) \\\nret (*old_##func)(__VA_ARGS__);       \\\nret new_##func(__VA_ARGS__)\n\nDCL_HOOK_FUNC(static char *, strdup, const char * str) {\n    if (strcmp(kZygoteInit, str) == 0) {\n        g_hook->hook_zygote_jni();\n    }\n    return old_strdup(str);\n}\n\n// Skip actual fork and return cached result if applicable\nDCL_HOOK_FUNC(int, fork) {\n    return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();\n}\n\n// Unmount stuffs in the process's private mount namespace\nDCL_HOOK_FUNC(static int, unshare, int flags) {\n    int res = old_unshare(flags);\n    if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0) {\n        if (g_ctx->flags & DO_REVERT_UNMOUNT) {\n            revert_unmount();\n        }\n        // Restore errno back to 0\n        errno = 0;\n    }\n    return res;\n}\n\n// This is the last moment before the secontext of the process changes\nDCL_HOOK_FUNC(static int, selinux_android_setcontext,\n              uid_t uid, bool isSystemServer, const char *seinfo, const char *pkgname) {\n    // Pre-fetch logd before secontext transition\n    zygisk_get_logd();\n    return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);\n}\n\n// Close file descriptors to prevent crashing\nDCL_HOOK_FUNC(static void, android_log_close) {\n    if (g_ctx == nullptr || !(g_ctx->flags & SKIP_CLOSE_LOG_PIPE)) {\n        // This happens during forks like nativeForkApp, nativeForkUsap,\n        // nativeForkSystemServer, and nativeForkAndSpecialize.\n        zygisk_close_logd();\n    }\n    old_android_log_close();\n}\n\n// It should be safe to assume all dlclose's in libnativebridge are for zygisk_loader\nDCL_HOOK_FUNC(static int, dlclose, void *handle) {\n    if (!g_hook->self_handle) {\n        ZLOGV(\"dlclose zygisk_loader\\n\");\n        g_hook->post_native_bridge_load(handle);\n    }\n    return 0;\n}\n\n// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,\n// it will return to our code which has been unmapped, causing segmentation fault.\n// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.\nDCL_HOOK_FUNC(static int, pthread_attr_destroy, void *target) {\n    int res = old_pthread_attr_destroy((pthread_attr_t *)target);\n\n    // Only perform unloading on the main thread\n    if (gettid() != getpid())\n        return res;\n\n    ZLOGV(\"pthread_attr_destroy\\n\");\n    if (g_hook->should_unmap) {\n        g_hook->restore_plt_hook();\n        if (g_hook->should_unmap) {\n            ZLOGV(\"dlclosing self\\n\");\n            void *self_handle = g_hook->self_handle;\n            delete g_hook;\n\n            // Because both `pthread_attr_destroy` and `dlclose` have the same function signature,\n            // we can use `musttail` to let the compiler reuse our stack frame and thus\n            // `dlclose` will directly return to the caller of `pthread_attr_destroy`.\n            [[clang::musttail]] return dlclose(self_handle);\n        }\n    }\n\n    delete g_hook;\n    return res;\n}\n\n#undef DCL_HOOK_FUNC\n\n// -----------------------------------------------------------------\n\nstatic size_t get_fd_max() {\n    rlimit r{32768, 32768};\n    getrlimit(RLIMIT_NOFILE, &r);\n    return r.rlim_max;\n}\n\nZygiskContext::ZygiskContext(JNIEnv *env, void *args) :\n    env(env), args{args}, process(nullptr), pid(-1), flags(0), info_flags(0),\n    allowed_fds(get_fd_max()), hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; }\n\nZygiskContext::~ZygiskContext() {\n    // This global pointer points to a variable on the stack.\n    // Set this to nullptr to prevent leaking local variable.\n    // This also disables most plt hooked functions.\n    g_ctx = nullptr;\n\n    if (!is_child())\n        return;\n\n    zygisk_close_logd();\n    android_logging();\n\n    // Strip out all API function pointers\n    for (auto &m : modules) {\n        m.clearApi();\n    }\n\n    // Cleanup\n    g_hook->should_unmap = true;\n    g_hook->restore_zygote_hook(env);\n    g_hook->hook_unloader();\n}\n\n// -----------------------------------------------------------------\n\ninline void *unwind_get_region_start(_Unwind_Context *ctx) {\n    auto fp = _Unwind_GetRegionStart(ctx);\n#if defined(__arm__)\n    // On arm32, we need to check if the pc is in thumb mode,\n    // if so, we need to set the lowest bit of fp to 1\n    auto pc = _Unwind_GetGR(ctx, 15); // r15 is pc\n    if (pc & 1) {\n        // Thumb mode\n        fp |= 1;\n    }\n#endif\n    return reinterpret_cast<void *>(fp);\n}\n\n// As we use NativeBridgeRuntimeCallbacks to reload native bridge and to hook jni functions,\n// we need to find it by the native bridge's unwind context.\n// For abis that use registers to pass arguments, i.e. arm32, arm64, x86_64, the registers are\n// caller-saved, and they are not preserved in the unwind context. However, they will be saved\n// into the callee-saved registers, so we will search the callee-saved registers for the second\n// argument, which is the pointer to NativeBridgeRuntimeCallbacks.\n// For x86, whose abi uses stack to pass arguments, we can directly get the pointer to\n// NativeBridgeRuntimeCallbacks from the stack.\nstatic const NativeBridgeRuntimeCallbacks* find_runtime_callbacks(struct _Unwind_Context *ctx) {\n    // Find the writable memory region of libart.so, where the NativeBridgeRuntimeCallbacks is located.\n    auto [start, end] = []()-> tuple<uintptr_t, uintptr_t> {\n        for (const auto &map : lsplt::MapInfo::Scan()) {\n            if (map.path.ends_with(\"/libart.so\") && map.perms == (PROT_WRITE | PROT_READ)) {\n                ZLOGV(\"libart.so: start=%p, end=%p\\n\",\n                      reinterpret_cast<void *>(map.start), reinterpret_cast<void *>(map.end));\n                return {map.start, map.end};\n            }\n        }\n        return {0, 0};\n    }();\n#if defined(__aarch64__)\n    // r19-r28 are callee-saved registers\n    for (int i = 19; i <= 28; ++i) {\n        auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));\n        ZLOGV(\"r%d = %p\\n\", i, reinterpret_cast<void *>(val));\n        if (val >= start && val < end)\n            return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);\n    }\n#elif defined(__arm__)\n    // r4-r10 are callee-saved registers\n    for (int i = 4; i <= 10; ++i) {\n        auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));\n        ZLOGV(\"r%d = %p\\n\", i, reinterpret_cast<void *>(val));\n        if (val >= start && val < end)\n            return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);\n    }\n#elif defined(__i386__)\n    // get ebp, which points to the bottom of the stack frame\n    auto ebp = static_cast<uintptr_t>(_Unwind_GetGR(ctx, 5));\n    // 1 pointer size above ebp is the old ebp\n    // 2 pointer sizes above ebp is the return address\n    // 3 pointer sizes above ebp is the 2nd arg\n    auto val = *reinterpret_cast<uintptr_t *>(ebp + 3 * sizeof(void *));\n    ZLOGV(\"ebp + 3 * ptr_size = %p\\n\", reinterpret_cast<void *>(val));\n    if (val >= start && val < end)\n        return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);\n#elif defined(__x86_64__)\n    // r12-r15 and rbx are callee-saved registers, but the compiler is likely to use them reversely\n    for (int i : {3, 15, 14, 13, 12}) {\n        auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));\n        ZLOGV(\"r%d = %p\\n\", i, reinterpret_cast<void *>(val));\n        if (val >= start && val < end)\n            return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);\n    }\n#elif defined(__riscv)\n    // x8-x9, x18-x27 callee-saved registers\n    for (int i : {8, 9, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}) {\n        auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));\n        ZLOGV(\"x%d = %p\\n\", i, reinterpret_cast<void *>(val));\n        if (val >= start && val < end)\n            return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);\n    }\n#else\n#error \"Unsupported architecture\"\n#endif\n    return nullptr;\n}\n\nvoid HookContext::post_native_bridge_load(void *handle) {\n    self_handle = handle;\n    using method_sig = const bool (*)(const char *, const NativeBridgeRuntimeCallbacks *);\n    struct trace_arg {\n        method_sig load_native_bridge;\n        const NativeBridgeRuntimeCallbacks *callbacks;\n    };\n    trace_arg arg{};\n\n    // Unwind to find the address of android::LoadNativeBridge and NativeBridgeRuntimeCallbacks\n    _Unwind_Backtrace(+[](_Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code {\n        void *fp = unwind_get_region_start(ctx);\n        Dl_info info{};\n        dladdr(fp, &info);\n        ZLOGV(\"backtrace: %p %s\\n\", fp, info.dli_fname ?: \"???\");\n        if (info.dli_fname && std::string_view(info.dli_fname).ends_with(\"/libnativebridge.so\")) {\n            auto payload = reinterpret_cast<trace_arg *>(arg);\n            payload->load_native_bridge = reinterpret_cast<method_sig>(fp);\n            payload->callbacks = find_runtime_callbacks(ctx);\n            ZLOGV(\"NativeBridgeRuntimeCallbacks: %p\\n\", payload->callbacks);\n            return _URC_END_OF_STACK;\n        }\n        return _URC_NO_REASON;\n    }, &arg);\n\n    if (!arg.load_native_bridge || !arg.callbacks)\n        return;\n\n    // Reload the real native bridge if necessary\n    auto nb = get_prop(NBPROP);\n    auto len = sizeof(ZYGISKLDR) - 1;\n    if (nb.size() > len) {\n        arg.load_native_bridge(nb.c_str() + len, arg.callbacks);\n    }\n    runtime_callbacks = arg.callbacks;\n}\n\n// -----------------------------------------------------------------\n\nvoid HookContext::register_hook(\n        dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {\n    if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {\n        ZLOGE(\"Failed to register plt_hook \\\"%s\\\"\\n\", symbol);\n        return;\n    }\n    plt_backup.emplace_back(dev, inode, symbol, old_func);\n}\n\n#define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME) \\\n    register_hook(DEV, INODE, SYM, \\\n    reinterpret_cast<void *>(new_##NAME), reinterpret_cast<void **>(&old_##NAME))\n\n#define PLT_HOOK_REGISTER(DEV, INODE, NAME) \\\n    PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME)\n\nvoid HookContext::hook_plt() {\n    ino_t android_runtime_inode = 0;\n    dev_t android_runtime_dev = 0;\n    ino_t native_bridge_inode = 0;\n    dev_t native_bridge_dev = 0;\n\n    for (auto &map : lsplt::MapInfo::Scan()) {\n        if (map.path.ends_with(\"/libandroid_runtime.so\")) {\n            android_runtime_inode = map.inode;\n            android_runtime_dev = map.dev;\n        } else if (map.path.ends_with(\"/libnativebridge.so\")) {\n            native_bridge_inode = map.inode;\n            native_bridge_dev = map.dev;\n        }\n    }\n\n    PLT_HOOK_REGISTER(native_bridge_dev, native_bridge_inode, dlclose);\n    PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);\n    PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);\n    PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext);\n    PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup);\n    PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, \"__android_log_close\", android_log_close);\n\n    if (!lsplt::CommitHook())\n        ZLOGE(\"plt_hook failed\\n\");\n\n    // Remove unhooked methods\n    std::erase_if(plt_backup, [](auto &t) { return *std::get<3>(t) == nullptr; });\n}\n\nvoid HookContext::hook_unloader() {\n    ino_t art_inode = 0;\n    dev_t art_dev = 0;\n\n    for (auto &map : lsplt::MapInfo::Scan()) {\n        if (map.path.ends_with(\"/libart.so\")) {\n            art_inode = map.inode;\n            art_dev = map.dev;\n            break;\n        }\n    }\n\n    PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy);\n    if (!lsplt::CommitHook())\n        ZLOGE(\"plt_hook failed\\n\");\n}\n\nvoid HookContext::restore_plt_hook() {\n    // Unhook plt_hook\n    for (const auto &[dev, inode, sym, old_func] : plt_backup) {\n        if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {\n            ZLOGE(\"Failed to register plt_hook [%s]\\n\", sym);\n            should_unmap = false;\n        }\n    }\n    if (!lsplt::CommitHook()) {\n        ZLOGE(\"Failed to restore plt_hook\\n\");\n        should_unmap = false;\n    }\n}\n\n// -----------------------------------------------------------------\n\nJNIMethodsDyn HookContext::get_jni_methods(JNIEnv *env, jclass clazz) const {\n    size_t total = runtime_callbacks->getNativeMethodCount(env, clazz);\n    auto methods = std::make_unique_for_overwrite<JNINativeMethod[]>(total);\n    runtime_callbacks->getNativeMethods(env, clazz, methods.get(), total);\n    return std::make_pair(std::move(methods), total);\n}\n\nstatic void register_jni_methods(JNIEnv *env, jclass clazz, JNIMethods methods) {\n    for (auto &method : methods) {\n        // It's useful to allow nullptr function pointer for restoring hook\n        if (!method.fnPtr) continue;\n\n        // It's normal that the method is not found\n        if (env->RegisterNatives(clazz, &method, 1) == JNI_ERR || env->ExceptionCheck() == JNI_TRUE) {\n            env->ExceptionClear();\n            method.fnPtr = nullptr;\n        }\n    }\n}\n\nint HookContext::hook_jni_methods(JNIEnv *env, jclass clazz, JNIMethods methods) const {\n    // Backup existing methods\n    auto o = get_jni_methods(env, clazz);\n    const auto old_methods = span(o.first.get(), o.second);\n\n    // WARNING: the signature field returned from getNativeMethods is in a non-standard format.\n    // DO NOT TRY TO USE IT. This is the reason why we try to call RegisterNatives on every single\n    // provided JNI methods directly to be 100% sure about whether a signature matches or not.\n\n    // Replace methods\n    register_jni_methods(env, clazz, methods);\n\n    // Fetch the new set of native methods\n    auto n = get_jni_methods(env, clazz);\n    const auto new_methods = span(n.first.get(), n.second);\n\n    // Find the old function pointer and return to caller\n    int hook_count = 0;\n    for (auto &method : methods) {\n        if (!method.fnPtr) continue;\n        for (const auto &new_method : new_methods) {\n            if (new_method.fnPtr == method.fnPtr) {\n                for (const auto &old_method : old_methods) {\n                    if (strcmp(old_method.name, new_method.name) == 0 &&\n                        strcmp(old_method.signature, new_method.signature) == 0) {\n                        ZLOGV(\"replace %s %s %p -> %p\\n\",\n                            method.name, method.signature, old_method.fnPtr, method.fnPtr);\n                        method.fnPtr = old_method.fnPtr;\n                        ++hook_count;\n                        // Break 2 levels of for loop\n                        goto next_method;\n                    }\n                }\n            }\n        }\n        next_method:\n    }\n    return hook_count;\n}\n\n\nvoid HookContext::hook_jni_methods(JNIEnv *env, const char *clz, JNIMethods methods) const {\n    jclass clazz;\n    if (!runtime_callbacks || !env || !clz || !((clazz = env->FindClass(clz)))) {\n        ranges::for_each(methods, [](auto &m) { m.fnPtr = nullptr; });\n        return;\n    }\n    hook_jni_methods(env, clazz, methods);\n}\n\nvoid HookContext::hook_zygote_jni() {\n    using method_sig = jint(*)(JavaVM **, jsize, jsize *);\n    auto get_created_vms = reinterpret_cast<method_sig>(\n            dlsym(RTLD_DEFAULT, \"JNI_GetCreatedJavaVMs\"));\n    if (!get_created_vms) {\n        for (auto &map: lsplt::MapInfo::Scan()) {\n            if (!map.path.ends_with(\"/libnativehelper.so\")) continue;\n            void *h = dlopen(map.path.data(), RTLD_LAZY);\n            if (!h) {\n                ZLOGW(\"Cannot dlopen libnativehelper.so: %s\\n\", dlerror());\n                break;\n            }\n            get_created_vms = reinterpret_cast<method_sig>(dlsym(h, \"JNI_GetCreatedJavaVMs\"));\n            dlclose(h);\n            break;\n        }\n        if (!get_created_vms) {\n            ZLOGW(\"JNI_GetCreatedJavaVMs not found\\n\");\n            return;\n        }\n    }\n\n    JavaVM *vm = nullptr;\n    jsize num = 0;\n    jint res = get_created_vms(&vm, 1, &num);\n    if (res != JNI_OK || vm == nullptr) {\n        ZLOGW(\"JavaVM not found\\n\");\n        return;\n    }\n    JNIEnv *env = nullptr;\n    res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);\n    if (res != JNI_OK || env == nullptr) {\n        ZLOGW(\"JNIEnv not found\\n\");\n    }\n\n    JNINativeMethod missing_method{};\n    bool replaced_fork_app = false;\n    bool replaced_specialize_app = false;\n    bool replaced_fork_server = false;\n\n    jclass clazz = env->FindClass(kZygote);\n    auto [ptr, count] = get_jni_methods(env, clazz);\n    for (const auto methods = span(ptr.get(), count); const auto &method : methods) {\n        if (strcmp(method.name, kForkApp) == 0) {\n            if (hook_jni_methods(env, clazz, fork_app_methods) == 0) {\n                missing_method = method;\n                break;\n            }\n            replaced_fork_app = true;\n        } else if (strcmp(method.name, kSpecializeApp) == 0) {\n            if (hook_jni_methods(env, clazz, specialize_app_methods) == 0) {\n                missing_method = method;\n                break;\n            }\n            replaced_specialize_app = true;\n        } else if (strcmp(method.name, kForkServer) == 0) {\n            if (hook_jni_methods(env, clazz, fork_server_methods) == 0) {\n                missing_method = method;\n                break;\n            }\n            replaced_fork_server = true;\n        }\n    }\n\n    if (missing_method.name != nullptr) {\n        ZLOGE(\"Cannot hook method: %s %s\\n\", missing_method.name, missing_method.signature);\n        // Restore methods that were already replaced\n        if (replaced_fork_app) register_jni_methods(env, clazz, fork_app_methods);\n        if (replaced_specialize_app) register_jni_methods(env, clazz, specialize_app_methods);\n        if (replaced_fork_server) register_jni_methods(env, clazz, fork_server_methods);\n        // Clear the method lists just in case\n        ranges::for_each(fork_app_methods, [](auto &m) { m.fnPtr = nullptr; });\n        ranges::for_each(specialize_app_methods, [](auto &m) { m.fnPtr = nullptr; });\n        ranges::for_each(fork_server_methods, [](auto &m) { m.fnPtr = nullptr; });\n    }\n}\n\nvoid HookContext::restore_zygote_hook(JNIEnv *env) {\n    jclass clazz = env->FindClass(kZygote);\n    register_jni_methods(env, clazz, fork_app_methods);\n    register_jni_methods(env, clazz, specialize_app_methods);\n    register_jni_methods(env, clazz, fork_server_methods);\n}\n\n// -----------------------------------------------------------------\n\nvoid hook_entry() {\n    default_new(g_hook);\n    g_hook->hook_plt();\n}\n\nvoid hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) {\n    g_hook->hook_jni_methods(env, clz, { methods, static_cast<size_t>(numMethods) });\n}\n"
  },
  {
    "path": "native/src/core/zygisk/jni_hooks.hpp",
    "content": "// Generated by gen_jni_hooks.py\n#pragma once\n\nstruct JniHookDefinitions;\nstatic JniHookDefinitions *get_defs();\n\nstruct JniHookDefinitions {\n\nstd::array<JNINativeMethod, 12> fork_app_methods = {{\n    // nativeForkAndSpecialize_l\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[0].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_o\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[1].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_p\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[2].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_q_alt\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app)>(get_defs()->fork_app_methods[3].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_r\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs)>(get_defs()->fork_app_methods[4].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_u\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            args.mount_sysprop_overrides = &mount_sysprop_overrides;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides)>(get_defs()->fork_app_methods[5].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_b\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;ZZ[Ljava/lang/String;[Ljava/lang/String;ZZZ)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean use_fifo_ui, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            args.mount_sysprop_overrides = &mount_sysprop_overrides;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean use_fifo_ui, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides)>(get_defs()->fork_app_methods[6].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, use_fifo_ui, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_samsung_m\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[7].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_samsung_n\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4)>(get_defs()->fork_app_methods[8].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_samsung_o\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[9].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_samsung_p\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir)>(get_defs()->fork_app_methods[10].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkAndSpecialize_nubia_u\n    {\n        \"nativeForkAndSpecialize\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;ZZ[Ljava/lang/String;[Ljava/lang/String;ZZZLjava/lang/String;)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean use_fifo_ui, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jstring _9) static -> jint {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.fds_to_ignore = &fds_to_ignore;\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            args.mount_sysprop_overrides = &mount_sysprop_overrides;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkAndSpecialize_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean use_fifo_ui, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jstring _9)>(get_defs()->fork_app_methods[11].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, use_fifo_ui, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides, _9\n            );\n            ctx.nativeForkAndSpecialize_post();\n            return ctx.pid;\n        }\n    },\n}};\n\nstd::array<JNINativeMethod, 7> specialize_app_methods = {{\n    // nativeSpecializeAppProcess_q\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir)>(get_defs()->specialize_app_methods[0].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_q_alt\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app)>(get_defs()->specialize_app_methods[1].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_r\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs)>(get_defs()->specialize_app_methods[2].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_u\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            args.mount_sysprop_overrides = &mount_sysprop_overrides;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides)>(get_defs()->specialize_app_methods[3].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_xr_u\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;ZZ[Ljava/lang/String;[Ljava/lang/String;ZZ)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean is_perception_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jboolean is_perception_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs)>(get_defs()->specialize_app_methods[4].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, is_perception_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_samsung_q\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _10, jint _11, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _10, jint _11, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir)>(get_defs()->specialize_app_methods[5].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _10, _11, nice_name, is_child_zygote, instruction_set, app_data_dir\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n    // nativeSpecializeAppProcess_nubia_u\n    {\n        \"nativeSpecializeAppProcess\",\n        \"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZLjava/lang/String;)V\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jstring _12) static -> void {\n            AppSpecializeArgs_v5 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);\n            args.is_child_zygote = &is_child_zygote;\n            args.is_top_app = &is_top_app;\n            args.pkg_data_info_list = &pkg_data_info_list;\n            args.whitelisted_data_info_list = &whitelisted_data_info_list;\n            args.mount_data_dirs = &mount_data_dirs;\n            args.mount_storage_dirs = &mount_storage_dirs;\n            args.mount_sysprop_overrides = &mount_sysprop_overrides;\n            ZygiskContext ctx(env, &args);\n            ctx.nativeSpecializeAppProcess_pre();\n            reinterpret_cast<void(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides, jstring _12)>(get_defs()->specialize_app_methods[6].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides, _12\n            );\n            ctx.nativeSpecializeAppProcess_post();\n        }\n    },\n}};\n\nstd::array<JNINativeMethod, 2> fork_server_methods = {{\n    // nativeForkSystemServer_l\n    {\n        \"nativeForkSystemServer\",\n        \"(II[II[[IJJ)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) static -> jint {\n            ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkSystemServer_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities)>(get_defs()->fork_server_methods[0].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities\n            );\n            ctx.nativeForkSystemServer_post();\n            return ctx.pid;\n        }\n    },\n    // nativeForkSystemServer_samsung_q\n    {\n        \"nativeForkSystemServer\",\n        \"(II[IIII[[IJJ)I\",\n        (void *) +[] [[clang::no_stack_protector]] (JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _13, jint _14, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) static -> jint {\n            ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);\n            ZygiskContext ctx(env, &args);\n            ctx.nativeForkSystemServer_pre();\n            reinterpret_cast<jint(*)(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _13, jint _14, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities)>(get_defs()->fork_server_methods[1].fnPtr)(\n                env, clazz, uid, gid, gids, runtime_flags, _13, _14, rlimits, permitted_capabilities, effective_capabilities\n            );\n            ctx.nativeForkSystemServer_post();\n            return ctx.pid;\n        }\n    },\n}};\n\n};\n"
  },
  {
    "path": "native/src/core/zygisk/mod.rs",
    "content": "mod daemon;\n\nuse crate::thread::ThreadPool;\nuse base::{fd_get_attr, libc};\npub use daemon::{ZygiskState, zygisk_should_load_module};\nuse std::os::fd::RawFd;\n\n#[unsafe(no_mangle)]\nextern \"C\" fn exec_companion_entry(client: RawFd, companion_handler: extern \"C\" fn(RawFd)) {\n    ThreadPool::exec_task(move || {\n        let Ok(s1) = fd_get_attr(client) else {\n            return;\n        };\n\n        companion_handler(client);\n\n        // Only close client if it is the same file so we don't\n        // accidentally close a re-used file descriptor.\n        // This check is required because the module companion\n        // handler could've closed the file descriptor already.\n        if let Ok(s2) = fd_get_attr(client)\n            && s1.st.st_dev == s2.st.st_dev\n            && s1.st.st_ino == s2.st.st_ino\n        {\n            unsafe { libc::close(client) };\n        }\n    });\n}\n"
  },
  {
    "path": "native/src/core/zygisk/module.cpp",
    "content": "#include <sys/mman.h>\n#include <android/dlext.h>\n#include <dlfcn.h>\n\n#include <lsplt.hpp>\n\n#include <base.hpp>\n\n#include \"zygisk.hpp\"\n#include \"module.hpp\"\n\nusing namespace std;\n\nstatic int zygisk_request(int req) {\n    int fd = connect_daemon(RequestCode::ZYGISK);\n    if (fd < 0) return fd;\n    write_int(fd, req);\n    return fd;\n}\n\nZygiskModule::ZygiskModule(int id, void *handle, void *entry)\n    : id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {\n    // Make sure all pointers are null\n    memset(&api, 0, sizeof(api));\n    api.base.impl = this;\n    api.base.registerModule = &ZygiskModule::RegisterModuleImpl;\n}\n\nbool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {\n    if (api == nullptr || module == nullptr)\n        return false;\n\n    long api_version = *module;\n    // Unsupported version\n    if (api_version > ZYGISK_API_VERSION)\n        return false;\n\n    // Set the actual module_abi*\n    api->base.impl->mod = { module };\n\n    // Fill in API accordingly with module API version\n    if (api_version >= 1) {\n        api->v1.hookJniNativeMethods = hookJniNativeMethods;\n        api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) {\n            if (g_ctx) g_ctx->plt_hook_register(a, b, c, d);\n        };\n        api->v1.pltHookExclude = [](auto a, auto b) {\n            if (g_ctx) g_ctx->plt_hook_exclude(a, b);\n        };\n        api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); };\n        api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };\n        api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };\n    }\n    if (api_version >= 2) {\n        api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };\n        api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };\n    }\n    if (api_version >= 4) {\n        api->v4.pltHookCommit = lsplt::CommitHook;\n        api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {\n            if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)\n                return;\n            lsplt::RegisterHook(dev, inode, symbol, fn, backup);\n        };\n        api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); };\n    }\n\n    return true;\n}\n\nbool ZygiskModule::valid() const {\n    if (mod.api_version == nullptr)\n        return false;\n    switch (*mod.api_version) {\n        case 5:\n        case 4:\n        case 3:\n        case 2:\n        case 1:\n            return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize &&\n                   mod.v1->preServerSpecialize && mod.v1->postServerSpecialize;\n        default:\n            return false;\n    }\n}\n\nint ZygiskModule::connectCompanion() const {\n    if (int fd = zygisk_request(+ZygiskRequest::ConnectCompanion); fd >= 0) {\n#ifdef __LP64__\n        write_any<bool>(fd, true);\n#else\n        write_any<bool>(fd, false);\n#endif\n        write_int(fd, id);\n        return fd;\n    }\n    return -1;\n}\n\nint ZygiskModule::getModuleDir() const {\n    if (owned_fd fd = zygisk_request(+ZygiskRequest::GetModDir); fd >= 0) {\n        write_int(fd, id);\n        return recv_fd(fd);\n    }\n    return -1;\n}\n\nvoid ZygiskModule::setOption(zygisk::Option opt) {\n    if (g_ctx == nullptr)\n        return;\n    switch (opt) {\n        case zygisk::FORCE_DENYLIST_UNMOUNT:\n            g_ctx->flags |= DO_REVERT_UNMOUNT;\n            break;\n        case zygisk::DLCLOSE_MODULE_LIBRARY:\n            unload = true;\n            break;\n    }\n}\n\nuint32_t ZygiskModule::getFlags() {\n    return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0;\n}\n\nvoid ZygiskModule::tryUnload() const {\n    if (unload) dlclose(handle);\n}\n\n// -----------------------------------------------------------------\n\n#define call_app(method)               \\\nswitch (*mod.api_version) {            \\\ncase 1:                                \\\ncase 2: {                              \\\n    AppSpecializeArgs_v1 a(args);      \\\n    mod.v1->method(mod.v1->impl, &a);  \\\n    break;                             \\\n}                                      \\\ncase 3:                                \\\ncase 4:                                \\\ncase 5:                                \\\n    mod.v1->method(mod.v1->impl, args);\\\n    break;                             \\\n}\n\nvoid ZygiskModule::preAppSpecialize(AppSpecializeArgs_v5 *args) const {\n    call_app(preAppSpecialize)\n}\n\nvoid ZygiskModule::postAppSpecialize(const AppSpecializeArgs_v5 *args) const {\n    call_app(postAppSpecialize)\n}\n\nvoid ZygiskModule::preServerSpecialize(ServerSpecializeArgs_v1 *args) const {\n    mod.v1->preServerSpecialize(mod.v1->impl, args);\n}\n\nvoid ZygiskModule::postServerSpecialize(const ServerSpecializeArgs_v1 *args) const {\n    mod.v1->postServerSpecialize(mod.v1->impl, args);\n}\n\n// -----------------------------------------------------------------\n\nvoid ZygiskContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) {\n    if (regex == nullptr || symbol == nullptr || fn == nullptr)\n        return;\n    regex_t re;\n    if (regcomp(&re, regex, REG_NOSUB) != 0)\n        return;\n    mutex_guard lock(hook_info_lock);\n    register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});\n}\n\nvoid ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {\n    if (!regex) return;\n    regex_t re;\n    if (regcomp(&re, regex, REG_NOSUB) != 0)\n        return;\n    mutex_guard lock(hook_info_lock);\n    ignore_info.emplace_back(IgnoreInfo{re, symbol ?: \"\"});\n}\n\nvoid ZygiskContext::plt_hook_process_regex() {\n    if (register_info.empty())\n        return;\n    for (auto &map : lsplt::MapInfo::Scan()) {\n        if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;\n        for (auto &reg: register_info) {\n            if (regexec(&reg.regex, map.path.data(), 0, nullptr, 0) != 0)\n                continue;\n            bool ignored = false;\n            for (auto &ign: ignore_info) {\n                if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0)\n                    continue;\n                if (ign.symbol.empty() || ign.symbol == reg.symbol) {\n                    ignored = true;\n                    break;\n                }\n            }\n            if (!ignored) {\n                lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup);\n            }\n        }\n    }\n}\n\nbool ZygiskContext::plt_hook_commit() {\n    {\n        mutex_guard lock(hook_info_lock);\n        plt_hook_process_regex();\n        for (auto& reg: register_info) {\n            regfree(&reg.regex);\n        }\n        for (auto& ign: ignore_info) {\n            regfree(&ign.regex);\n        }\n        register_info.clear();\n        ignore_info.clear();\n    }\n    return lsplt::CommitHook();\n}\n\n// -----------------------------------------------------------------\n\nint ZygiskContext::get_module_info(int uid, rust::Vec<int> &fds) {\n    if (int fd = zygisk_request(+ZygiskRequest::GetInfo); fd >= 0) {\n        write_int(fd, uid);\n        write_string(fd, process);\n#ifdef __LP64__\n        write_any<bool>(fd, true);\n#else\n        write_any<bool>(fd, false);\n#endif\n        xxread(fd, &info_flags, sizeof(info_flags));\n        if (zygisk_should_load_module(info_flags)) {\n            fds = recv_fds(fd);\n        }\n        return fd;\n    }\n    return -1;\n}\n\nvoid ZygiskContext::sanitize_fds() {\n    zygisk_close_logd();\n\n    if (!is_child()) {\n        return;\n    }\n\n    if (can_exempt_fd() && !exempted_fds.empty()) {\n        auto update_fd_array = [&](int old_len) -> jintArray {\n            jintArray array = env->NewIntArray(static_cast<int>(old_len + exempted_fds.size()));\n            if (array == nullptr)\n                return nullptr;\n\n            env->SetIntArrayRegion(\n                    array, old_len, static_cast<int>(exempted_fds.size()), exempted_fds.data());\n            for (int fd : exempted_fds) {\n                if (fd >= 0 && fd < allowed_fds.size()) {\n                    allowed_fds[fd] = true;\n                }\n            }\n            *args.app->fds_to_ignore = array;\n            return array;\n        };\n\n        if (jintArray fdsToIgnore = *args.app->fds_to_ignore) {\n            int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr);\n            int len = env->GetArrayLength(fdsToIgnore);\n            for (int i = 0; i < len; ++i) {\n                int fd = arr[i];\n                if (fd >= 0 && fd < allowed_fds.size()) {\n                    allowed_fds[fd] = true;\n                }\n            }\n            if (jintArray newFdList = update_fd_array(len)) {\n                env->SetIntArrayRegion(newFdList, 0, len, arr);\n            }\n            env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT);\n        } else {\n            update_fd_array(0);\n        }\n    }\n\n    // Close all forbidden fds to prevent crashing\n    auto dir = xopen_dir(\"/proc/self/fd\");\n    int dfd = dirfd(dir.get());\n    for (dirent *entry; (entry = xreaddir(dir.get()));) {\n        int fd = parse_int(entry->d_name);\n        if ((fd < 0 || fd >= allowed_fds.size() || !allowed_fds[fd]) && fd != dfd) {\n            close(fd);\n        }\n    }\n}\n\nbool ZygiskContext::exempt_fd(int fd) {\n    if ((flags & POST_SPECIALIZE) || (flags & SKIP_CLOSE_LOG_PIPE))\n        return true;\n    if (!can_exempt_fd())\n        return false;\n    exempted_fds.push_back(fd);\n    return true;\n}\n\nbool ZygiskContext::can_exempt_fd() const {\n    return (flags & APP_FORK_AND_SPECIALIZE) && args.app->fds_to_ignore;\n}\n\nstatic int sigmask(int how, int signum) {\n    sigset_t set;\n    sigemptyset(&set);\n    sigaddset(&set, signum);\n    return sigprocmask(how, &set, nullptr);\n}\n\nvoid ZygiskContext::fork_pre() {\n    // Do our own fork before loading any 3rd party code\n    // First block SIGCHLD, unblock after original fork is done\n    sigmask(SIG_BLOCK, SIGCHLD);\n    pid = old_fork();\n\n    if (!is_child())\n        return;\n\n    // Record all open fds\n    auto dir = xopen_dir(\"/proc/self/fd\");\n    for (dirent *entry; (entry = xreaddir(dir.get()));) {\n        int fd = parse_int(entry->d_name);\n        if (fd < 0 || fd >= allowed_fds.size()) {\n            close(fd);\n            continue;\n        }\n        allowed_fds[fd] = true;\n    }\n    // The dirfd will be closed once out of scope\n    allowed_fds[dirfd(dir.get())] = false;\n    // logd_fd should be handled separately\n    if (int fd = zygisk_get_logd(); fd >= 0) {\n        allowed_fds[fd] = false;\n    }\n}\n\nvoid ZygiskContext::fork_post() {\n    // Unblock SIGCHLD in case the original method didn't\n    sigmask(SIG_UNBLOCK, SIGCHLD);\n}\n\nvoid ZygiskContext::run_modules_pre(rust::Vec<int> &fds) {\n    for (int i = 0; i < fds.size(); ++i) {\n        owned_fd fd = fds[i];\n        struct stat s{};\n        if (fstat(fd, &s) != 0 || !S_ISREG(s.st_mode)) {\n            fds[i] = -1;\n            continue;\n        }\n        android_dlextinfo info {\n            .flags = ANDROID_DLEXT_USE_LIBRARY_FD,\n            .library_fd = fd,\n        };\n        if (void *h = android_dlopen_ext(\"/jit-cache\", RTLD_LAZY, &info)) {\n            if (void *e = dlsym(h, \"zygisk_module_entry\")) {\n                modules.emplace_back(i, h, e);\n            }\n        } else if (flags & SERVER_FORK_AND_SPECIALIZE) {\n            ZLOGW(\"Failed to dlopen zygisk module: %s\\n\", dlerror());\n            fds[i] = -1;\n        }\n    }\n\n    for (auto it = modules.begin(); it != modules.end();) {\n        it->onLoad(env);\n        if (it->valid()) {\n            ++it;\n        } else {\n            it = modules.erase(it);\n        }\n    }\n\n    for (auto &m : modules) {\n        if (flags & APP_SPECIALIZE) {\n            m.preAppSpecialize(args.app);\n        } else if (flags & SERVER_FORK_AND_SPECIALIZE) {\n            m.preServerSpecialize(args.server);\n        }\n    }\n}\n\nvoid ZygiskContext::run_modules_post() {\n    flags |= POST_SPECIALIZE;\n    for (const auto &m : modules) {\n        if (flags & APP_SPECIALIZE) {\n            m.postAppSpecialize(args.app);\n        } else if (flags & SERVER_FORK_AND_SPECIALIZE) {\n            m.postServerSpecialize(args.server);\n        }\n        m.tryUnload();\n    }\n}\n\nvoid ZygiskContext::app_specialize_pre() {\n    flags |= APP_SPECIALIZE;\n\n    rust::Vec<int> module_fds;\n    owned_fd fd = get_module_info(args.app->uid, module_fds);\n    if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) {\n        ZLOGI(\"[%s] is on the denylist\\n\", process);\n        flags |= DO_REVERT_UNMOUNT;\n    } else if (fd >= 0) {\n        run_modules_pre(module_fds);\n    }\n}\n\nvoid ZygiskContext::app_specialize_post() {\n    run_modules_post();\n    if (info_flags & +ZygiskStateFlags::ProcessIsMagiskApp) {\n        setenv(\"ZYGISK_ENABLED\", \"1\", 1);\n    }\n\n    // Cleanups\n    env->ReleaseStringUTFChars(args.app->nice_name, process);\n}\n\nvoid ZygiskContext::server_specialize_pre() {\n    rust::Vec<int> module_fds;\n    if (owned_fd fd = get_module_info(1000, module_fds); fd >= 0) {\n        if (module_fds.empty()) {\n            write_int(fd, 0);\n        } else {\n            run_modules_pre(module_fds);\n\n            // Find all failed module ids and send it back to magiskd\n            vector<int> failed_ids;\n            for (int i = 0; i < module_fds.size(); ++i) {\n                if (module_fds[i] < 0) {\n                    failed_ids.push_back(i);\n                }\n            }\n            write_vector(fd, failed_ids);\n        }\n    }\n}\n\nvoid ZygiskContext::server_specialize_post() {\n    run_modules_post();\n}\n\n// -----------------------------------------------------------------\n\nvoid ZygiskContext::nativeSpecializeAppProcess_pre() {\n    process = env->GetStringUTFChars(args.app->nice_name, nullptr);\n    ZLOGV(\"pre  specialize [%s]\\n\", process);\n    // App specialize does not check FD\n    flags |= SKIP_CLOSE_LOG_PIPE;\n    app_specialize_pre();\n}\n\nvoid ZygiskContext::nativeSpecializeAppProcess_post() {\n    ZLOGV(\"post specialize [%s]\\n\", process);\n    app_specialize_post();\n}\n\nvoid ZygiskContext::nativeForkSystemServer_pre() {\n    ZLOGV(\"pre  forkSystemServer\\n\");\n    flags |= SERVER_FORK_AND_SPECIALIZE;\n    process = \"system_server\";\n\n    fork_pre();\n    if (is_child()) {\n        server_specialize_pre();\n    }\n    sanitize_fds();\n}\n\nvoid ZygiskContext::nativeForkSystemServer_post() {\n    if (is_child()) {\n        ZLOGV(\"post forkSystemServer\\n\");\n        server_specialize_post();\n    }\n    fork_post();\n}\n\nvoid ZygiskContext::nativeForkAndSpecialize_pre() {\n    process = env->GetStringUTFChars(args.app->nice_name, nullptr);\n    ZLOGV(\"pre  forkAndSpecialize [%s]\\n\", process);\n    flags |= APP_FORK_AND_SPECIALIZE;\n\n    fork_pre();\n    if (is_child()) {\n        app_specialize_pre();\n    }\n    sanitize_fds();\n}\n\nvoid ZygiskContext::nativeForkAndSpecialize_post() {\n    if (is_child()) {\n        ZLOGV(\"post forkAndSpecialize [%s]\\n\", process);\n        app_specialize_post();\n    }\n    fork_post();\n}\n"
  },
  {
    "path": "native/src/core/zygisk/module.hpp",
    "content": "#pragma once\n\n#include <regex.h>\n#include <list>\n\n#include \"api.hpp\"\n\nstruct ZygiskContext;\nstruct ZygiskModule;\n\nstruct AppSpecializeArgs_v1;\nusing  AppSpecializeArgs_v2 = AppSpecializeArgs_v1;\nstruct AppSpecializeArgs_v3;\nusing  AppSpecializeArgs_v4 = AppSpecializeArgs_v3;\nstruct AppSpecializeArgs_v5;\n\nstruct module_abi_v1;\nusing  module_abi_v2 = module_abi_v1;\nusing  module_abi_v3 = module_abi_v1;\nusing  module_abi_v4 = module_abi_v1;\nusing  module_abi_v5 = module_abi_v1;\n\nstruct api_abi_v1;\nstruct api_abi_v2;\nusing  api_abi_v3 = api_abi_v2;\nstruct api_abi_v4;\nusing  api_abi_v5 = api_abi_v4;\n\nunion ApiTable;\n\nstruct AppSpecializeArgs_v3 {\n    jint &uid;\n    jint &gid;\n    jintArray &gids;\n    jint &runtime_flags;\n    jobjectArray &rlimits;\n    jint &mount_external;\n    jstring &se_info;\n    jstring &nice_name;\n    jstring &instruction_set;\n    jstring &app_data_dir;\n\n    jintArray *fds_to_ignore = nullptr;\n    jboolean *is_child_zygote = nullptr;\n    jboolean *is_top_app = nullptr;\n    jobjectArray *pkg_data_info_list = nullptr;\n    jobjectArray *whitelisted_data_info_list = nullptr;\n    jboolean *mount_data_dirs = nullptr;\n    jboolean *mount_storage_dirs = nullptr;\n\n    AppSpecializeArgs_v3(\n            jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,\n            jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,\n            jstring &instruction_set, jstring &app_data_dir) :\n            uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), rlimits(rlimits),\n            mount_external(mount_external), se_info(se_info), nice_name(nice_name),\n            instruction_set(instruction_set), app_data_dir(app_data_dir) {}\n};\n\nstruct AppSpecializeArgs_v5 : public AppSpecializeArgs_v3 {\n    jboolean *mount_sysprop_overrides = nullptr;\n\n    AppSpecializeArgs_v5(\n            jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,\n            jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,\n            jstring &instruction_set, jstring &app_data_dir) : AppSpecializeArgs_v3(\n                    uid, gid, gids, runtime_flags, rlimits, mount_external,\n                    se_info, nice_name, instruction_set, app_data_dir) {}\n};\n\nstruct AppSpecializeArgs_v1 {\n    jint &uid;\n    jint &gid;\n    jintArray &gids;\n    jint &runtime_flags;\n    jint &mount_external;\n    jstring &se_info;\n    jstring &nice_name;\n    jstring &instruction_set;\n    jstring &app_data_dir;\n\n    jboolean *const is_child_zygote;\n    jboolean *const is_top_app;\n    jobjectArray *const pkg_data_info_list;\n    jobjectArray *const whitelisted_data_info_list;\n    jboolean *const mount_data_dirs;\n    jboolean *const mount_storage_dirs;\n\n    AppSpecializeArgs_v1(const AppSpecializeArgs_v5 *a) :\n            uid(a->uid), gid(a->gid), gids(a->gids), runtime_flags(a->runtime_flags),\n            mount_external(a->mount_external), se_info(a->se_info), nice_name(a->nice_name),\n            instruction_set(a->instruction_set), app_data_dir(a->app_data_dir),\n            is_child_zygote(a->is_child_zygote), is_top_app(a->is_top_app),\n            pkg_data_info_list(a->pkg_data_info_list),\n            whitelisted_data_info_list(a->whitelisted_data_info_list),\n            mount_data_dirs(a->mount_data_dirs), mount_storage_dirs(a->mount_storage_dirs) {}\n};\n\nstruct ServerSpecializeArgs_v1 {\n    jint &uid;\n    jint &gid;\n    jintArray &gids;\n    jint &runtime_flags;\n    jlong &permitted_capabilities;\n    jlong &effective_capabilities;\n\n    ServerSpecializeArgs_v1(\n            jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,\n            jlong &permitted_capabilities, jlong &effective_capabilities) :\n            uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags),\n            permitted_capabilities(permitted_capabilities),\n            effective_capabilities(effective_capabilities) {}\n};\n\nstruct module_abi_v1 {\n    long api_version;\n    void *impl;\n    void (*preAppSpecialize)(void *, void *);\n    void (*postAppSpecialize)(void *, const void *);\n    void (*preServerSpecialize)(void *, void *);\n    void (*postServerSpecialize)(void *, const void *);\n};\n\n// Assert the flag values to be the same as the public API\nstatic_assert(+ZygiskStateFlags::ProcessGrantedRoot == zygisk::StateFlag::PROCESS_GRANTED_ROOT);\nstatic_assert(+ZygiskStateFlags::ProcessOnDenyList == zygisk::StateFlag::PROCESS_ON_DENYLIST);\n\nenum : uint32_t {\n    UNMOUNT_MASK = (+ZygiskStateFlags::ProcessOnDenyList | +ZygiskStateFlags::DenyListEnforced),\n    PRIVATE_MASK = (+ZygiskStateFlags::DenyListEnforced | +ZygiskStateFlags::ProcessIsMagiskApp)\n};\n\nstruct api_abi_base {\n    ZygiskModule *impl;\n    bool (*registerModule)(ApiTable *, long *);\n};\n\nstruct api_abi_v1 : public api_abi_base {\n    /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);\n    /* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **);\n    /* 2 */ void (*pltHookExclude)(const char *, const char *);\n    /* 3 */ bool (*pltHookCommit)();\n    /* 4 */ int (*connectCompanion)(ZygiskModule *);\n    /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);\n};\n\nstruct api_abi_v2 : public api_abi_v1 {\n    /* 6 */ int (*getModuleDir)(ZygiskModule *);\n    /* 7 */ uint32_t (*getFlags)(ZygiskModule *);\n};\n\nstruct api_abi_v4 : public api_abi_base {\n    /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);\n    /* 1 */ void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);\n    /* 2 */ bool (*exemptFd)(int);\n    /* 3 */ bool (*pltHookCommit)();\n    /* 4 */ int (*connectCompanion)(ZygiskModule *);\n    /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);\n    /* 6 */ int (*getModuleDir)(ZygiskModule *);\n    /* 7 */ uint32_t (*getFlags)(ZygiskModule *);\n};\n\nunion ApiTable {\n    api_abi_base base;\n    api_abi_v1 v1;\n    api_abi_v2 v2;\n    api_abi_v4 v4;\n};\n\nstruct ZygiskModule {\n\n    void onLoad(void *env) {\n        entry.fn(&api, env);\n    }\n\n    void preAppSpecialize(AppSpecializeArgs_v5 *args) const;\n    void postAppSpecialize(const AppSpecializeArgs_v5 *args) const;\n    void preServerSpecialize(ServerSpecializeArgs_v1 *args) const;\n    void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const;\n\n    bool valid() const;\n    int connectCompanion() const;\n    int getModuleDir() const;\n    void setOption(zygisk::Option opt);\n    static uint32_t getFlags();\n    void tryUnload() const;\n    void clearApi() { memset(&api, 0, sizeof(api)); }\n\n    ZygiskModule(int id, void *handle, void *entry);\n\n    static bool RegisterModuleImpl(ApiTable *api, long *module);\n\nprivate:\n    const int id;\n    bool unload = false;\n\n    void * const handle;\n    union {\n        void * const ptr;\n        void (* const fn)(void *, void *);\n    } entry;\n\n    ApiTable api;\n\n    union {\n        long *api_version;\n        module_abi_v1 *v1;\n    } mod;\n};\n\nextern ZygiskContext *g_ctx;\nextern int (*old_fork)(void);\n\nenum : uint32_t {\n    POST_SPECIALIZE = (1u << 0),\n    APP_FORK_AND_SPECIALIZE = (1u << 1),\n    APP_SPECIALIZE = (1u << 2),\n    SERVER_FORK_AND_SPECIALIZE = (1u << 3),\n    DO_REVERT_UNMOUNT = (1u << 4),\n    SKIP_CLOSE_LOG_PIPE = (1u << 5),\n};\n\n#define DCL_PRE_POST(name) \\\nvoid name##_pre();         \\\nvoid name##_post();\n\nstruct ZygiskContext {\n    JNIEnv *env;\n    union {\n        void *ptr;\n        AppSpecializeArgs_v5 *app;\n        ServerSpecializeArgs_v1 *server;\n    } args;\n\n    const char *process;\n    std::list<ZygiskModule> modules;\n\n    int pid;\n    uint32_t flags;\n    uint32_t info_flags;\n    std::vector<bool> allowed_fds;\n    std::vector<int> exempted_fds;\n\n    struct RegisterInfo {\n        regex_t regex;\n        std::string symbol;\n        void *callback;\n        void **backup;\n    };\n\n    struct IgnoreInfo {\n        regex_t regex;\n        std::string symbol;\n    };\n\n    pthread_mutex_t hook_info_lock;\n    std::vector<RegisterInfo> register_info;\n    std::vector<IgnoreInfo> ignore_info;\n\n    ZygiskContext(JNIEnv *env, void *args);\n    ~ZygiskContext();\n\n    void run_modules_pre(rust::Vec<int> &fds);\n    void run_modules_post();\n    DCL_PRE_POST(fork)\n    DCL_PRE_POST(app_specialize)\n    DCL_PRE_POST(server_specialize)\n    DCL_PRE_POST(nativeForkAndSpecialize)\n    DCL_PRE_POST(nativeSpecializeAppProcess)\n    DCL_PRE_POST(nativeForkSystemServer)\n\n    int get_module_info(int uid, rust::Vec<int> &fds);\n    void sanitize_fds();\n    bool exempt_fd(int fd);\n    bool can_exempt_fd() const;\n    bool is_child() const { return pid <= 0; }\n\n    // Compatibility shim\n    void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);\n    void plt_hook_exclude(const char *regex, const char *symbol);\n    void plt_hook_process_regex();\n\n    bool plt_hook_commit();\n};\n\n#undef DCL_PRE_POST\n"
  },
  {
    "path": "native/src/core/zygisk/zygisk.hpp",
    "content": "#pragma once\n\n#include <jni.h>\n#include <core.hpp>\n\n#define ZYGISKLDR       \"libzygisk.so\"\n#define NBPROP          \"ro.dalvik.vm.native.bridge\"\n\n#if defined(__LP64__)\n#define ZLOGD(...) LOGD(\"zygisk64: \" __VA_ARGS__)\n#define ZLOGE(...) LOGE(\"zygisk64: \" __VA_ARGS__)\n#define ZLOGI(...) LOGI(\"zygisk64: \" __VA_ARGS__)\n#define ZLOGW(...) LOGW(\"zygisk64: \" __VA_ARGS__)\n#else\n#define ZLOGD(...) LOGD(\"zygisk32: \" __VA_ARGS__)\n#define ZLOGE(...) LOGE(\"zygisk32: \" __VA_ARGS__)\n#define ZLOGI(...) LOGI(\"zygisk32: \" __VA_ARGS__)\n#define ZLOGW(...) LOGW(\"zygisk32: \" __VA_ARGS__)\n#endif\n\n// Extreme verbose logging\n// #define ZLOGV(...) ZLOGD(__VA_ARGS__)\n#define ZLOGV(...) (void*)0\n\nvoid hook_entry();\nvoid hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods);\n\n// The reference of the following structs\n// https://cs.android.com/android/platform/superproject/main/+/main:art/libnativebridge/include/nativebridge/native_bridge.h\n\nstruct NativeBridgeRuntimeCallbacks {\n    const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid);\n    uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz);\n    uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods,\n                                 uint32_t method_count);\n};\n\nstruct NativeBridgeCallbacks {\n    uint32_t version;\n    void *padding[5];\n    bool (*isCompatibleWith)(uint32_t);\n};\n"
  },
  {
    "path": "native/src/exported_sym.txt",
    "content": "{\n    NativeBridgeItf;\n};\n"
  },
  {
    "path": "native/src/external/Android.mk",
    "content": "LOCAL_PATH := $(call my-dir)\n\n# libxz.a\ninclude $(CLEAR_VARS)\nLOCAL_MODULE:= libxz\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/xz-embedded\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\nLOCAL_SRC_FILES := \\\n    xz-embedded/xz_crc32.c \\\n    xz-embedded/xz_dec_lzma2.c \\\n    xz-embedded/xz_dec_stream.c\ninclude $(BUILD_STATIC_LIBRARY)\n\n# liblz4.a\ninclude $(CLEAR_VARS)\nLOCAL_MODULE := liblz4\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/lz4/lib\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\nLOCAL_SRC_FILES := \\\n    lz4/lib/lz4.c \\\n    lz4/lib/lz4frame.c \\\n    lz4/lib/lz4hc.c \\\n    lz4/lib/xxhash.c\ninclude $(BUILD_STATIC_LIBRARY)\n\nSE_PATH := $(LOCAL_PATH)/selinux\n\n# libsepol.a\ninclude $(CLEAR_VARS)\nLIBSEPOL := $(SE_PATH)/libsepol/include $(SE_PATH)/libsepol/cil/include\nLOCAL_MODULE := libsepol\nLOCAL_C_INCLUDES := $(LIBSEPOL) $(LOCAL_PATH)/selinux/libsepol/src\nLOCAL_EXPORT_C_INCLUDES := $(LIBSEPOL)\nLOCAL_SRC_FILES := \\\n    selinux/libsepol/src/assertion.c \\\n    selinux/libsepol/src/avrule_block.c \\\n    selinux/libsepol/src/avtab.c \\\n    selinux/libsepol/src/boolean_record.c \\\n    selinux/libsepol/src/booleans.c \\\n    selinux/libsepol/src/conditional.c \\\n    selinux/libsepol/src/constraint.c \\\n    selinux/libsepol/src/context.c \\\n    selinux/libsepol/src/context_record.c \\\n    selinux/libsepol/src/debug.c \\\n    selinux/libsepol/src/ebitmap.c \\\n    selinux/libsepol/src/expand.c \\\n    selinux/libsepol/src/handle.c \\\n    selinux/libsepol/src/hashtab.c \\\n    selinux/libsepol/src/hierarchy.c \\\n    selinux/libsepol/src/ibendport_record.c \\\n    selinux/libsepol/src/ibendports.c \\\n    selinux/libsepol/src/ibpkey_record.c \\\n    selinux/libsepol/src/ibpkeys.c \\\n    selinux/libsepol/src/iface_record.c \\\n    selinux/libsepol/src/interfaces.c \\\n    selinux/libsepol/src/kernel_to_cil.c \\\n    selinux/libsepol/src/kernel_to_common.c \\\n    selinux/libsepol/src/kernel_to_conf.c \\\n    selinux/libsepol/src/link.c \\\n    selinux/libsepol/src/mls.c \\\n    selinux/libsepol/src/module.c \\\n    selinux/libsepol/src/module_to_cil.c \\\n    selinux/libsepol/src/node_record.c \\\n    selinux/libsepol/src/nodes.c \\\n    selinux/libsepol/src/optimize.c \\\n    selinux/libsepol/src/polcaps.c \\\n    selinux/libsepol/src/policydb.c \\\n    selinux/libsepol/src/policydb_convert.c \\\n    selinux/libsepol/src/policydb_public.c \\\n    selinux/libsepol/src/policydb_validate.c \\\n    selinux/libsepol/src/port_record.c \\\n    selinux/libsepol/src/ports.c \\\n    selinux/libsepol/src/services.c \\\n    selinux/libsepol/src/sidtab.c \\\n    selinux/libsepol/src/symtab.c \\\n    selinux/libsepol/src/user_record.c \\\n    selinux/libsepol/src/users.c \\\n    selinux/libsepol/src/util.c \\\n    selinux/libsepol/src/write.c \\\n    selinux/libsepol/cil/src/cil.c \\\n    selinux/libsepol/cil/src/cil_binary.c \\\n    selinux/libsepol/cil/src/cil_build_ast.c \\\n    selinux/libsepol/cil/src/cil_copy_ast.c \\\n    selinux/libsepol/cil/src/cil_deny.c \\\n    selinux/libsepol/cil/src/cil_find.c \\\n    selinux/libsepol/cil/src/cil_fqn.c \\\n    selinux/libsepol/cil/src/cil_lexer.c \\\n    selinux/libsepol/cil/src/cil_list.c \\\n    selinux/libsepol/cil/src/cil_log.c \\\n    selinux/libsepol/cil/src/cil_mem.c \\\n    selinux/libsepol/cil/src/cil_parser.c \\\n    selinux/libsepol/cil/src/cil_policy.c \\\n    selinux/libsepol/cil/src/cil_post.c \\\n    selinux/libsepol/cil/src/cil_reset_ast.c \\\n    selinux/libsepol/cil/src/cil_resolve_ast.c \\\n    selinux/libsepol/cil/src/cil_stack.c \\\n    selinux/libsepol/cil/src/cil_strpool.c \\\n    selinux/libsepol/cil/src/cil_symtab.c \\\n    selinux/libsepol/cil/src/cil_tree.c \\\n    selinux/libsepol/cil/src/cil_verify.c \\\n    selinux/libsepol/cil/src/cil_write_ast.c\n\nLOCAL_CFLAGS := -Wno-unused-but-set-variable\nifeq ($(TARGET_ARCH),riscv64)\nLOCAL_CFLAGS += -DHAVE_REALLOCARRAY\nendif\ninclude $(BUILD_STATIC_LIBRARY)\n\n# liblsplt.a\ninclude $(CLEAR_VARS)\nLOCAL_MODULE:= liblsplt\nLOCAL_C_INCLUDES := $(LOCAL_PATH)/lsplt/lsplt/src/main/jni/include\nLOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)\nLOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -D__android_log_print=magisk_log_print\nLOCAL_CPPFLAGS := -std=c++20\nLOCAL_STATIC_LIBRARIES := libcxx\nLOCAL_SRC_FILES := \\\n    lsplt/lsplt/src/main/jni/elf_util.cc \\\n    lsplt/lsplt/src/main/jni/lsplt.cc\ninclude $(BUILD_STATIC_LIBRARY)\n\nCWD := $(LOCAL_PATH)\ninclude $(CWD)/system_properties/Android.mk\ninclude $(CWD)/libcxx/Android.mk\n\nifdef B_CRT0\ninclude $(CWD)/crt0/Android.mk\nendif\n"
  },
  {
    "path": "native/src/external/lz4-sys/Cargo.toml",
    "content": "[package]\nname = \"lz4-sys\"\nlicense = \"MIT\"\nversion = \"1.11.1+lz4-1.10.0\"\nauthors = [ \"Jens Heyens <jens.heyens@ewetel.net>\", \"Artem V. Navrotskiy <bozaro@buzzsoft.ru>\", \"Patrick Marks <pmarks@gmail.com>\"]\ndescription = \"Rust LZ4 sys package.\"\nrepository = \"https://github.com/10xGenomics/lz4-rs\"\n\n[dependencies]\nlibc = \"0.2\"\n"
  },
  {
    "path": "native/src/external/lz4-sys/src/lib.rs",
    "content": "#![allow(unexpected_cfgs)]\n#![no_std]\nextern crate libc;\n\n#[cfg(not(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n)))]\npub use libc::{c_char, c_int, c_uint, c_ulonglong, c_void, size_t};\n\n#[cfg(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n))]\nextern crate alloc;\n\n#[cfg(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n))]\nmod wasm_shim;\n\n#[cfg(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n))]\nextern crate std;\n\n#[cfg(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n))]\npub use std::os::raw::{c_char, c_int, c_uint, c_ulonglong, c_void};\n\n#[cfg(all(\n    target_arch = \"wasm32\",\n    not(any(target_env = \"wasi\", target_os = \"wasi\"))\n))]\n#[allow(non_camel_case_types)]\npub type size_t = usize;\n\n#[derive(Clone, Copy, Debug)]\n#[repr(C)]\npub struct LZ4FCompressionContext(pub *mut c_void);\nunsafe impl Send for LZ4FCompressionContext {}\n\n#[derive(Clone, Copy, Debug)]\n#[repr(C)]\npub struct LZ4FDecompressionContext(pub *mut c_void);\nunsafe impl Send for LZ4FDecompressionContext {}\n\npub type LZ4FErrorCode = size_t;\n\n#[derive(Clone, Debug)]\n#[repr(u32)]\npub enum BlockSize {\n    Default = 0, // Default - 64KB\n    Max64KB = 4,\n    Max256KB = 5,\n    Max1MB = 6,\n    Max4MB = 7,\n}\n\nimpl BlockSize {\n    pub fn get_size(&self) -> usize {\n        match self {\n            &BlockSize::Default | &BlockSize::Max64KB => 64 * 1024,\n            &BlockSize::Max256KB => 256 * 1024,\n            &BlockSize::Max1MB => 1 * 1024 * 1024,\n            &BlockSize::Max4MB => 4 * 1024 * 1024,\n        }\n    }\n}\n\n#[derive(Clone, Debug)]\n#[repr(u32)]\npub enum BlockMode {\n    Linked = 0,\n    Independent,\n}\n\n#[derive(Clone, Debug)]\n#[repr(u32)]\npub enum ContentChecksum {\n    NoChecksum = 0,\n    ChecksumEnabled,\n}\n\n#[derive(Clone, Debug)]\n#[repr(u32)]\npub enum FrameType {\n    Frame = 0,\n    SkippableFrame,\n}\n\n#[derive(Clone, Debug)]\n#[repr(u32)]\npub enum BlockChecksum {\n    NoBlockChecksum = 0,\n    BlockChecksumEnabled,\n}\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4FFrameInfo {\n    pub block_size_id: BlockSize,\n    pub block_mode: BlockMode,\n    pub content_checksum_flag: ContentChecksum,\n    pub frame_type: FrameType,\n    pub content_size: c_ulonglong,\n    pub dict_id: c_uint,\n    pub block_checksum_flag: BlockChecksum,\n}\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4FPreferences {\n    pub frame_info: LZ4FFrameInfo,\n    pub compression_level: c_uint, // 0 == default (fast mode); values above 16 count as 16\n    pub auto_flush: c_uint,        // 1 == always flush : reduce need for tmp buffer\n    pub favor_dec_speed: c_uint,   // 1 == favor decompression speed over ratio, requires level 10+\n    pub reserved: [c_uint; 3],\n}\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4FCompressOptions {\n    pub stable_src: c_uint, /* 1 == src content will remain available on future calls\n                             * to LZ4F_compress(); avoid saving src content within tmp\n                             * buffer as future dictionary */\n    pub reserved: [c_uint; 3],\n}\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4FDecompressOptions {\n    pub stable_dst: c_uint, /* guarantee that decompressed data will still be there on next\n                             * function calls (avoid storage into tmp buffers) */\n    pub reserved: [c_uint; 3],\n}\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4StreamEncode(c_void);\n\n#[derive(Debug)]\n#[repr(C)]\npub struct LZ4StreamDecode(c_void);\n\npub const LZ4F_VERSION: c_uint = 100;\n\nextern \"C\" {\n\n    // int LZ4_compress_default(const char* source, char* dest, int sourceSize, int maxDestSize);\n    #[allow(non_snake_case)]\n    pub fn LZ4_compress_default(\n        source: *const c_char,\n        dest: *mut c_char,\n        sourceSize: c_int,\n        maxDestSize: c_int,\n    ) -> c_int;\n\n    // int LZ4_compress_fast (const char* source, char* dest, int sourceSize, int maxDestSize, int acceleration);\n    #[allow(non_snake_case)]\n    pub fn LZ4_compress_fast(\n        source: *const c_char,\n        dest: *mut c_char,\n        sourceSize: c_int,\n        maxDestSize: c_int,\n        acceleration: c_int,\n    ) -> c_int;\n\n    // int LZ4_compress_HC (const char* src, char* dst, int srcSize, int dstCapacity, int compressionLevel);\n    #[allow(non_snake_case)]\n    pub fn LZ4_compress_HC(\n        src: *const c_char,\n        dst: *mut c_char,\n        srcSize: c_int,\n        dstCapacity: c_int,\n        compressionLevel: c_int,\n    ) -> c_int;\n\n    // int LZ4_decompress_safe (const char* source, char* dest, int compressedSize, int maxDecompressedSize);\n    #[allow(non_snake_case)]\n    pub fn LZ4_decompress_safe(\n        source: *const c_char,\n        dest: *mut c_char,\n        compressedSize: c_int,\n        maxDecompressedSize: c_int,\n    ) -> c_int;\n\n    // unsigned    LZ4F_isError(LZ4F_errorCode_t code);\n    pub fn LZ4F_isError(code: size_t) -> c_uint;\n\n    // const char* LZ4F_getErrorName(LZ4F_errorCode_t code);\n    pub fn LZ4F_getErrorName(code: size_t) -> *const c_char;\n\n    // LZ4F_createCompressionContext() :\n    // The first thing to do is to create a compressionContext object, which will be used in all\n    // compression operations.\n    // This is achieved using LZ4F_createCompressionContext(), which takes as argument a version\n    // and an LZ4F_preferences_t structure.\n    // The version provided MUST be LZ4F_VERSION. It is intended to track potential version\n    // differences between different binaries.\n    // The function will provide a pointer to a fully allocated LZ4F_compressionContext_t object.\n    // If the result LZ4F_errorCode_t is not zero, there was an error during context creation.\n    // Object can release its memory using LZ4F_freeCompressionContext();\n    //\n    // LZ4F_errorCode_t LZ4F_createCompressionContext(\n    //                                   LZ4F_compressionContext_t* LZ4F_compressionContextPtr,\n    //                                   unsigned version);\n    pub fn LZ4F_createCompressionContext(\n        ctx: &mut LZ4FCompressionContext,\n        version: c_uint,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_errorCode_t LZ4F_freeCompressionContext(\n    //                                  LZ4F_compressionContext_t LZ4F_compressionContext);\n    pub fn LZ4F_freeCompressionContext(ctx: LZ4FCompressionContext) -> LZ4FErrorCode;\n\n    // LZ4F_compressBegin() :\n    // will write the frame header into dstBuffer.\n    // dstBuffer must be large enough to accommodate a header (dstMaxSize). Maximum header\n    // size is 19 bytes.\n    // The LZ4F_preferences_t structure is optional : you can provide NULL as argument, all\n    // preferences will then be set to default.\n    // The result of the function is the number of bytes written into dstBuffer for the header\n    // or an error code (can be tested using LZ4F_isError())\n    //\n    // size_t LZ4F_compressBegin(LZ4F_compressionContext_t compressionContext,\n    //                           void* dstBuffer,\n    //                           size_t dstMaxSize,\n    //                           const LZ4F_preferences_t* preferencesPtr);\n    pub fn LZ4F_compressBegin(\n        ctx: LZ4FCompressionContext,\n        dstBuffer: *mut u8,\n        dstMaxSize: size_t,\n        preferencesPtr: *const LZ4FPreferences,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_compressBound() :\n    // Provides the minimum size of Dst buffer given srcSize to handle worst case situations.\n    // preferencesPtr is optional : you can provide NULL as argument, all preferences will then\n    // be set to default.\n    // Note that different preferences will produce in different results.\n    //\n    // size_t LZ4F_compressBound(size_t srcSize, const LZ4F_preferences_t* preferencesPtr);\n    pub fn LZ4F_compressBound(\n        srcSize: size_t,\n        preferencesPtr: *const LZ4FPreferences,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_compressUpdate()\n    // LZ4F_compressUpdate() can be called repetitively to compress as much data as necessary.\n    // The most important rule is that dstBuffer MUST be large enough (dstMaxSize) to ensure\n    // compression completion even in worst case.\n    // If this condition is not respected, LZ4F_compress() will fail (result is an errorCode)\n    // You can get the minimum value of dstMaxSize by using LZ4F_compressBound()\n    // The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument.\n    // The result of the function is the number of bytes written into dstBuffer : it can be zero,\n    // meaning input data was just buffered.\n    // The function outputs an error code if it fails (can be tested using LZ4F_isError())\n    //\n    // size_t LZ4F_compressUpdate(LZ4F_compressionContext_t compressionContext,\n    //                            void* dstBuffer,\n    //                            size_t dstMaxSize,\n    //                            const void* srcBuffer,\n    //                            size_t srcSize,\n    //                            const LZ4F_compressOptions_t* compressOptionsPtr);\n    pub fn LZ4F_compressUpdate(\n        ctx: LZ4FCompressionContext,\n        dstBuffer: *mut u8,\n        dstMaxSize: size_t,\n        srcBuffer: *const u8,\n        srcSize: size_t,\n        compressOptionsPtr: *const LZ4FCompressOptions,\n    ) -> size_t;\n\n    // LZ4F_flush()\n    // Should you need to create compressed data immediately, without waiting for a block\n    // to be be filled, you can call LZ4_flush(), which will immediately compress any remaining\n    // data buffered within compressionContext.\n    // The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument.\n    // The result of the function is the number of bytes written into dstBuffer\n    // (it can be zero, this means there was no data left within compressionContext)\n    // The function outputs an error code if it fails (can be tested using LZ4F_isError())\n    //\n    // size_t LZ4F_flush(LZ4F_compressionContext_t compressionContext,\n    //                   void* dstBuffer,\n    //                   size_t dstMaxSize,\n    //                   const LZ4F_compressOptions_t* compressOptionsPtr);\n    pub fn LZ4F_flush(\n        ctx: LZ4FCompressionContext,\n        dstBuffer: *mut u8,\n        dstMaxSize: size_t,\n        compressOptionsPtr: *const LZ4FCompressOptions,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_compressEnd()\n    // When you want to properly finish the compressed frame, just call LZ4F_compressEnd().\n    // It will flush whatever data remained within compressionContext (like LZ4_flush())\n    // but also properly finalize the frame, with an endMark and a checksum.\n    // The result of the function is the number of bytes written into dstBuffer\n    // (necessarily >= 4 (endMark size))\n    // The function outputs an error code if it fails (can be tested using LZ4F_isError())\n    // The LZ4F_compressOptions_t structure is optional : you can provide NULL as argument.\n    // compressionContext can then be used again, starting with LZ4F_compressBegin().\n    //\n    // size_t LZ4F_compressEnd(LZ4F_compressionContext_t compressionContext,\n    //                         void* dstBuffer,\n    //                         size_t dstMaxSize,\n    //                         const LZ4F_compressOptions_t* compressOptionsPtr);\n    pub fn LZ4F_compressEnd(\n        ctx: LZ4FCompressionContext,\n        dstBuffer: *mut u8,\n        dstMaxSize: size_t,\n        compressOptionsPtr: *const LZ4FCompressOptions,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_createDecompressionContext() :\n    // The first thing to do is to create a decompressionContext object, which will be used\n    // in all decompression operations.\n    // This is achieved using LZ4F_createDecompressionContext().\n    // The version provided MUST be LZ4F_VERSION. It is intended to track potential version\n    // differences between different binaries.\n    // The function will provide a pointer to a fully allocated and initialized\n    // LZ4F_decompressionContext_t object.\n    // If the result LZ4F_errorCode_t is not OK_NoError, there was an error during\n    // context creation.\n    // Object can release its memory using LZ4F_freeDecompressionContext();\n    //\n    // LZ4F_errorCode_t LZ4F_createDecompressionContext(LZ4F_decompressionContext_t* ctxPtr,\n    //                                                  unsigned version);\n    pub fn LZ4F_createDecompressionContext(\n        ctx: &mut LZ4FDecompressionContext,\n        version: c_uint,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_errorCode_t LZ4F_freeDecompressionContext(LZ4F_decompressionContext_t ctx);\n    pub fn LZ4F_freeDecompressionContext(ctx: LZ4FDecompressionContext) -> LZ4FErrorCode;\n\n    // LZ4F_getFrameInfo()\n    // This function decodes frame header information, such as blockSize.\n    // It is optional : you could start by calling directly LZ4F_decompress() instead.\n    // The objective is to extract header information without starting decompression, typically\n    // for allocation purposes.\n    // LZ4F_getFrameInfo() can also be used *after* starting decompression, on a\n    // valid LZ4F_decompressionContext_t.\n    // The number of bytes read from srcBuffer will be provided within *srcSizePtr\n    // (necessarily <= original value).\n    // You are expected to resume decompression from where it stopped (srcBuffer + *srcSizePtr)\n    // The function result is an hint of how many srcSize bytes LZ4F_decompress() expects for\n    // next call, or an error code which can be tested using LZ4F_isError().\n    //\n    // size_t LZ4F_getFrameInfo(LZ4F_decompressionContext_t ctx,\n    // \t\t\t\t\tLZ4F_frameInfo_t* frameInfoPtr,\n    // \t\t\t\t\tconst void* srcBuffer, size_t* srcSizePtr);\n    pub fn LZ4F_getFrameInfo(\n        ctx: LZ4FDecompressionContext,\n        frameInfoPtr: &mut LZ4FFrameInfo,\n        srcBuffer: *const u8,\n        srcSizePtr: &mut size_t,\n    ) -> LZ4FErrorCode;\n\n    // LZ4F_decompress()\n    // Call this function repetitively to regenerate data compressed within srcBuffer.\n    // The function will attempt to decode *srcSizePtr bytes from srcBuffer, into dstBuffer of\n    // maximum size *dstSizePtr.\n    //\n    // The number of bytes regenerated into dstBuffer will be provided within *dstSizePtr\n    // (necessarily <= original value).\n    //\n    // The number of bytes read from srcBuffer will be provided within *srcSizePtr\n    // (necessarily <= original value).\n    // If number of bytes read is < number of bytes provided, then decompression operation\n    // is not completed. It typically happens when dstBuffer is not large enough to contain\n    // all decoded data.\n    // LZ4F_decompress() must be called again, starting from where it stopped\n    // (srcBuffer + *srcSizePtr)\n    // The function will check this condition, and refuse to continue if it is not respected.\n    //\n    // dstBuffer is supposed to be flushed between each call to the function, since its content\n    // will be overwritten.\n    // dst arguments can be changed at will with each consecutive call to the function.\n    //\n    // The function result is an hint of how many srcSize bytes LZ4F_decompress() expects for\n    // next call.\n    // Schematically, it's the size of the current (or remaining) compressed block + header of\n    // next block.\n    // Respecting the hint provides some boost to performance, since it does skip intermediate\n    // buffers.\n    // This is just a hint, you can always provide any srcSize you want.\n    // When a frame is fully decoded, the function result will be 0. (no more data expected)\n    // If decompression failed, function result is an error code, which can be tested\n    // using LZ4F_isError().\n    //\n    // size_t LZ4F_decompress(LZ4F_decompressionContext_t ctx,\n    //                        void* dstBuffer, size_t* dstSizePtr,\n    //                        const void* srcBuffer, size_t* srcSizePtr,\n    //                        const LZ4F_decompressOptions_t* optionsPtr);\n    pub fn LZ4F_decompress(\n        ctx: LZ4FDecompressionContext,\n        dstBuffer: *mut u8,\n        dstSizePtr: &mut size_t,\n        srcBuffer: *const u8,\n        srcSizePtr: &mut size_t,\n        optionsPtr: *const LZ4FDecompressOptions,\n    ) -> LZ4FErrorCode;\n\n    // int LZ4_versionNumber(void)\n    pub fn LZ4_versionNumber() -> c_int;\n\n    // int LZ4_compressBound(int isize)\n    pub fn LZ4_compressBound(size: c_int) -> c_int;\n\n    // LZ4_stream_t* LZ4_createStream(void)\n    pub fn LZ4_createStream() -> *mut LZ4StreamEncode;\n\n    // int LZ4_compress_continue(LZ4_stream_t* LZ4_streamPtr,\n    //                           const char* source,\n    //                           char* dest,\n    //                           int inputSize)\n    pub fn LZ4_compress_continue(\n        LZ4_stream: *mut LZ4StreamEncode,\n        source: *const u8,\n        dest: *mut u8,\n        input_size: c_int,\n    ) -> c_int;\n\n    // int LZ4_freeStream(LZ4_stream_t* LZ4_streamPtr)\n    pub fn LZ4_freeStream(LZ4_stream: *mut LZ4StreamEncode) -> c_int;\n\n    // int LZ4_setStreamDecode (LZ4_streamDecode_t* LZ4_streamDecode,\n    //                          const char* dictionary,\n    //                          int dictSize)\n    pub fn LZ4_setStreamDecode(\n        LZ4_stream: *mut LZ4StreamDecode,\n        dictionary: *const u8,\n        dict_size: c_int,\n    ) -> c_int;\n\n    // LZ4_streamDecode_t* LZ4_createStreamDecode(void)\n    pub fn LZ4_createStreamDecode() -> *mut LZ4StreamDecode;\n\n    // int LZ4_decompress_safe_continue(LZ4_streamDecode_t* LZ4_streamDecode,\n    //                                  const char* source,\n    //                                  char* dest,\n    //                                  int compressedSize,\n    //                                  int maxDecompressedSize)\n    pub fn LZ4_decompress_safe_continue(\n        LZ4_stream: *mut LZ4StreamDecode,\n        source: *const u8,\n        dest: *mut u8,\n        compressed_size: c_int,\n        max_decompressed_size: c_int,\n    ) -> c_int;\n\n    // int LZ4_freeStreamDecode(LZ4_streamDecode_t* LZ4_stream)\n    pub fn LZ4_freeStreamDecode(LZ4_stream: *mut LZ4StreamDecode) -> c_int;\n\n    // LZ4F_resetDecompressionContext()\n    // In case of an error, the context is left in \"undefined\" state.\n    // In which case, it's necessary to reset it, before re-using it.\n    // This method can also be used to abruptly stop any unfinished decompression,\n    // and start a new one using same context resources.\n    pub fn LZ4F_resetDecompressionContext(ctx: LZ4FDecompressionContext);\n\n}\n\n#[test]\nfn test_version_number() {\n    unsafe {\n        LZ4_versionNumber();\n    }\n}\n\n#[test]\nfn test_frame_info_size() {\n    assert_eq!(core::mem::size_of::<LZ4FFrameInfo>(), 32);\n}\n"
  },
  {
    "path": "native/src/external/lz4-sys/src/wasm_shim.rs",
    "content": "//! A shim for the libc functions used in lz4-rs that are not available when building for wasm\n//! targets. Adapted from the shim present in the [zstd](https://github.com/gyscos/zstd-rs) crate.\n//! zstd-rs license here:\n//! The MIT License (MIT)\n//! Copyright (c) 2016 Alexandre Bury\n//!\n//! Permission is hereby granted, free of charge, to any person obtaining a copy of this software\n//! and associated documentation files (the \"Software\"), to deal in the Software without\n//! restriction, including without limitation the rights to use, copy, modify, merge, publish,\n//! distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the\n//! Software is furnished to do so, subject to the following conditions:\n//!\n//! The above copyright notice and this permission notice shall be included in all copies or\n//! substantial portions of the Software.\n//!\n//! THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING\n//! BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n//! NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\n//! DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\nuse alloc::alloc::{alloc, alloc_zeroed, dealloc, Layout};\nuse core::ffi::{c_int, c_void};\n\nconst USIZE_ALIGN: usize = core::mem::align_of::<usize>();\nconst USIZE_SIZE: usize = core::mem::size_of::<usize>();\n\n#[no_mangle]\npub extern \"C\" fn rust_lz4_wasm_shim_malloc(size: usize) -> *mut c_void {\n    wasm_shim_alloc::<false>(size)\n}\n\n#[no_mangle]\npub extern \"C\" fn rust_lz4_wasm_shim_memcmp(\n    str1: *const c_void,\n    str2: *const c_void,\n    n: usize,\n) -> i32 {\n    // Safety: function contracts requires str1 and str2 at least `n`-long.\n    unsafe {\n        let str1: &[u8] = core::slice::from_raw_parts(str1 as *const u8, n);\n        let str2: &[u8] = core::slice::from_raw_parts(str2 as *const u8, n);\n        match str1.cmp(str2) {\n            core::cmp::Ordering::Less => -1,\n            core::cmp::Ordering::Equal => 0,\n            core::cmp::Ordering::Greater => 1,\n        }\n    }\n}\n\n#[no_mangle]\npub extern \"C\" fn rust_lz4_wasm_shim_calloc(nmemb: usize, size: usize) -> *mut c_void {\n    // note: calloc expects the allocation to be zeroed\n    wasm_shim_alloc::<true>(nmemb * size)\n}\n\n#[inline]\nfn wasm_shim_alloc<const ZEROED: bool>(size: usize) -> *mut c_void {\n    // in order to recover the size upon free, we store the size below the allocation\n    // special alignment is never requested via the malloc API,\n    // so it's not stored, and usize-alignment is used\n    // memory layout: [size] [allocation]\n\n    let full_alloc_size = size + USIZE_SIZE;\n\n    unsafe {\n        let layout = Layout::from_size_align_unchecked(full_alloc_size, USIZE_ALIGN);\n\n        let ptr = if ZEROED {\n            alloc_zeroed(layout)\n        } else {\n            alloc(layout)\n        };\n\n        // SAFETY: ptr is usize-aligned and we've allocated sufficient memory\n        ptr.cast::<usize>().write(full_alloc_size);\n\n        ptr.add(USIZE_SIZE).cast()\n    }\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn rust_lz4_wasm_shim_free(ptr: *mut c_void) {\n    // the layout for the allocation needs to be recovered for dealloc\n    // - the size must be recovered from directly below the allocation\n    // - the alignment will always by USIZE_ALIGN\n\n    let alloc_ptr = ptr.sub(USIZE_SIZE);\n    // SAFETY: the allocation routines must uphold having a valid usize below the provided pointer\n    let full_alloc_size = alloc_ptr.cast::<usize>().read();\n\n    let layout = Layout::from_size_align_unchecked(full_alloc_size, USIZE_ALIGN);\n    dealloc(alloc_ptr.cast(), layout);\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn rust_lz4_wasm_shim_memcpy(\n    dest: *mut c_void,\n    src: *const c_void,\n    n: usize,\n) -> *mut c_void {\n    core::ptr::copy_nonoverlapping(src as *const u8, dest as *mut u8, n);\n    dest\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn rust_lz4_wasm_shim_memmove(\n    dest: *mut c_void,\n    src: *const c_void,\n    n: usize,\n) -> *mut c_void {\n    core::ptr::copy(src as *const u8, dest as *mut u8, n);\n    dest\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn rust_lz4_wasm_shim_memset(\n    dest: *mut c_void,\n    c: c_int,\n    n: usize,\n) -> *mut c_void {\n    core::ptr::write_bytes(dest as *mut u8, c as u8, n);\n    dest\n}\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz.h",
    "content": "/*\n * XZ decompressor\n *\n * Authors: Lasse Collin <lasse.collin@tukaani.org>\n *          Igor Pavlov <http://7-zip.org/>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#ifndef XZ_H\n#define XZ_H\n\n#ifdef __KERNEL__\n#\tinclude <linux/stddef.h>\n#\tinclude <linux/types.h>\n#else\n#\tinclude <stddef.h>\n#\tinclude <stdint.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* In Linux, this is used to make extern functions static when needed. */\n#ifndef XZ_EXTERN\n#\tdefine XZ_EXTERN extern\n#endif\n\n/**\n * enum xz_mode - Operation mode\n *\n * @XZ_SINGLE:              Single-call mode. This uses less RAM than\n *                          than multi-call modes, because the LZMA2\n *                          dictionary doesn't need to be allocated as\n *                          part of the decoder state. All required data\n *                          structures are allocated at initialization,\n *                          so xz_dec_run() cannot return XZ_MEM_ERROR.\n * @XZ_PREALLOC:            Multi-call mode with preallocated LZMA2\n *                          dictionary buffer. All data structures are\n *                          allocated at initialization, so xz_dec_run()\n *                          cannot return XZ_MEM_ERROR.\n * @XZ_DYNALLOC:            Multi-call mode. The LZMA2 dictionary is\n *                          allocated once the required size has been\n *                          parsed from the stream headers. If the\n *                          allocation fails, xz_dec_run() will return\n *                          XZ_MEM_ERROR.\n *\n * It is possible to enable support only for a subset of the above\n * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC,\n * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled\n * with support for all operation modes, but the preboot code may\n * be built with fewer features to minimize code size.\n */\nenum xz_mode {\n    XZ_SINGLE,\n    XZ_PREALLOC,\n    XZ_DYNALLOC\n};\n\n/**\n * enum xz_ret - Return codes\n * @XZ_OK:                  Everything is OK so far. More input or more\n *                          output space is required to continue. This\n *                          return code is possible only in multi-call mode\n *                          (XZ_PREALLOC or XZ_DYNALLOC).\n * @XZ_STREAM_END:          Operation finished successfully.\n * @XZ_UNSUPPORTED_CHECK:   Integrity check type is not supported. Decoding\n *                          is still possible in multi-call mode by simply\n *                          calling xz_dec_run() again.\n *                          Note that this return value is used only if\n *                          XZ_DEC_ANY_CHECK was defined at build time,\n *                          which is not used in the kernel. Unsupported\n *                          check types return XZ_OPTIONS_ERROR if\n *                          XZ_DEC_ANY_CHECK was not defined at build time.\n * @XZ_MEM_ERROR:           Allocating memory failed. This return code is\n *                          possible only if the decoder was initialized\n *                          with XZ_DYNALLOC. The amount of memory that was\n *                          tried to be allocated was no more than the\n *                          dict_max argument given to xz_dec_init().\n * @XZ_MEMLIMIT_ERROR:      A bigger LZMA2 dictionary would be needed than\n *                          allowed by the dict_max argument given to\n *                          xz_dec_init(). This return value is possible\n *                          only in multi-call mode (XZ_PREALLOC or\n *                          XZ_DYNALLOC); the single-call mode (XZ_SINGLE)\n *                          ignores the dict_max argument.\n * @XZ_FORMAT_ERROR:        File format was not recognized (wrong magic\n *                          bytes).\n * @XZ_OPTIONS_ERROR:       This implementation doesn't support the requested\n *                          compression options. In the decoder this means\n *                          that the header CRC32 matches, but the header\n *                          itself specifies something that we don't support.\n * @XZ_DATA_ERROR:          Compressed data is corrupt.\n * @XZ_BUF_ERROR:           Cannot make any progress. Details are slightly\n *                          different between multi-call and single-call\n *                          mode; more information below.\n *\n * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls\n * to XZ code cannot consume any input and cannot produce any new output.\n * This happens when there is no new input available, or the output buffer\n * is full while at least one output byte is still pending. Assuming your\n * code is not buggy, you can get this error only when decoding a compressed\n * stream that is truncated or otherwise corrupt.\n *\n * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer\n * is too small or the compressed input is corrupt in a way that makes the\n * decoder produce more output than the caller expected. When it is\n * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR\n * is used instead of XZ_BUF_ERROR.\n */\nenum xz_ret {\n    XZ_OK,\n    XZ_STREAM_END,\n    XZ_UNSUPPORTED_CHECK,\n    XZ_MEM_ERROR,\n    XZ_MEMLIMIT_ERROR,\n    XZ_FORMAT_ERROR,\n    XZ_OPTIONS_ERROR,\n    XZ_DATA_ERROR,\n    XZ_BUF_ERROR\n};\n\n/**\n * struct xz_buf - Passing input and output buffers to XZ code\n * @in:         Beginning of the input buffer. This may be NULL if and only\n *              if in_pos is equal to in_size.\n * @in_pos:     Current position in the input buffer. This must not exceed\n *              in_size.\n * @in_size:    Size of the input buffer\n * @out:        Beginning of the output buffer. This may be NULL if and only\n *              if out_pos is equal to out_size.\n * @out_pos:    Current position in the output buffer. This must not exceed\n *              out_size.\n * @out_size:   Size of the output buffer\n *\n * Only the contents of the output buffer from out[out_pos] onward, and\n * the variables in_pos and out_pos are modified by the XZ code.\n */\nstruct xz_buf {\n    const uint8_t *in;\n    size_t in_pos;\n    size_t in_size;\n\n    uint8_t *out;\n    size_t out_pos;\n    size_t out_size;\n};\n\n/**\n * struct xz_dec - Opaque type to hold the XZ decoder state\n */\nstruct xz_dec;\n\n/**\n * xz_dec_init() - Allocate and initialize a XZ decoder state\n * @mode:       Operation mode\n * @dict_max:   Maximum size of the LZMA2 dictionary (history buffer) for\n *              multi-call decoding. This is ignored in single-call mode\n *              (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes\n *              or 2^n + 2^(n-1) bytes (the latter sizes are less common\n *              in practice), so other values for dict_max don't make sense.\n *              In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB,\n *              512 KiB, and 1 MiB are probably the only reasonable values,\n *              except for kernel and initramfs images where a bigger\n *              dictionary can be fine and useful.\n *\n * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at\n * once. The caller must provide enough output space or the decoding will\n * fail. The output space is used as the dictionary buffer, which is why\n * there is no need to allocate the dictionary as part of the decoder's\n * internal state.\n *\n * Because the output buffer is used as the workspace, streams encoded using\n * a big dictionary are not a problem in single-call mode. It is enough that\n * the output buffer is big enough to hold the actual uncompressed data; it\n * can be smaller than the dictionary size stored in the stream headers.\n *\n * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes\n * of memory is preallocated for the LZMA2 dictionary. This way there is no\n * risk that xz_dec_run() could run out of memory, since xz_dec_run() will\n * never allocate any memory. Instead, if the preallocated dictionary is too\n * small for decoding the given input stream, xz_dec_run() will return\n * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be\n * decoded to avoid allocating excessive amount of memory for the dictionary.\n *\n * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC):\n * dict_max specifies the maximum allowed dictionary size that xz_dec_run()\n * may allocate once it has parsed the dictionary size from the stream\n * headers. This way excessive allocations can be avoided while still\n * limiting the maximum memory usage to a sane value to prevent running the\n * system out of memory when decompressing streams from untrusted sources.\n *\n * On success, xz_dec_init() returns a pointer to struct xz_dec, which is\n * ready to be used with xz_dec_run(). If memory allocation fails,\n * xz_dec_init() returns NULL.\n */\nXZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max);\n\n/**\n * xz_dec_run() - Run the XZ decoder\n * @s:          Decoder state allocated using xz_dec_init()\n * @b:          Input and output buffers\n *\n * The possible return values depend on build options and operation mode.\n * See enum xz_ret for details.\n *\n * Note that if an error occurs in single-call mode (return value is not\n * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the\n * contents of the output buffer from b->out[b->out_pos] onward are\n * undefined. This is true even after XZ_BUF_ERROR, because with some filter\n * chains, there may be a second pass over the output buffer, and this pass\n * cannot be properly done if the output buffer is truncated. Thus, you\n * cannot give the single-call decoder a too small buffer and then expect to\n * get that amount valid data from the beginning of the stream. You must use\n * the multi-call decoder if you don't want to uncompress the whole stream.\n */\nXZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b);\n\n/**\n * xz_dec_reset() - Reset an already allocated decoder state\n * @s:          Decoder state allocated using xz_dec_init()\n *\n * This function can be used to reset the multi-call decoder state without\n * freeing and reallocating memory with xz_dec_end() and xz_dec_init().\n *\n * In single-call mode, xz_dec_reset() is always called in the beginning of\n * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in\n * multi-call mode.\n */\nXZ_EXTERN void xz_dec_reset(struct xz_dec *s);\n\n/**\n * xz_dec_end() - Free the memory allocated for the decoder state\n * @s:          Decoder state allocated using xz_dec_init(). If s is NULL,\n *              this function does nothing.\n */\nXZ_EXTERN void xz_dec_end(struct xz_dec *s);\n\n/*\n * Standalone build (userspace build or in-kernel build for boot time use)\n * needs a CRC32 implementation. For normal in-kernel use, kernel's own\n * CRC32 module is used instead, and users of this module don't need to\n * care about the functions below.\n */\n#ifndef XZ_INTERNAL_CRC32\n#\tifdef __KERNEL__\n#\t\tdefine XZ_INTERNAL_CRC32 0\n#\telse\n#\t\tdefine XZ_INTERNAL_CRC32 1\n#\tendif\n#endif\n\n/*\n * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64\n * implementation is needed too.\n */\n#ifndef XZ_USE_CRC64\n#\tundef XZ_INTERNAL_CRC64\n#\tdefine XZ_INTERNAL_CRC64 0\n#endif\n#ifndef XZ_INTERNAL_CRC64\n#\tifdef __KERNEL__\n#\t\terror Using CRC64 in the kernel has not been implemented.\n#\telse\n#\t\tdefine XZ_INTERNAL_CRC64 1\n#\tendif\n#endif\n\n#if XZ_INTERNAL_CRC32\n/*\n * This must be called before any other xz_* function to initialize\n * the CRC32 lookup table.\n */\nXZ_EXTERN void xz_crc32_init(void);\n\n/*\n * Update CRC32 value using the polynomial from IEEE-802.3. To start a new\n * calculation, the third argument must be zero. To continue the calculation,\n * the previously returned value is passed as the third argument.\n */\nXZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc);\n#endif\n\n#if XZ_INTERNAL_CRC64\n/*\n * This must be called before any other xz_* function (except xz_crc32_init())\n * to initialize the CRC64 lookup table.\n */\nXZ_EXTERN void xz_crc64_init(void);\n\n/*\n * Update CRC64 value using the polynomial from ECMA-182. To start a new\n * calculation, the third argument must be zero. To continue the calculation,\n * the previously returned value is passed as the third argument.\n */\nXZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc);\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_config.h",
    "content": "/*\n * Private includes and definitions for userspace use of XZ Embedded\n *\n * Author: Lasse Collin <lasse.collin@tukaani.org>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#ifndef XZ_CONFIG_H\n#define XZ_CONFIG_H\n\n/* Uncomment to enable CRC64 support. */\n/* #define XZ_USE_CRC64 */\n\n/* Uncomment as needed to enable BCJ filter decoders. */\n/* #define XZ_DEC_X86 */\n/* #define XZ_DEC_POWERPC */\n/* #define XZ_DEC_IA64 */\n/* #define XZ_DEC_ARM */\n/* #define XZ_DEC_ARMTHUMB */\n/* #define XZ_DEC_SPARC */\n\n/*\n * MSVC doesn't support modern C but XZ Embedded is mostly C89\n * so these are enough.\n */\n#ifdef _MSC_VER\ntypedef unsigned char bool;\n#\tdefine true 1\n#\tdefine false 0\n#\tdefine inline __inline\n#else\n#\tinclude <stdbool.h>\n#endif\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"xz.h\"\n\n#define kmalloc(size, flags) malloc(size)\n#define kfree(ptr) free(ptr)\n#define vmalloc(size) malloc(size)\n#define vfree(ptr) free(ptr)\n\n#define memeq(a, b, size) (memcmp(a, b, size) == 0)\n#define memzero(buf, size) memset(buf, 0, size)\n\n#ifndef min\n#\tdefine min(x, y) ((x) < (y) ? (x) : (y))\n#endif\n#define min_t(type, x, y) min(x, y)\n\n/*\n * Some functions have been marked with __always_inline to keep the\n * performance reasonable even when the compiler is optimizing for\n * small code size. You may be able to save a few bytes by #defining\n * __always_inline to plain inline, but don't complain if the code\n * becomes slow.\n *\n * NOTE: System headers on GNU/Linux may #define this macro already,\n * so if you want to change it, you need to #undef it first.\n */\n#ifndef __always_inline\n#\tifdef __GNUC__\n#\t\tdefine __always_inline \\\n            inline __attribute__((__always_inline__))\n#\telse\n#\t\tdefine __always_inline inline\n#\tendif\n#endif\n\n/* Inline functions to access unaligned unsigned 32-bit integers */\n#ifndef get_unaligned_le32\nstatic inline uint32_t get_unaligned_le32(const uint8_t *buf)\n{\n    return (uint32_t)buf[0]\n            | ((uint32_t)buf[1] << 8)\n            | ((uint32_t)buf[2] << 16)\n            | ((uint32_t)buf[3] << 24);\n}\n#endif\n\n#ifndef get_unaligned_be32\nstatic inline uint32_t get_unaligned_be32(const uint8_t *buf)\n{\n    return (uint32_t)(buf[0] << 24)\n            | ((uint32_t)buf[1] << 16)\n            | ((uint32_t)buf[2] << 8)\n            | (uint32_t)buf[3];\n}\n#endif\n\n#ifndef put_unaligned_le32\nstatic inline void put_unaligned_le32(uint32_t val, uint8_t *buf)\n{\n    buf[0] = (uint8_t)val;\n    buf[1] = (uint8_t)(val >> 8);\n    buf[2] = (uint8_t)(val >> 16);\n    buf[3] = (uint8_t)(val >> 24);\n}\n#endif\n\n#ifndef put_unaligned_be32\nstatic inline void put_unaligned_be32(uint32_t val, uint8_t *buf)\n{\n    buf[0] = (uint8_t)(val >> 24);\n    buf[1] = (uint8_t)(val >> 16);\n    buf[2] = (uint8_t)(val >> 8);\n    buf[3] = (uint8_t)val;\n}\n#endif\n\n/*\n * Use get_unaligned_le32() also for aligned access for simplicity. On\n * little endian systems, #define get_le32(ptr) (*(const uint32_t *)(ptr))\n * could save a few bytes in code size.\n */\n#ifndef get_le32\n#\tdefine get_le32 get_unaligned_le32\n#endif\n\n#endif\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_crc32.c",
    "content": "/*\n * CRC32 using the polynomial from IEEE-802.3\n *\n * Authors: Lasse Collin <lasse.collin@tukaani.org>\n *          Igor Pavlov <http://7-zip.org/>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n/*\n * This is not the fastest implementation, but it is pretty compact.\n * The fastest versions of xz_crc32() on modern CPUs without hardware\n * accelerated CRC instruction are 3-5 times as fast as this version,\n * but they are bigger and use more memory for the lookup table.\n */\n\n#include \"xz_private.h\"\n\n/*\n * STATIC_RW_DATA is used in the pre-boot environment on some architectures.\n * See <linux/decompress/mm.h> for details.\n */\n#ifndef STATIC_RW_DATA\n#\tdefine STATIC_RW_DATA static\n#endif\n\nSTATIC_RW_DATA uint32_t xz_crc32_table[256];\n\nXZ_EXTERN void xz_crc32_init(void)\n{\n    const uint32_t poly = 0xEDB88320;\n\n    uint32_t i;\n    uint32_t j;\n    uint32_t r;\n\n    for (i = 0; i < 256; ++i) {\n        r = i;\n        for (j = 0; j < 8; ++j)\n            r = (r >> 1) ^ (poly & ~((r & 1) - 1));\n\n        xz_crc32_table[i] = r;\n    }\n\n    return;\n}\n\nXZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc)\n{\n    crc = ~crc;\n\n    while (size != 0) {\n        crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8);\n        --size;\n    }\n\n    return ~crc;\n}\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_dec_lzma2.c",
    "content": "/*\n * LZMA2 decoder\n *\n * Authors: Lasse Collin <lasse.collin@tukaani.org>\n *          Igor Pavlov <http://7-zip.org/>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#include \"xz_private.h\"\n#include \"xz_lzma2.h\"\n\n/*\n * Range decoder initialization eats the first five bytes of each LZMA chunk.\n */\n#define RC_INIT_BYTES 5\n\n/*\n * Minimum number of usable input buffer to safely decode one LZMA symbol.\n * The worst case is that we decode 22 bits using probabilities and 26\n * direct bits. This may decode at maximum of 20 bytes of input. However,\n * lzma_main() does an extra normalization before returning, thus we\n * need to put 21 here.\n */\n#define LZMA_IN_REQUIRED 21\n\n/*\n * Dictionary (history buffer)\n *\n * These are always true:\n *    start <= pos <= full <= end\n *    pos <= limit <= end\n *\n * In multi-call mode, also these are true:\n *    end == size\n *    size <= size_max\n *    allocated <= size\n *\n * Most of these variables are size_t to support single-call mode,\n * in which the dictionary variables address the actual output\n * buffer directly.\n */\nstruct dictionary {\n    /* Beginning of the history buffer */\n    uint8_t *buf;\n\n    /* Old position in buf (before decoding more data) */\n    size_t start;\n\n    /* Position in buf */\n    size_t pos;\n\n    /*\n     * How full dictionary is. This is used to detect corrupt input that\n     * would read beyond the beginning of the uncompressed stream.\n     */\n    size_t full;\n\n    /* Write limit; we don't write to buf[limit] or later bytes. */\n    size_t limit;\n\n    /*\n     * End of the dictionary buffer. In multi-call mode, this is\n     * the same as the dictionary size. In single-call mode, this\n     * indicates the size of the output buffer.\n     */\n    size_t end;\n\n    /*\n     * Size of the dictionary as specified in Block Header. This is used\n     * together with \"full\" to detect corrupt input that would make us\n     * read beyond the beginning of the uncompressed stream.\n     */\n    uint32_t size;\n\n    /*\n     * Maximum allowed dictionary size in multi-call mode.\n     * This is ignored in single-call mode.\n     */\n    uint32_t size_max;\n\n    /*\n     * Amount of memory currently allocated for the dictionary.\n     * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC,\n     * size_max is always the same as the allocated size.)\n     */\n    uint32_t allocated;\n\n    /* Operation mode */\n    enum xz_mode mode;\n};\n\n/* Range decoder */\nstruct rc_dec {\n    uint32_t range;\n    uint32_t code;\n\n    /*\n     * Number of initializing bytes remaining to be read\n     * by rc_read_init().\n     */\n    uint32_t init_bytes_left;\n\n    /*\n     * Buffer from which we read our input. It can be either\n     * temp.buf or the caller-provided input buffer.\n     */\n    const uint8_t *in;\n    size_t in_pos;\n    size_t in_limit;\n};\n\n/* Probabilities for a length decoder. */\nstruct lzma_len_dec {\n    /* Probability of match length being at least 10 */\n    uint16_t choice;\n\n    /* Probability of match length being at least 18 */\n    uint16_t choice2;\n\n    /* Probabilities for match lengths 2-9 */\n    uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS];\n\n    /* Probabilities for match lengths 10-17 */\n    uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS];\n\n    /* Probabilities for match lengths 18-273 */\n    uint16_t high[LEN_HIGH_SYMBOLS];\n};\n\nstruct lzma_dec {\n    /* Distances of latest four matches */\n    uint32_t rep0;\n    uint32_t rep1;\n    uint32_t rep2;\n    uint32_t rep3;\n\n    /* Types of the most recently seen LZMA symbols */\n    enum lzma_state state;\n\n    /*\n     * Length of a match. This is updated so that dict_repeat can\n     * be called again to finish repeating the whole match.\n     */\n    uint32_t len;\n\n    /*\n     * LZMA properties or related bit masks (number of literal\n     * context bits, a mask dervied from the number of literal\n     * position bits, and a mask dervied from the number\n     * position bits)\n     */\n    uint32_t lc;\n    uint32_t literal_pos_mask; /* (1 << lp) - 1 */\n    uint32_t pos_mask;         /* (1 << pb) - 1 */\n\n    /* If 1, it's a match. Otherwise it's a single 8-bit literal. */\n    uint16_t is_match[STATES][POS_STATES_MAX];\n\n    /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */\n    uint16_t is_rep[STATES];\n\n    /*\n     * If 0, distance of a repeated match is rep0.\n     * Otherwise check is_rep1.\n     */\n    uint16_t is_rep0[STATES];\n\n    /*\n     * If 0, distance of a repeated match is rep1.\n     * Otherwise check is_rep2.\n     */\n    uint16_t is_rep1[STATES];\n\n    /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */\n    uint16_t is_rep2[STATES];\n\n    /*\n     * If 1, the repeated match has length of one byte. Otherwise\n     * the length is decoded from rep_len_decoder.\n     */\n    uint16_t is_rep0_long[STATES][POS_STATES_MAX];\n\n    /*\n     * Probability tree for the highest two bits of the match\n     * distance. There is a separate probability tree for match\n     * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273].\n     */\n    uint16_t dist_slot[DIST_STATES][DIST_SLOTS];\n\n    /*\n     * Probility trees for additional bits for match distance\n     * when the distance is in the range [4, 127].\n     */\n    uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END];\n\n    /*\n     * Probability tree for the lowest four bits of a match\n     * distance that is equal to or greater than 128.\n     */\n    uint16_t dist_align[ALIGN_SIZE];\n\n    /* Length of a normal match */\n    struct lzma_len_dec match_len_dec;\n\n    /* Length of a repeated match */\n    struct lzma_len_dec rep_len_dec;\n\n    /* Probabilities of literals */\n    uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE];\n};\n\nstruct lzma2_dec {\n    /* Position in xz_dec_lzma2_run(). */\n    enum lzma2_seq {\n        SEQ_CONTROL,\n        SEQ_UNCOMPRESSED_1,\n        SEQ_UNCOMPRESSED_2,\n        SEQ_COMPRESSED_0,\n        SEQ_COMPRESSED_1,\n        SEQ_PROPERTIES,\n        SEQ_LZMA_PREPARE,\n        SEQ_LZMA_RUN,\n        SEQ_COPY\n    } sequence;\n\n    /* Next position after decoding the compressed size of the chunk. */\n    enum lzma2_seq next_sequence;\n\n    /* Uncompressed size of LZMA chunk (2 MiB at maximum) */\n    uint32_t uncompressed;\n\n    /*\n     * Compressed size of LZMA chunk or compressed/uncompressed\n     * size of uncompressed chunk (64 KiB at maximum)\n     */\n    uint32_t compressed;\n\n    /*\n     * True if dictionary reset is needed. This is false before\n     * the first chunk (LZMA or uncompressed).\n     */\n    bool need_dict_reset;\n\n    /*\n     * True if new LZMA properties are needed. This is false\n     * before the first LZMA chunk.\n     */\n    bool need_props;\n};\n\nstruct xz_dec_lzma2 {\n    /*\n     * The order below is important on x86 to reduce code size and\n     * it shouldn't hurt on other platforms. Everything up to and\n     * including lzma.pos_mask are in the first 128 bytes on x86-32,\n     * which allows using smaller instructions to access those\n     * variables. On x86-64, fewer variables fit into the first 128\n     * bytes, but this is still the best order without sacrificing\n     * the readability by splitting the structures.\n     */\n    struct rc_dec rc;\n    struct dictionary dict;\n    struct lzma2_dec lzma2;\n    struct lzma_dec lzma;\n\n    /*\n     * Temporary buffer which holds small number of input bytes between\n     * decoder calls. See lzma2_lzma() for details.\n     */\n    struct {\n        uint32_t size;\n        uint8_t buf[3 * LZMA_IN_REQUIRED];\n    } temp;\n};\n\n/**************\n * Dictionary *\n **************/\n\n/*\n * Reset the dictionary state. When in single-call mode, set up the beginning\n * of the dictionary to point to the actual output buffer.\n */\nstatic void dict_reset(struct dictionary *dict, struct xz_buf *b)\n{\n    if (DEC_IS_SINGLE(dict->mode)) {\n        dict->buf = b->out + b->out_pos;\n        dict->end = b->out_size - b->out_pos;\n    }\n\n    dict->start = 0;\n    dict->pos = 0;\n    dict->limit = 0;\n    dict->full = 0;\n}\n\n/* Set dictionary write limit */\nstatic void dict_limit(struct dictionary *dict, size_t out_max)\n{\n    if (dict->end - dict->pos <= out_max)\n        dict->limit = dict->end;\n    else\n        dict->limit = dict->pos + out_max;\n}\n\n/* Return true if at least one byte can be written into the dictionary. */\nstatic inline bool dict_has_space(const struct dictionary *dict)\n{\n    return dict->pos < dict->limit;\n}\n\n/*\n * Get a byte from the dictionary at the given distance. The distance is\n * assumed to valid, or as a special case, zero when the dictionary is\n * still empty. This special case is needed for single-call decoding to\n * avoid writing a '\\0' to the end of the destination buffer.\n */\nstatic inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist)\n{\n    size_t offset = dict->pos - dist - 1;\n\n    if (dist >= dict->pos)\n        offset += dict->end;\n\n    return dict->full > 0 ? dict->buf[offset] : 0;\n}\n\n/*\n * Put one byte into the dictionary. It is assumed that there is space for it.\n */\nstatic inline void dict_put(struct dictionary *dict, uint8_t byte)\n{\n    dict->buf[dict->pos++] = byte;\n\n    if (dict->full < dict->pos)\n        dict->full = dict->pos;\n}\n\n/*\n * Repeat given number of bytes from the given distance. If the distance is\n * invalid, false is returned. On success, true is returned and *len is\n * updated to indicate how many bytes were left to be repeated.\n */\nstatic bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist)\n{\n    size_t back;\n    uint32_t left;\n\n    if (dist >= dict->full || dist >= dict->size)\n        return false;\n\n    left = min_t(size_t, dict->limit - dict->pos, *len);\n    *len -= left;\n\n    back = dict->pos - dist - 1;\n    if (dist >= dict->pos)\n        back += dict->end;\n\n    do {\n        dict->buf[dict->pos++] = dict->buf[back++];\n        if (back == dict->end)\n            back = 0;\n    } while (--left > 0);\n\n    if (dict->full < dict->pos)\n        dict->full = dict->pos;\n\n    return true;\n}\n\n/* Copy uncompressed data as is from input to dictionary and output buffers. */\nstatic void dict_uncompressed(struct dictionary *dict, struct xz_buf *b,\n                  uint32_t *left)\n{\n    size_t copy_size;\n\n    while (*left > 0 && b->in_pos < b->in_size\n            && b->out_pos < b->out_size) {\n        copy_size = min(b->in_size - b->in_pos,\n                b->out_size - b->out_pos);\n        if (copy_size > dict->end - dict->pos)\n            copy_size = dict->end - dict->pos;\n        if (copy_size > *left)\n            copy_size = *left;\n\n        *left -= copy_size;\n\n        memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size);\n        dict->pos += copy_size;\n\n        if (dict->full < dict->pos)\n            dict->full = dict->pos;\n\n        if (DEC_IS_MULTI(dict->mode)) {\n            if (dict->pos == dict->end)\n                dict->pos = 0;\n\n            memcpy(b->out + b->out_pos, b->in + b->in_pos,\n                    copy_size);\n        }\n\n        dict->start = dict->pos;\n\n        b->out_pos += copy_size;\n        b->in_pos += copy_size;\n    }\n}\n\n/*\n * Flush pending data from dictionary to b->out. It is assumed that there is\n * enough space in b->out. This is guaranteed because caller uses dict_limit()\n * before decoding data into the dictionary.\n */\nstatic uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b)\n{\n    size_t copy_size = dict->pos - dict->start;\n\n    if (DEC_IS_MULTI(dict->mode)) {\n        if (dict->pos == dict->end)\n            dict->pos = 0;\n\n        memcpy(b->out + b->out_pos, dict->buf + dict->start,\n                copy_size);\n    }\n\n    dict->start = dict->pos;\n    b->out_pos += copy_size;\n    return copy_size;\n}\n\n/*****************\n * Range decoder *\n *****************/\n\n/* Reset the range decoder. */\nstatic void rc_reset(struct rc_dec *rc)\n{\n    rc->range = (uint32_t)-1;\n    rc->code = 0;\n    rc->init_bytes_left = RC_INIT_BYTES;\n}\n\n/*\n * Read the first five initial bytes into rc->code if they haven't been\n * read already. (Yes, the first byte gets completely ignored.)\n */\nstatic bool rc_read_init(struct rc_dec *rc, struct xz_buf *b)\n{\n    while (rc->init_bytes_left > 0) {\n        if (b->in_pos == b->in_size)\n            return false;\n\n        rc->code = (rc->code << 8) + b->in[b->in_pos++];\n        --rc->init_bytes_left;\n    }\n\n    return true;\n}\n\n/* Return true if there may not be enough input for the next decoding loop. */\nstatic inline bool rc_limit_exceeded(const struct rc_dec *rc)\n{\n    return rc->in_pos > rc->in_limit;\n}\n\n/*\n * Return true if it is possible (from point of view of range decoder) that\n * we have reached the end of the LZMA chunk.\n */\nstatic inline bool rc_is_finished(const struct rc_dec *rc)\n{\n    return rc->code == 0;\n}\n\n/* Read the next input byte if needed. */\nstatic __always_inline void rc_normalize(struct rc_dec *rc)\n{\n    if (rc->range < RC_TOP_VALUE) {\n        rc->range <<= RC_SHIFT_BITS;\n        rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++];\n    }\n}\n\n/*\n * Decode one bit. In some versions, this function has been splitted in three\n * functions so that the compiler is supposed to be able to more easily avoid\n * an extra branch. In this particular version of the LZMA decoder, this\n * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3\n * on x86). Using a non-splitted version results in nicer looking code too.\n *\n * NOTE: This must return an int. Do not make it return a bool or the speed\n * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care,\n * and it generates 10-20 % faster code than GCC 3.x from this file anyway.)\n */\nstatic __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob)\n{\n    uint32_t bound;\n    int bit;\n\n    rc_normalize(rc);\n    bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob;\n    if (rc->code < bound) {\n        rc->range = bound;\n        *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS;\n        bit = 0;\n    } else {\n        rc->range -= bound;\n        rc->code -= bound;\n        *prob -= *prob >> RC_MOVE_BITS;\n        bit = 1;\n    }\n\n    return bit;\n}\n\n/* Decode a bittree starting from the most significant bit. */\nstatic __always_inline uint32_t rc_bittree(struct rc_dec *rc,\n                       uint16_t *probs, uint32_t limit)\n{\n    uint32_t symbol = 1;\n\n    do {\n        if (rc_bit(rc, &probs[symbol]))\n            symbol = (symbol << 1) + 1;\n        else\n            symbol <<= 1;\n    } while (symbol < limit);\n\n    return symbol;\n}\n\n/* Decode a bittree starting from the least significant bit. */\nstatic __always_inline void rc_bittree_reverse(struct rc_dec *rc,\n                           uint16_t *probs,\n                           uint32_t *dest, uint32_t limit)\n{\n    uint32_t symbol = 1;\n    uint32_t i = 0;\n\n    do {\n        if (rc_bit(rc, &probs[symbol])) {\n            symbol = (symbol << 1) + 1;\n            *dest += 1 << i;\n        } else {\n            symbol <<= 1;\n        }\n    } while (++i < limit);\n}\n\n/* Decode direct bits (fixed fifty-fifty probability) */\nstatic inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit)\n{\n    uint32_t mask;\n\n    do {\n        rc_normalize(rc);\n        rc->range >>= 1;\n        rc->code -= rc->range;\n        mask = (uint32_t)0 - (rc->code >> 31);\n        rc->code += rc->range & mask;\n        *dest = (*dest << 1) + (mask + 1);\n    } while (--limit > 0);\n}\n\n/********\n * LZMA *\n ********/\n\n/* Get pointer to literal coder probability array. */\nstatic uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s)\n{\n    uint32_t prev_byte = dict_get(&s->dict, 0);\n    uint32_t low = prev_byte >> (8 - s->lzma.lc);\n    uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc;\n    return s->lzma.literal[low + high];\n}\n\n/* Decode a literal (one 8-bit byte) */\nstatic void lzma_literal(struct xz_dec_lzma2 *s)\n{\n    uint16_t *probs;\n    uint32_t symbol;\n    uint32_t match_byte;\n    uint32_t match_bit;\n    uint32_t offset;\n    uint32_t i;\n\n    probs = lzma_literal_probs(s);\n\n    if (lzma_state_is_literal(s->lzma.state)) {\n        symbol = rc_bittree(&s->rc, probs, 0x100);\n    } else {\n        symbol = 1;\n        match_byte = dict_get(&s->dict, s->lzma.rep0) << 1;\n        offset = 0x100;\n\n        do {\n            match_bit = match_byte & offset;\n            match_byte <<= 1;\n            i = offset + match_bit + symbol;\n\n            if (rc_bit(&s->rc, &probs[i])) {\n                symbol = (symbol << 1) + 1;\n                offset &= match_bit;\n            } else {\n                symbol <<= 1;\n                offset &= ~match_bit;\n            }\n        } while (symbol < 0x100);\n    }\n\n    dict_put(&s->dict, (uint8_t)symbol);\n    lzma_state_literal(&s->lzma.state);\n}\n\n/* Decode the length of the match into s->lzma.len. */\nstatic void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l,\n             uint32_t pos_state)\n{\n    uint16_t *probs;\n    uint32_t limit;\n\n    if (!rc_bit(&s->rc, &l->choice)) {\n        probs = l->low[pos_state];\n        limit = LEN_LOW_SYMBOLS;\n        s->lzma.len = MATCH_LEN_MIN;\n    } else {\n        if (!rc_bit(&s->rc, &l->choice2)) {\n            probs = l->mid[pos_state];\n            limit = LEN_MID_SYMBOLS;\n            s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS;\n        } else {\n            probs = l->high;\n            limit = LEN_HIGH_SYMBOLS;\n            s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS\n                    + LEN_MID_SYMBOLS;\n        }\n    }\n\n    s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit;\n}\n\n/* Decode a match. The distance will be stored in s->lzma.rep0. */\nstatic void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state)\n{\n    uint16_t *probs;\n    uint32_t dist_slot;\n    uint32_t limit;\n\n    lzma_state_match(&s->lzma.state);\n\n    s->lzma.rep3 = s->lzma.rep2;\n    s->lzma.rep2 = s->lzma.rep1;\n    s->lzma.rep1 = s->lzma.rep0;\n\n    lzma_len(s, &s->lzma.match_len_dec, pos_state);\n\n    probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)];\n    dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS;\n\n    if (dist_slot < DIST_MODEL_START) {\n        s->lzma.rep0 = dist_slot;\n    } else {\n        limit = (dist_slot >> 1) - 1;\n        s->lzma.rep0 = 2 + (dist_slot & 1);\n\n        if (dist_slot < DIST_MODEL_END) {\n            s->lzma.rep0 <<= limit;\n            probs = s->lzma.dist_special + s->lzma.rep0\n                    - dist_slot - 1;\n            rc_bittree_reverse(&s->rc, probs,\n                    &s->lzma.rep0, limit);\n        } else {\n            rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS);\n            s->lzma.rep0 <<= ALIGN_BITS;\n            rc_bittree_reverse(&s->rc, s->lzma.dist_align,\n                    &s->lzma.rep0, ALIGN_BITS);\n        }\n    }\n}\n\n/*\n * Decode a repeated match. The distance is one of the four most recently\n * seen matches. The distance will be stored in s->lzma.rep0.\n */\nstatic void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state)\n{\n    uint32_t tmp;\n\n    if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) {\n        if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[\n                s->lzma.state][pos_state])) {\n            lzma_state_short_rep(&s->lzma.state);\n            s->lzma.len = 1;\n            return;\n        }\n    } else {\n        if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) {\n            tmp = s->lzma.rep1;\n        } else {\n            if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) {\n                tmp = s->lzma.rep2;\n            } else {\n                tmp = s->lzma.rep3;\n                s->lzma.rep3 = s->lzma.rep2;\n            }\n\n            s->lzma.rep2 = s->lzma.rep1;\n        }\n\n        s->lzma.rep1 = s->lzma.rep0;\n        s->lzma.rep0 = tmp;\n    }\n\n    lzma_state_long_rep(&s->lzma.state);\n    lzma_len(s, &s->lzma.rep_len_dec, pos_state);\n}\n\n/* LZMA decoder core */\nstatic bool lzma_main(struct xz_dec_lzma2 *s)\n{\n    uint32_t pos_state;\n\n    /*\n     * If the dictionary was reached during the previous call, try to\n     * finish the possibly pending repeat in the dictionary.\n     */\n    if (dict_has_space(&s->dict) && s->lzma.len > 0)\n        dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0);\n\n    /*\n     * Decode more LZMA symbols. One iteration may consume up to\n     * LZMA_IN_REQUIRED - 1 bytes.\n     */\n    while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) {\n        pos_state = s->dict.pos & s->lzma.pos_mask;\n\n        if (!rc_bit(&s->rc, &s->lzma.is_match[\n                s->lzma.state][pos_state])) {\n            lzma_literal(s);\n        } else {\n            if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state]))\n                lzma_rep_match(s, pos_state);\n            else\n                lzma_match(s, pos_state);\n\n            if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0))\n                return false;\n        }\n    }\n\n    /*\n     * Having the range decoder always normalized when we are outside\n     * this function makes it easier to correctly handle end of the chunk.\n     */\n    rc_normalize(&s->rc);\n\n    return true;\n}\n\n/*\n * Reset the LZMA decoder and range decoder state. Dictionary is nore reset\n * here, because LZMA state may be reset without resetting the dictionary.\n */\nstatic void lzma_reset(struct xz_dec_lzma2 *s)\n{\n    uint16_t *probs;\n    size_t i;\n\n    s->lzma.state = STATE_LIT_LIT;\n    s->lzma.rep0 = 0;\n    s->lzma.rep1 = 0;\n    s->lzma.rep2 = 0;\n    s->lzma.rep3 = 0;\n\n    /*\n     * All probabilities are initialized to the same value. This hack\n     * makes the code smaller by avoiding a separate loop for each\n     * probability array.\n     *\n     * This could be optimized so that only that part of literal\n     * probabilities that are actually required. In the common case\n     * we would write 12 KiB less.\n     */\n    probs = s->lzma.is_match[0];\n    for (i = 0; i < PROBS_TOTAL; ++i)\n        probs[i] = RC_BIT_MODEL_TOTAL / 2;\n\n    rc_reset(&s->rc);\n}\n\n/*\n * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks\n * from the decoded lp and pb values. On success, the LZMA decoder state is\n * reset and true is returned.\n */\nstatic bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props)\n{\n    if (props > (4 * 5 + 4) * 9 + 8)\n        return false;\n\n    s->lzma.pos_mask = 0;\n    while (props >= 9 * 5) {\n        props -= 9 * 5;\n        ++s->lzma.pos_mask;\n    }\n\n    s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1;\n\n    s->lzma.literal_pos_mask = 0;\n    while (props >= 9) {\n        props -= 9;\n        ++s->lzma.literal_pos_mask;\n    }\n\n    s->lzma.lc = props;\n\n    if (s->lzma.lc + s->lzma.literal_pos_mask > 4)\n        return false;\n\n    s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1;\n\n    lzma_reset(s);\n\n    return true;\n}\n\n/*********\n * LZMA2 *\n *********/\n\n/*\n * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't\n * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This\n * wrapper function takes care of making the LZMA decoder's assumption safe.\n *\n * As long as there is plenty of input left to be decoded in the current LZMA\n * chunk, we decode directly from the caller-supplied input buffer until\n * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into\n * s->temp.buf, which (hopefully) gets filled on the next call to this\n * function. We decode a few bytes from the temporary buffer so that we can\n * continue decoding from the caller-supplied input buffer again.\n */\nstatic bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b)\n{\n    size_t in_avail;\n    uint32_t tmp;\n\n    in_avail = b->in_size - b->in_pos;\n    if (s->temp.size > 0 || s->lzma2.compressed == 0) {\n        tmp = 2 * LZMA_IN_REQUIRED - s->temp.size;\n        if (tmp > s->lzma2.compressed - s->temp.size)\n            tmp = s->lzma2.compressed - s->temp.size;\n        if (tmp > in_avail)\n            tmp = in_avail;\n\n        memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp);\n\n        if (s->temp.size + tmp == s->lzma2.compressed) {\n            memzero(s->temp.buf + s->temp.size + tmp,\n                    sizeof(s->temp.buf)\n                        - s->temp.size - tmp);\n            s->rc.in_limit = s->temp.size + tmp;\n        } else if (s->temp.size + tmp < LZMA_IN_REQUIRED) {\n            s->temp.size += tmp;\n            b->in_pos += tmp;\n            return true;\n        } else {\n            s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED;\n        }\n\n        s->rc.in = s->temp.buf;\n        s->rc.in_pos = 0;\n\n        if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp)\n            return false;\n\n        s->lzma2.compressed -= s->rc.in_pos;\n\n        if (s->rc.in_pos < s->temp.size) {\n            s->temp.size -= s->rc.in_pos;\n            memmove(s->temp.buf, s->temp.buf + s->rc.in_pos,\n                    s->temp.size);\n            return true;\n        }\n\n        b->in_pos += s->rc.in_pos - s->temp.size;\n        s->temp.size = 0;\n    }\n\n    in_avail = b->in_size - b->in_pos;\n    if (in_avail >= LZMA_IN_REQUIRED) {\n        s->rc.in = b->in;\n        s->rc.in_pos = b->in_pos;\n\n        if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED)\n            s->rc.in_limit = b->in_pos + s->lzma2.compressed;\n        else\n            s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED;\n\n        if (!lzma_main(s))\n            return false;\n\n        in_avail = s->rc.in_pos - b->in_pos;\n        if (in_avail > s->lzma2.compressed)\n            return false;\n\n        s->lzma2.compressed -= in_avail;\n        b->in_pos = s->rc.in_pos;\n    }\n\n    in_avail = b->in_size - b->in_pos;\n    if (in_avail < LZMA_IN_REQUIRED) {\n        if (in_avail > s->lzma2.compressed)\n            in_avail = s->lzma2.compressed;\n\n        memcpy(s->temp.buf, b->in + b->in_pos, in_avail);\n        s->temp.size = in_avail;\n        b->in_pos += in_avail;\n    }\n\n    return true;\n}\n\n/*\n * Take care of the LZMA2 control layer, and forward the job of actual LZMA\n * decoding or copying of uncompressed chunks to other functions.\n */\nXZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s,\n                       struct xz_buf *b)\n{\n    uint32_t tmp;\n\n    while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) {\n        switch (s->lzma2.sequence) {\n        case SEQ_CONTROL:\n            /*\n             * LZMA2 control byte\n             *\n             * Exact values:\n             *   0x00   End marker\n             *   0x01   Dictionary reset followed by\n             *          an uncompressed chunk\n             *   0x02   Uncompressed chunk (no dictionary reset)\n             *\n             * Highest three bits (s->control & 0xE0):\n             *   0xE0   Dictionary reset, new properties and state\n             *          reset, followed by LZMA compressed chunk\n             *   0xC0   New properties and state reset, followed\n             *          by LZMA compressed chunk (no dictionary\n             *          reset)\n             *   0xA0   State reset using old properties,\n             *          followed by LZMA compressed chunk (no\n             *          dictionary reset)\n             *   0x80   LZMA chunk (no dictionary or state reset)\n             *\n             * For LZMA compressed chunks, the lowest five bits\n             * (s->control & 1F) are the highest bits of the\n             * uncompressed size (bits 16-20).\n             *\n             * A new LZMA2 stream must begin with a dictionary\n             * reset. The first LZMA chunk must set new\n             * properties and reset the LZMA state.\n             *\n             * Values that don't match anything described above\n             * are invalid and we return XZ_DATA_ERROR.\n             */\n            tmp = b->in[b->in_pos++];\n\n            if (tmp == 0x00)\n                return XZ_STREAM_END;\n\n            if (tmp >= 0xE0 || tmp == 0x01) {\n                s->lzma2.need_props = true;\n                s->lzma2.need_dict_reset = false;\n                dict_reset(&s->dict, b);\n            } else if (s->lzma2.need_dict_reset) {\n                return XZ_DATA_ERROR;\n            }\n\n            if (tmp >= 0x80) {\n                s->lzma2.uncompressed = (tmp & 0x1F) << 16;\n                s->lzma2.sequence = SEQ_UNCOMPRESSED_1;\n\n                if (tmp >= 0xC0) {\n                    /*\n                     * When there are new properties,\n                     * state reset is done at\n                     * SEQ_PROPERTIES.\n                     */\n                    s->lzma2.need_props = false;\n                    s->lzma2.next_sequence\n                            = SEQ_PROPERTIES;\n\n                } else if (s->lzma2.need_props) {\n                    return XZ_DATA_ERROR;\n\n                } else {\n                    s->lzma2.next_sequence\n                            = SEQ_LZMA_PREPARE;\n                    if (tmp >= 0xA0)\n                        lzma_reset(s);\n                }\n            } else {\n                if (tmp > 0x02)\n                    return XZ_DATA_ERROR;\n\n                s->lzma2.sequence = SEQ_COMPRESSED_0;\n                s->lzma2.next_sequence = SEQ_COPY;\n            }\n\n            break;\n\n        case SEQ_UNCOMPRESSED_1:\n            s->lzma2.uncompressed\n                    += (uint32_t)b->in[b->in_pos++] << 8;\n            s->lzma2.sequence = SEQ_UNCOMPRESSED_2;\n            break;\n\n        case SEQ_UNCOMPRESSED_2:\n            s->lzma2.uncompressed\n                    += (uint32_t)b->in[b->in_pos++] + 1;\n            s->lzma2.sequence = SEQ_COMPRESSED_0;\n            break;\n\n        case SEQ_COMPRESSED_0:\n            s->lzma2.compressed\n                    = (uint32_t)b->in[b->in_pos++] << 8;\n            s->lzma2.sequence = SEQ_COMPRESSED_1;\n            break;\n\n        case SEQ_COMPRESSED_1:\n            s->lzma2.compressed\n                    += (uint32_t)b->in[b->in_pos++] + 1;\n            s->lzma2.sequence = s->lzma2.next_sequence;\n            break;\n\n        case SEQ_PROPERTIES:\n            if (!lzma_props(s, b->in[b->in_pos++]))\n                return XZ_DATA_ERROR;\n\n            s->lzma2.sequence = SEQ_LZMA_PREPARE;\n\n        case SEQ_LZMA_PREPARE:\n            if (s->lzma2.compressed < RC_INIT_BYTES)\n                return XZ_DATA_ERROR;\n\n            if (!rc_read_init(&s->rc, b))\n                return XZ_OK;\n\n            s->lzma2.compressed -= RC_INIT_BYTES;\n            s->lzma2.sequence = SEQ_LZMA_RUN;\n\n        case SEQ_LZMA_RUN:\n            /*\n             * Set dictionary limit to indicate how much we want\n             * to be encoded at maximum. Decode new data into the\n             * dictionary. Flush the new data from dictionary to\n             * b->out. Check if we finished decoding this chunk.\n             * In case the dictionary got full but we didn't fill\n             * the output buffer yet, we may run this loop\n             * multiple times without changing s->lzma2.sequence.\n             */\n            dict_limit(&s->dict, min_t(size_t,\n                    b->out_size - b->out_pos,\n                    s->lzma2.uncompressed));\n            if (!lzma2_lzma(s, b))\n                return XZ_DATA_ERROR;\n\n            s->lzma2.uncompressed -= dict_flush(&s->dict, b);\n\n            if (s->lzma2.uncompressed == 0) {\n                if (s->lzma2.compressed > 0 || s->lzma.len > 0\n                        || !rc_is_finished(&s->rc))\n                    return XZ_DATA_ERROR;\n\n                rc_reset(&s->rc);\n                s->lzma2.sequence = SEQ_CONTROL;\n\n            } else if (b->out_pos == b->out_size\n                    || (b->in_pos == b->in_size\n                        && s->temp.size\n                        < s->lzma2.compressed)) {\n                return XZ_OK;\n            }\n\n            break;\n\n        case SEQ_COPY:\n            dict_uncompressed(&s->dict, b, &s->lzma2.compressed);\n            if (s->lzma2.compressed > 0)\n                return XZ_OK;\n\n            s->lzma2.sequence = SEQ_CONTROL;\n            break;\n        }\n    }\n\n    return XZ_OK;\n}\n\nXZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode,\n                           uint32_t dict_max)\n{\n    struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL);\n    if (s == NULL)\n        return NULL;\n\n    s->dict.mode = mode;\n    s->dict.size_max = dict_max;\n\n    if (DEC_IS_PREALLOC(mode)) {\n        s->dict.buf = vmalloc(dict_max);\n        if (s->dict.buf == NULL) {\n            kfree(s);\n            return NULL;\n        }\n    } else if (DEC_IS_DYNALLOC(mode)) {\n        s->dict.buf = NULL;\n        s->dict.allocated = 0;\n    }\n\n    return s;\n}\n\nXZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props)\n{\n    /* This limits dictionary size to 3 GiB to keep parsing simpler. */\n    if (props > 39)\n        return XZ_OPTIONS_ERROR;\n\n    s->dict.size = 2 + (props & 1);\n    s->dict.size <<= (props >> 1) + 11;\n\n    if (DEC_IS_MULTI(s->dict.mode)) {\n        if (s->dict.size > s->dict.size_max)\n            return XZ_MEMLIMIT_ERROR;\n\n        s->dict.end = s->dict.size;\n\n        if (DEC_IS_DYNALLOC(s->dict.mode)) {\n            if (s->dict.allocated < s->dict.size) {\n                vfree(s->dict.buf);\n                s->dict.buf = vmalloc(s->dict.size);\n                if (s->dict.buf == NULL) {\n                    s->dict.allocated = 0;\n                    return XZ_MEM_ERROR;\n                }\n            }\n        }\n    }\n\n    s->lzma.len = 0;\n\n    s->lzma2.sequence = SEQ_CONTROL;\n    s->lzma2.need_dict_reset = true;\n\n    s->temp.size = 0;\n\n    return XZ_OK;\n}\n\nXZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s)\n{\n    if (DEC_IS_MULTI(s->dict.mode))\n        vfree(s->dict.buf);\n\n    kfree(s);\n}\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_dec_stream.c",
    "content": "/*\n * .xz Stream decoder\n *\n * Author: Lasse Collin <lasse.collin@tukaani.org>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#include \"xz_private.h\"\n#include \"xz_stream.h\"\n\n#ifdef XZ_USE_CRC64\n#\tdefine IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64)\n#else\n#\tdefine IS_CRC64(check_type) false\n#endif\n\n/* Hash used to validate the Index field */\nstruct xz_dec_hash {\n    vli_type unpadded;\n    vli_type uncompressed;\n    uint32_t crc32;\n};\n\nstruct xz_dec {\n    /* Position in dec_main() */\n    enum {\n        SEQ_STREAM_HEADER,\n        SEQ_BLOCK_START,\n        SEQ_BLOCK_HEADER,\n        SEQ_BLOCK_UNCOMPRESS,\n        SEQ_BLOCK_PADDING,\n        SEQ_BLOCK_CHECK,\n        SEQ_INDEX,\n        SEQ_INDEX_PADDING,\n        SEQ_INDEX_CRC32,\n        SEQ_STREAM_FOOTER\n    } sequence;\n\n    /* Position in variable-length integers and Check fields */\n    uint32_t pos;\n\n    /* Variable-length integer decoded by dec_vli() */\n    vli_type vli;\n\n    /* Saved in_pos and out_pos */\n    size_t in_start;\n    size_t out_start;\n\n#ifdef XZ_USE_CRC64\n    /* CRC32 or CRC64 value in Block or CRC32 value in Index */\n    uint64_t crc;\n#else\n    /* CRC32 value in Block or Index */\n    uint32_t crc;\n#endif\n\n    /* Type of the integrity check calculated from uncompressed data */\n    enum xz_check check_type;\n\n    /* Operation mode */\n    enum xz_mode mode;\n\n    /*\n     * True if the next call to xz_dec_run() is allowed to return\n     * XZ_BUF_ERROR.\n     */\n    bool allow_buf_error;\n\n    /* Information stored in Block Header */\n    struct {\n        /*\n         * Value stored in the Compressed Size field, or\n         * VLI_UNKNOWN if Compressed Size is not present.\n         */\n        vli_type compressed;\n\n        /*\n         * Value stored in the Uncompressed Size field, or\n         * VLI_UNKNOWN if Uncompressed Size is not present.\n         */\n        vli_type uncompressed;\n\n        /* Size of the Block Header field */\n        uint32_t size;\n    } block_header;\n\n    /* Information collected when decoding Blocks */\n    struct {\n        /* Observed compressed size of the current Block */\n        vli_type compressed;\n\n        /* Observed uncompressed size of the current Block */\n        vli_type uncompressed;\n\n        /* Number of Blocks decoded so far */\n        vli_type count;\n\n        /*\n         * Hash calculated from the Block sizes. This is used to\n         * validate the Index field.\n         */\n        struct xz_dec_hash hash;\n    } block;\n\n    /* Variables needed when verifying the Index field */\n    struct {\n        /* Position in dec_index() */\n        enum {\n            SEQ_INDEX_COUNT,\n            SEQ_INDEX_UNPADDED,\n            SEQ_INDEX_UNCOMPRESSED\n        } sequence;\n\n        /* Size of the Index in bytes */\n        vli_type size;\n\n        /* Number of Records (matches block.count in valid files) */\n        vli_type count;\n\n        /*\n         * Hash calculated from the Records (matches block.hash in\n         * valid files).\n         */\n        struct xz_dec_hash hash;\n    } index;\n\n    /*\n     * Temporary buffer needed to hold Stream Header, Block Header,\n     * and Stream Footer. The Block Header is the biggest (1 KiB)\n     * so we reserve space according to that. buf[] has to be aligned\n     * to a multiple of four bytes; the size_t variables before it\n     * should guarantee this.\n     */\n    struct {\n        size_t pos;\n        size_t size;\n        uint8_t buf[1024];\n    } temp;\n\n    struct xz_dec_lzma2 *lzma2;\n\n#ifdef XZ_DEC_BCJ\n    struct xz_dec_bcj *bcj;\n    bool bcj_active;\n#endif\n};\n\n#ifdef XZ_DEC_ANY_CHECK\n/* Sizes of the Check field with different Check IDs */\nstatic const uint8_t check_sizes[16] = {\n    0,\n    4, 4, 4,\n    8, 8, 8,\n    16, 16, 16,\n    32, 32, 32,\n    64, 64, 64\n};\n#endif\n\n/*\n * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller\n * must have set s->temp.pos to indicate how much data we are supposed\n * to copy into s->temp.buf. Return true once s->temp.pos has reached\n * s->temp.size.\n */\nstatic bool fill_temp(struct xz_dec *s, struct xz_buf *b)\n{\n    size_t copy_size = min_t(size_t,\n            b->in_size - b->in_pos, s->temp.size - s->temp.pos);\n\n    memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size);\n    b->in_pos += copy_size;\n    s->temp.pos += copy_size;\n\n    if (s->temp.pos == s->temp.size) {\n        s->temp.pos = 0;\n        return true;\n    }\n\n    return false;\n}\n\n/* Decode a variable-length integer (little-endian base-128 encoding) */\nstatic enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in,\n               size_t *in_pos, size_t in_size)\n{\n    uint8_t byte;\n\n    if (s->pos == 0)\n        s->vli = 0;\n\n    while (*in_pos < in_size) {\n        byte = in[*in_pos];\n        ++*in_pos;\n\n        s->vli |= (vli_type)(byte & 0x7F) << s->pos;\n\n        if ((byte & 0x80) == 0) {\n            /* Don't allow non-minimal encodings. */\n            if (byte == 0 && s->pos != 0)\n                return XZ_DATA_ERROR;\n\n            s->pos = 0;\n            return XZ_STREAM_END;\n        }\n\n        s->pos += 7;\n        if (s->pos == 7 * VLI_BYTES_MAX)\n            return XZ_DATA_ERROR;\n    }\n\n    return XZ_OK;\n}\n\n/*\n * Decode the Compressed Data field from a Block. Update and validate\n * the observed compressed and uncompressed sizes of the Block so that\n * they don't exceed the values possibly stored in the Block Header\n * (validation assumes that no integer overflow occurs, since vli_type\n * is normally uint64_t). Update the CRC32 or CRC64 value if presence of\n * the CRC32 or CRC64 field was indicated in Stream Header.\n *\n * Once the decoding is finished, validate that the observed sizes match\n * the sizes possibly stored in the Block Header. Update the hash and\n * Block count, which are later used to validate the Index field.\n */\nstatic enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b)\n{\n    enum xz_ret ret;\n\n    s->in_start = b->in_pos;\n    s->out_start = b->out_pos;\n\n#ifdef XZ_DEC_BCJ\n    if (s->bcj_active)\n        ret = xz_dec_bcj_run(s->bcj, s->lzma2, b);\n    else\n#endif\n        ret = xz_dec_lzma2_run(s->lzma2, b);\n\n    s->block.compressed += b->in_pos - s->in_start;\n    s->block.uncompressed += b->out_pos - s->out_start;\n\n    /*\n     * There is no need to separately check for VLI_UNKNOWN, since\n     * the observed sizes are always smaller than VLI_UNKNOWN.\n     */\n    if (s->block.compressed > s->block_header.compressed\n            || s->block.uncompressed\n                > s->block_header.uncompressed)\n        return XZ_DATA_ERROR;\n\n    if (s->check_type == XZ_CHECK_CRC32)\n        s->crc = xz_crc32(b->out + s->out_start,\n                b->out_pos - s->out_start, s->crc);\n#ifdef XZ_USE_CRC64\n    else if (s->check_type == XZ_CHECK_CRC64)\n        s->crc = xz_crc64(b->out + s->out_start,\n                b->out_pos - s->out_start, s->crc);\n#endif\n\n    if (ret == XZ_STREAM_END) {\n        if (s->block_header.compressed != VLI_UNKNOWN\n                && s->block_header.compressed\n                    != s->block.compressed)\n            return XZ_DATA_ERROR;\n\n        if (s->block_header.uncompressed != VLI_UNKNOWN\n                && s->block_header.uncompressed\n                    != s->block.uncompressed)\n            return XZ_DATA_ERROR;\n\n        s->block.hash.unpadded += s->block_header.size\n                + s->block.compressed;\n\n#ifdef XZ_DEC_ANY_CHECK\n        s->block.hash.unpadded += check_sizes[s->check_type];\n#else\n        if (s->check_type == XZ_CHECK_CRC32)\n            s->block.hash.unpadded += 4;\n        else if (IS_CRC64(s->check_type))\n            s->block.hash.unpadded += 8;\n#endif\n\n        s->block.hash.uncompressed += s->block.uncompressed;\n        s->block.hash.crc32 = xz_crc32(\n                (const uint8_t *)&s->block.hash,\n                sizeof(s->block.hash), s->block.hash.crc32);\n\n        ++s->block.count;\n    }\n\n    return ret;\n}\n\n/* Update the Index size and the CRC32 value. */\nstatic void index_update(struct xz_dec *s, const struct xz_buf *b)\n{\n    size_t in_used = b->in_pos - s->in_start;\n    s->index.size += in_used;\n    s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc);\n}\n\n/*\n * Decode the Number of Records, Unpadded Size, and Uncompressed Size\n * fields from the Index field. That is, Index Padding and CRC32 are not\n * decoded by this function.\n *\n * This can return XZ_OK (more input needed), XZ_STREAM_END (everything\n * successfully decoded), or XZ_DATA_ERROR (input is corrupt).\n */\nstatic enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b)\n{\n    enum xz_ret ret;\n\n    do {\n        ret = dec_vli(s, b->in, &b->in_pos, b->in_size);\n        if (ret != XZ_STREAM_END) {\n            index_update(s, b);\n            return ret;\n        }\n\n        switch (s->index.sequence) {\n        case SEQ_INDEX_COUNT:\n            s->index.count = s->vli;\n\n            /*\n             * Validate that the Number of Records field\n             * indicates the same number of Records as\n             * there were Blocks in the Stream.\n             */\n            if (s->index.count != s->block.count)\n                return XZ_DATA_ERROR;\n\n            s->index.sequence = SEQ_INDEX_UNPADDED;\n            break;\n\n        case SEQ_INDEX_UNPADDED:\n            s->index.hash.unpadded += s->vli;\n            s->index.sequence = SEQ_INDEX_UNCOMPRESSED;\n            break;\n\n        case SEQ_INDEX_UNCOMPRESSED:\n            s->index.hash.uncompressed += s->vli;\n            s->index.hash.crc32 = xz_crc32(\n                    (const uint8_t *)&s->index.hash,\n                    sizeof(s->index.hash),\n                    s->index.hash.crc32);\n            --s->index.count;\n            s->index.sequence = SEQ_INDEX_UNPADDED;\n            break;\n        }\n    } while (s->index.count > 0);\n\n    return XZ_STREAM_END;\n}\n\n/*\n * Validate that the next four or eight input bytes match the value\n * of s->crc. s->pos must be zero when starting to validate the first byte.\n * The \"bits\" argument allows using the same code for both CRC32 and CRC64.\n */\nstatic enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b,\n                uint32_t bits)\n{\n    do {\n        if (b->in_pos == b->in_size)\n            return XZ_OK;\n\n        if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++])\n            return XZ_DATA_ERROR;\n\n        s->pos += 8;\n\n    } while (s->pos < bits);\n\n    s->crc = 0;\n    s->pos = 0;\n\n    return XZ_STREAM_END;\n}\n\n#ifdef XZ_DEC_ANY_CHECK\n/*\n * Skip over the Check field when the Check ID is not supported.\n * Returns true once the whole Check field has been skipped over.\n */\nstatic bool check_skip(struct xz_dec *s, struct xz_buf *b)\n{\n    while (s->pos < check_sizes[s->check_type]) {\n        if (b->in_pos == b->in_size)\n            return false;\n\n        ++b->in_pos;\n        ++s->pos;\n    }\n\n    s->pos = 0;\n\n    return true;\n}\n#endif\n\n/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */\nstatic enum xz_ret dec_stream_header(struct xz_dec *s)\n{\n    if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE))\n        return XZ_FORMAT_ERROR;\n\n    if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0)\n            != get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2))\n        return XZ_DATA_ERROR;\n\n    if (s->temp.buf[HEADER_MAGIC_SIZE] != 0)\n        return XZ_OPTIONS_ERROR;\n\n    /*\n     * Of integrity checks, we support none (Check ID = 0),\n     * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4).\n     * However, if XZ_DEC_ANY_CHECK is defined, we will accept other\n     * check types too, but then the check won't be verified and\n     * a warning (XZ_UNSUPPORTED_CHECK) will be given.\n     */\n    s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1];\n\n#ifdef XZ_DEC_ANY_CHECK\n    if (s->check_type > XZ_CHECK_MAX)\n        return XZ_OPTIONS_ERROR;\n\n    if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type))\n        return XZ_UNSUPPORTED_CHECK;\n#else\n    if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type))\n        return XZ_OPTIONS_ERROR;\n#endif\n\n    return XZ_OK;\n}\n\n/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */\nstatic enum xz_ret dec_stream_footer(struct xz_dec *s)\n{\n    if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE))\n        return XZ_DATA_ERROR;\n\n    if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf))\n        return XZ_DATA_ERROR;\n\n    /*\n     * Validate Backward Size. Note that we never added the size of the\n     * Index CRC32 field to s->index.size, thus we use s->index.size / 4\n     * instead of s->index.size / 4 - 1.\n     */\n    if ((s->index.size >> 2) != get_le32(s->temp.buf + 4))\n        return XZ_DATA_ERROR;\n\n    if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type)\n        return XZ_DATA_ERROR;\n\n    /*\n     * Use XZ_STREAM_END instead of XZ_OK to be more convenient\n     * for the caller.\n     */\n    return XZ_STREAM_END;\n}\n\n/* Decode the Block Header and initialize the filter chain. */\nstatic enum xz_ret dec_block_header(struct xz_dec *s)\n{\n    enum xz_ret ret;\n\n    /*\n     * Validate the CRC32. We know that the temp buffer is at least\n     * eight bytes so this is safe.\n     */\n    s->temp.size -= 4;\n    if (xz_crc32(s->temp.buf, s->temp.size, 0)\n            != get_le32(s->temp.buf + s->temp.size))\n        return XZ_DATA_ERROR;\n\n    s->temp.pos = 2;\n\n    /*\n     * Catch unsupported Block Flags. We support only one or two filters\n     * in the chain, so we catch that with the same test.\n     */\n#ifdef XZ_DEC_BCJ\n    if (s->temp.buf[1] & 0x3E)\n#else\n    if (s->temp.buf[1] & 0x3F)\n#endif\n        return XZ_OPTIONS_ERROR;\n\n    /* Compressed Size */\n    if (s->temp.buf[1] & 0x40) {\n        if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size)\n                    != XZ_STREAM_END)\n            return XZ_DATA_ERROR;\n\n        s->block_header.compressed = s->vli;\n    } else {\n        s->block_header.compressed = VLI_UNKNOWN;\n    }\n\n    /* Uncompressed Size */\n    if (s->temp.buf[1] & 0x80) {\n        if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size)\n                != XZ_STREAM_END)\n            return XZ_DATA_ERROR;\n\n        s->block_header.uncompressed = s->vli;\n    } else {\n        s->block_header.uncompressed = VLI_UNKNOWN;\n    }\n\n#ifdef XZ_DEC_BCJ\n    /* If there are two filters, the first one must be a BCJ filter. */\n    s->bcj_active = s->temp.buf[1] & 0x01;\n    if (s->bcj_active) {\n        if (s->temp.size - s->temp.pos < 2)\n            return XZ_OPTIONS_ERROR;\n\n        ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]);\n        if (ret != XZ_OK)\n            return ret;\n\n        /*\n         * We don't support custom start offset,\n         * so Size of Properties must be zero.\n         */\n        if (s->temp.buf[s->temp.pos++] != 0x00)\n            return XZ_OPTIONS_ERROR;\n    }\n#endif\n\n    /* Valid Filter Flags always take at least two bytes. */\n    if (s->temp.size - s->temp.pos < 2)\n        return XZ_DATA_ERROR;\n\n    /* Filter ID = LZMA2 */\n    if (s->temp.buf[s->temp.pos++] != 0x21)\n        return XZ_OPTIONS_ERROR;\n\n    /* Size of Properties = 1-byte Filter Properties */\n    if (s->temp.buf[s->temp.pos++] != 0x01)\n        return XZ_OPTIONS_ERROR;\n\n    /* Filter Properties contains LZMA2 dictionary size. */\n    if (s->temp.size - s->temp.pos < 1)\n        return XZ_DATA_ERROR;\n\n    ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]);\n    if (ret != XZ_OK)\n        return ret;\n\n    /* The rest must be Header Padding. */\n    while (s->temp.pos < s->temp.size)\n        if (s->temp.buf[s->temp.pos++] != 0x00)\n            return XZ_OPTIONS_ERROR;\n\n    s->temp.pos = 0;\n    s->block.compressed = 0;\n    s->block.uncompressed = 0;\n\n    return XZ_OK;\n}\n\nstatic enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b)\n{\n    enum xz_ret ret;\n\n    /*\n     * Store the start position for the case when we are in the middle\n     * of the Index field.\n     */\n    s->in_start = b->in_pos;\n\n    while (true) {\n        switch (s->sequence) {\n        case SEQ_STREAM_HEADER:\n            /*\n             * Stream Header is copied to s->temp, and then\n             * decoded from there. This way if the caller\n             * gives us only little input at a time, we can\n             * still keep the Stream Header decoding code\n             * simple. Similar approach is used in many places\n             * in this file.\n             */\n            if (!fill_temp(s, b))\n                return XZ_OK;\n\n            /*\n             * If dec_stream_header() returns\n             * XZ_UNSUPPORTED_CHECK, it is still possible\n             * to continue decoding if working in multi-call\n             * mode. Thus, update s->sequence before calling\n             * dec_stream_header().\n             */\n            s->sequence = SEQ_BLOCK_START;\n\n            ret = dec_stream_header(s);\n            if (ret != XZ_OK)\n                return ret;\n\n        case SEQ_BLOCK_START:\n            /* We need one byte of input to continue. */\n            if (b->in_pos == b->in_size)\n                return XZ_OK;\n\n            /* See if this is the beginning of the Index field. */\n            if (b->in[b->in_pos] == 0) {\n                s->in_start = b->in_pos++;\n                s->sequence = SEQ_INDEX;\n                break;\n            }\n\n            /*\n             * Calculate the size of the Block Header and\n             * prepare to decode it.\n             */\n            s->block_header.size\n                = ((uint32_t)b->in[b->in_pos] + 1) * 4;\n\n            s->temp.size = s->block_header.size;\n            s->temp.pos = 0;\n            s->sequence = SEQ_BLOCK_HEADER;\n\n        case SEQ_BLOCK_HEADER:\n            if (!fill_temp(s, b))\n                return XZ_OK;\n\n            ret = dec_block_header(s);\n            if (ret != XZ_OK)\n                return ret;\n\n            s->sequence = SEQ_BLOCK_UNCOMPRESS;\n\n        case SEQ_BLOCK_UNCOMPRESS:\n            ret = dec_block(s, b);\n            if (ret != XZ_STREAM_END)\n                return ret;\n\n            s->sequence = SEQ_BLOCK_PADDING;\n\n        case SEQ_BLOCK_PADDING:\n            /*\n             * Size of Compressed Data + Block Padding\n             * must be a multiple of four. We don't need\n             * s->block.compressed for anything else\n             * anymore, so we use it here to test the size\n             * of the Block Padding field.\n             */\n            while (s->block.compressed & 3) {\n                if (b->in_pos == b->in_size)\n                    return XZ_OK;\n\n                if (b->in[b->in_pos++] != 0)\n                    return XZ_DATA_ERROR;\n\n                ++s->block.compressed;\n            }\n\n            s->sequence = SEQ_BLOCK_CHECK;\n\n        case SEQ_BLOCK_CHECK:\n            if (s->check_type == XZ_CHECK_CRC32) {\n                ret = crc_validate(s, b, 32);\n                if (ret != XZ_STREAM_END)\n                    return ret;\n            }\n            else if (IS_CRC64(s->check_type)) {\n                ret = crc_validate(s, b, 64);\n                if (ret != XZ_STREAM_END)\n                    return ret;\n            }\n#ifdef XZ_DEC_ANY_CHECK\n            else if (!check_skip(s, b)) {\n                return XZ_OK;\n            }\n#endif\n\n            s->sequence = SEQ_BLOCK_START;\n            break;\n\n        case SEQ_INDEX:\n            ret = dec_index(s, b);\n            if (ret != XZ_STREAM_END)\n                return ret;\n\n            s->sequence = SEQ_INDEX_PADDING;\n\n        case SEQ_INDEX_PADDING:\n            while ((s->index.size + (b->in_pos - s->in_start))\n                    & 3) {\n                if (b->in_pos == b->in_size) {\n                    index_update(s, b);\n                    return XZ_OK;\n                }\n\n                if (b->in[b->in_pos++] != 0)\n                    return XZ_DATA_ERROR;\n            }\n\n            /* Finish the CRC32 value and Index size. */\n            index_update(s, b);\n\n            /* Compare the hashes to validate the Index field. */\n            if (!memeq(&s->block.hash, &s->index.hash,\n                    sizeof(s->block.hash)))\n                return XZ_DATA_ERROR;\n\n            s->sequence = SEQ_INDEX_CRC32;\n\n        case SEQ_INDEX_CRC32:\n            ret = crc_validate(s, b, 32);\n            if (ret != XZ_STREAM_END)\n                return ret;\n\n            s->temp.size = STREAM_HEADER_SIZE;\n            s->sequence = SEQ_STREAM_FOOTER;\n\n        case SEQ_STREAM_FOOTER:\n            if (!fill_temp(s, b))\n                return XZ_OK;\n\n            return dec_stream_footer(s);\n        }\n    }\n\n    /* Never reached */\n}\n\n/*\n * xz_dec_run() is a wrapper for dec_main() to handle some special cases in\n * multi-call and single-call decoding.\n *\n * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we\n * are not going to make any progress anymore. This is to prevent the caller\n * from calling us infinitely when the input file is truncated or otherwise\n * corrupt. Since zlib-style API allows that the caller fills the input buffer\n * only when the decoder doesn't produce any new output, we have to be careful\n * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only\n * after the second consecutive call to xz_dec_run() that makes no progress.\n *\n * In single-call mode, if we couldn't decode everything and no error\n * occurred, either the input is truncated or the output buffer is too small.\n * Since we know that the last input byte never produces any output, we know\n * that if all the input was consumed and decoding wasn't finished, the file\n * must be corrupt. Otherwise the output buffer has to be too small or the\n * file is corrupt in a way that decoding it produces too big output.\n *\n * If single-call decoding fails, we reset b->in_pos and b->out_pos back to\n * their original values. This is because with some filter chains there won't\n * be any valid uncompressed data in the output buffer unless the decoding\n * actually succeeds (that's the price to pay of using the output buffer as\n * the workspace).\n */\nXZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b)\n{\n    size_t in_start;\n    size_t out_start;\n    enum xz_ret ret;\n\n    if (DEC_IS_SINGLE(s->mode))\n        xz_dec_reset(s);\n\n    in_start = b->in_pos;\n    out_start = b->out_pos;\n    ret = dec_main(s, b);\n\n    if (DEC_IS_SINGLE(s->mode)) {\n        if (ret == XZ_OK)\n            ret = b->in_pos == b->in_size\n                    ? XZ_DATA_ERROR : XZ_BUF_ERROR;\n\n        if (ret != XZ_STREAM_END) {\n            b->in_pos = in_start;\n            b->out_pos = out_start;\n        }\n\n    } else if (ret == XZ_OK && in_start == b->in_pos\n            && out_start == b->out_pos) {\n        if (s->allow_buf_error)\n            ret = XZ_BUF_ERROR;\n\n        s->allow_buf_error = true;\n    } else {\n        s->allow_buf_error = false;\n    }\n\n    return ret;\n}\n\nXZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max)\n{\n    struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL);\n    if (s == NULL)\n        return NULL;\n\n    s->mode = mode;\n\n#ifdef XZ_DEC_BCJ\n    s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode));\n    if (s->bcj == NULL)\n        goto error_bcj;\n#endif\n\n    s->lzma2 = xz_dec_lzma2_create(mode, dict_max);\n    if (s->lzma2 == NULL)\n        goto error_lzma2;\n\n    xz_dec_reset(s);\n    return s;\n\nerror_lzma2:\n#ifdef XZ_DEC_BCJ\n    xz_dec_bcj_end(s->bcj);\nerror_bcj:\n#endif\n    kfree(s);\n    return NULL;\n}\n\nXZ_EXTERN void xz_dec_reset(struct xz_dec *s)\n{\n    s->sequence = SEQ_STREAM_HEADER;\n    s->allow_buf_error = false;\n    s->pos = 0;\n    s->crc = 0;\n    memzero(&s->block, sizeof(s->block));\n    memzero(&s->index, sizeof(s->index));\n    s->temp.pos = 0;\n    s->temp.size = STREAM_HEADER_SIZE;\n}\n\nXZ_EXTERN void xz_dec_end(struct xz_dec *s)\n{\n    if (s != NULL) {\n        xz_dec_lzma2_end(s->lzma2);\n#ifdef XZ_DEC_BCJ\n        xz_dec_bcj_end(s->bcj);\n#endif\n        kfree(s);\n    }\n}\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_lzma2.h",
    "content": "/*\n * LZMA2 definitions\n *\n * Authors: Lasse Collin <lasse.collin@tukaani.org>\n *          Igor Pavlov <http://7-zip.org/>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#ifndef XZ_LZMA2_H\n#define XZ_LZMA2_H\n\n/* Range coder constants */\n#define RC_SHIFT_BITS 8\n#define RC_TOP_BITS 24\n#define RC_TOP_VALUE (1 << RC_TOP_BITS)\n#define RC_BIT_MODEL_TOTAL_BITS 11\n#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS)\n#define RC_MOVE_BITS 5\n\n/*\n * Maximum number of position states. A position state is the lowest pb\n * number of bits of the current uncompressed offset. In some places there\n * are different sets of probabilities for different position states.\n */\n#define POS_STATES_MAX (1 << 4)\n\n/*\n * This enum is used to track which LZMA symbols have occurred most recently\n * and in which order. This information is used to predict the next symbol.\n *\n * Symbols:\n *  - Literal: One 8-bit byte\n *  - Match: Repeat a chunk of data at some distance\n *  - Long repeat: Multi-byte match at a recently seen distance\n *  - Short repeat: One-byte repeat at a recently seen distance\n *\n * The symbol names are in from STATE_oldest_older_previous. REP means\n * either short or long repeated match, and NONLIT means any non-literal.\n */\nenum lzma_state {\n    STATE_LIT_LIT,\n    STATE_MATCH_LIT_LIT,\n    STATE_REP_LIT_LIT,\n    STATE_SHORTREP_LIT_LIT,\n    STATE_MATCH_LIT,\n    STATE_REP_LIT,\n    STATE_SHORTREP_LIT,\n    STATE_LIT_MATCH,\n    STATE_LIT_LONGREP,\n    STATE_LIT_SHORTREP,\n    STATE_NONLIT_MATCH,\n    STATE_NONLIT_REP\n};\n\n/* Total number of states */\n#define STATES 12\n\n/* The lowest 7 states indicate that the previous state was a literal. */\n#define LIT_STATES 7\n\n/* Indicate that the latest symbol was a literal. */\nstatic inline void lzma_state_literal(enum lzma_state *state)\n{\n    if (*state <= STATE_SHORTREP_LIT_LIT)\n        *state = STATE_LIT_LIT;\n    else if (*state <= STATE_LIT_SHORTREP)\n        *state -= 3;\n    else\n        *state -= 6;\n}\n\n/* Indicate that the latest symbol was a match. */\nstatic inline void lzma_state_match(enum lzma_state *state)\n{\n    *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH;\n}\n\n/* Indicate that the latest state was a long repeated match. */\nstatic inline void lzma_state_long_rep(enum lzma_state *state)\n{\n    *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP;\n}\n\n/* Indicate that the latest symbol was a short match. */\nstatic inline void lzma_state_short_rep(enum lzma_state *state)\n{\n    *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP;\n}\n\n/* Test if the previous symbol was a literal. */\nstatic inline bool lzma_state_is_literal(enum lzma_state state)\n{\n    return state < LIT_STATES;\n}\n\n/* Each literal coder is divided in three sections:\n *   - 0x001-0x0FF: Without match byte\n *   - 0x101-0x1FF: With match byte; match bit is 0\n *   - 0x201-0x2FF: With match byte; match bit is 1\n *\n * Match byte is used when the previous LZMA symbol was something else than\n * a literal (that is, it was some kind of match).\n */\n#define LITERAL_CODER_SIZE 0x300\n\n/* Maximum number of literal coders */\n#define LITERAL_CODERS_MAX (1 << 4)\n\n/* Minimum length of a match is two bytes. */\n#define MATCH_LEN_MIN 2\n\n/* Match length is encoded with 4, 5, or 10 bits.\n *\n * Length   Bits\n *  2-9      4 = Choice=0 + 3 bits\n * 10-17     5 = Choice=1 + Choice2=0 + 3 bits\n * 18-273   10 = Choice=1 + Choice2=1 + 8 bits\n */\n#define LEN_LOW_BITS 3\n#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS)\n#define LEN_MID_BITS 3\n#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS)\n#define LEN_HIGH_BITS 8\n#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS)\n#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS)\n\n/*\n * Maximum length of a match is 273 which is a result of the encoding\n * described above.\n */\n#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1)\n\n/*\n * Different sets of probabilities are used for match distances that have\n * very short match length: Lengths of 2, 3, and 4 bytes have a separate\n * set of probabilities for each length. The matches with longer length\n * use a shared set of probabilities.\n */\n#define DIST_STATES 4\n\n/*\n * Get the index of the appropriate probability array for decoding\n * the distance slot.\n */\nstatic inline uint32_t lzma_get_dist_state(uint32_t len)\n{\n    return len < DIST_STATES + MATCH_LEN_MIN\n            ? len - MATCH_LEN_MIN : DIST_STATES - 1;\n}\n\n/*\n * The highest two bits of a 32-bit match distance are encoded using six bits.\n * This six-bit value is called a distance slot. This way encoding a 32-bit\n * value takes 6-36 bits, larger values taking more bits.\n */\n#define DIST_SLOT_BITS 6\n#define DIST_SLOTS (1 << DIST_SLOT_BITS)\n\n/* Match distances up to 127 are fully encoded using probabilities. Since\n * the highest two bits (distance slot) are always encoded using six bits,\n * the distances 0-3 don't need any additional bits to encode, since the\n * distance slot itself is the same as the actual distance. DIST_MODEL_START\n * indicates the first distance slot where at least one additional bit is\n * needed.\n */\n#define DIST_MODEL_START 4\n\n/*\n * Match distances greater than 127 are encoded in three pieces:\n *   - distance slot: the highest two bits\n *   - direct bits: 2-26 bits below the highest two bits\n *   - alignment bits: four lowest bits\n *\n * Direct bits don't use any probabilities.\n *\n * The distance slot value of 14 is for distances 128-191.\n */\n#define DIST_MODEL_END 14\n\n/* Distance slots that indicate a distance <= 127. */\n#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2)\n#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS)\n\n/*\n * For match distances greater than 127, only the highest two bits and the\n * lowest four bits (alignment) is encoded using probabilities.\n */\n#define ALIGN_BITS 4\n#define ALIGN_SIZE (1 << ALIGN_BITS)\n#define ALIGN_MASK (ALIGN_SIZE - 1)\n\n/* Total number of all probability variables */\n#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX * LITERAL_CODER_SIZE)\n\n/*\n * LZMA remembers the four most recent match distances. Reusing these\n * distances tends to take less space than re-encoding the actual\n * distance value.\n */\n#define REPS 4\n\n#endif\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_private.h",
    "content": "/*\n * Private includes and definitions\n *\n * Author: Lasse Collin <lasse.collin@tukaani.org>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#ifndef XZ_PRIVATE_H\n#define XZ_PRIVATE_H\n\n#ifdef __KERNEL__\n#\tinclude <linux/xz.h>\n#\tinclude <linux/kernel.h>\n#\tinclude <asm/unaligned.h>\n    /* XZ_PREBOOT may be defined only via decompress_unxz.c. */\n#\tifndef XZ_PREBOOT\n#\t\tinclude <linux/slab.h>\n#\t\tinclude <linux/vmalloc.h>\n#\t\tinclude <linux/string.h>\n#\t\tifdef CONFIG_XZ_DEC_X86\n#\t\t\tdefine XZ_DEC_X86\n#\t\tendif\n#\t\tifdef CONFIG_XZ_DEC_POWERPC\n#\t\t\tdefine XZ_DEC_POWERPC\n#\t\tendif\n#\t\tifdef CONFIG_XZ_DEC_IA64\n#\t\t\tdefine XZ_DEC_IA64\n#\t\tendif\n#\t\tifdef CONFIG_XZ_DEC_ARM\n#\t\t\tdefine XZ_DEC_ARM\n#\t\tendif\n#\t\tifdef CONFIG_XZ_DEC_ARMTHUMB\n#\t\t\tdefine XZ_DEC_ARMTHUMB\n#\t\tendif\n#\t\tifdef CONFIG_XZ_DEC_SPARC\n#\t\t\tdefine XZ_DEC_SPARC\n#\t\tendif\n#\t\tdefine memeq(a, b, size) (memcmp(a, b, size) == 0)\n#\t\tdefine memzero(buf, size) memset(buf, 0, size)\n#\tendif\n#\tdefine get_le32(p) le32_to_cpup((const uint32_t *)(p))\n#else\n    /*\n     * For userspace builds, use a separate header to define the required\n     * macros and functions. This makes it easier to adapt the code into\n     * different environments and avoids clutter in the Linux kernel tree.\n     */\n#\tinclude \"xz_config.h\"\n#endif\n\n/* If no specific decoding mode is requested, enable support for all modes. */\n#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) \\\n        && !defined(XZ_DEC_DYNALLOC)\n#\tdefine XZ_DEC_SINGLE\n#\tdefine XZ_DEC_PREALLOC\n#\tdefine XZ_DEC_DYNALLOC\n#endif\n\n/*\n * The DEC_IS_foo(mode) macros are used in \"if\" statements. If only some\n * of the supported modes are enabled, these macros will evaluate to true or\n * false at compile time and thus allow the compiler to omit unneeded code.\n */\n#ifdef XZ_DEC_SINGLE\n#\tdefine DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE)\n#else\n#\tdefine DEC_IS_SINGLE(mode) (false)\n#endif\n\n#ifdef XZ_DEC_PREALLOC\n#\tdefine DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC)\n#else\n#\tdefine DEC_IS_PREALLOC(mode) (false)\n#endif\n\n#ifdef XZ_DEC_DYNALLOC\n#\tdefine DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC)\n#else\n#\tdefine DEC_IS_DYNALLOC(mode) (false)\n#endif\n\n#if !defined(XZ_DEC_SINGLE)\n#\tdefine DEC_IS_MULTI(mode) (true)\n#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC)\n#\tdefine DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE)\n#else\n#\tdefine DEC_IS_MULTI(mode) (false)\n#endif\n\n/*\n * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ.\n * XZ_DEC_BCJ is used to enable generic support for BCJ decoders.\n */\n#ifndef XZ_DEC_BCJ\n#\tif defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) \\\n            || defined(XZ_DEC_IA64) || defined(XZ_DEC_ARM) \\\n            || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) \\\n            || defined(XZ_DEC_SPARC)\n#\t\tdefine XZ_DEC_BCJ\n#\tendif\n#endif\n\n/*\n * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used\n * before calling xz_dec_lzma2_run().\n */\nXZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode,\n                           uint32_t dict_max);\n\n/*\n * Decode the LZMA2 properties (one byte) and reset the decoder. Return\n * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not\n * big enough, and XZ_OPTIONS_ERROR if props indicates something that this\n * decoder doesn't support.\n */\nXZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s,\n                     uint8_t props);\n\n/* Decode raw LZMA2 stream from b->in to b->out. */\nXZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s,\n                       struct xz_buf *b);\n\n/* Free the memory allocated for the LZMA2 decoder. */\nXZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s);\n\n#ifdef XZ_DEC_BCJ\n/*\n * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before\n * calling xz_dec_bcj_run().\n */\nXZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call);\n\n/*\n * Decode the Filter ID of a BCJ filter. This implementation doesn't\n * support custom start offsets, so no decoding of Filter Properties\n * is needed. Returns XZ_OK if the given Filter ID is supported.\n * Otherwise XZ_OPTIONS_ERROR is returned.\n */\nXZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id);\n\n/*\n * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is\n * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run()\n * must be called directly.\n */\nXZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s,\n                     struct xz_dec_lzma2 *lzma2,\n                     struct xz_buf *b);\n\n/* Free the memory allocated for the BCJ filters. */\n#define xz_dec_bcj_end(s) kfree(s)\n#endif\n\n#endif\n"
  },
  {
    "path": "native/src/external/xz-embedded/xz_stream.h",
    "content": "/*\n * Definitions for handling the .xz file format\n *\n * Author: Lasse Collin <lasse.collin@tukaani.org>\n *\n * This file has been put into the public domain.\n * You can do whatever you want with this file.\n */\n\n#ifndef XZ_STREAM_H\n#define XZ_STREAM_H\n\n#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32\n#\tinclude <linux/crc32.h>\n#\tundef crc32\n#\tdefine xz_crc32(buf, size, crc) \\\n        (~crc32_le(~(uint32_t)(crc), buf, size))\n#endif\n\n/*\n * See the .xz file format specification at\n * http://tukaani.org/xz/xz-file-format.txt\n * to understand the container format.\n */\n\n#define STREAM_HEADER_SIZE 12\n\n#define HEADER_MAGIC \"\\3757zXZ\"\n#define HEADER_MAGIC_SIZE 6\n\n#define FOOTER_MAGIC \"YZ\"\n#define FOOTER_MAGIC_SIZE 2\n\n/*\n * Variable-length integer can hold a 63-bit unsigned integer or a special\n * value indicating that the value is unknown.\n *\n * Experimental: vli_type can be defined to uint32_t to save a few bytes\n * in code size (no effect on speed). Doing so limits the uncompressed and\n * compressed size of the file to less than 256 MiB and may also weaken\n * error detection slightly.\n */\ntypedef uint64_t vli_type;\n\n#define VLI_MAX ((vli_type)-1 / 2)\n#define VLI_UNKNOWN ((vli_type)-1)\n\n/* Maximum encoded size of a VLI */\n#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7)\n\n/* Integrity Check types */\nenum xz_check {\n    XZ_CHECK_NONE = 0,\n    XZ_CHECK_CRC32 = 1,\n    XZ_CHECK_CRC64 = 4,\n    XZ_CHECK_SHA256 = 10\n};\n\n/* Maximum possible Check ID */\n#define XZ_CHECK_MAX 15\n\n#endif\n"
  },
  {
    "path": "native/src/external/xz_config/config.h",
    "content": "/* config.h.  Generated from config.h.in by configure.  */\n/* config.h.in.  Generated from configure.ac by autoheader.  */\n\n/* Define if building universal (internal helper macro) */\n/* #undef AC_APPLE_UNIVERSAL_BUILD */\n\n/* How many MiB of RAM to assume if the real amount cannot be determined. */\n#define ASSUME_RAM 128\n\n/* Define to 1 if translation of program messages to the user's native\n   language is requested. */\n/* #undef ENABLE_NLS */\n\n/* Define to 1 if bswap_16 is available. */\n#define HAVE_BSWAP_16 1\n\n/* Define to 1 if bswap_32 is available. */\n#define HAVE_BSWAP_32 1\n\n/* Define to 1 if bswap_64 is available. */\n#define HAVE_BSWAP_64 1\n\n/* Define to 1 if you have the <byteswap.h> header file. */\n#define HAVE_BYTESWAP_H 1\n\n/* Define to 1 if Capsicum is available. */\n/* #undef HAVE_CAPSICUM */\n\n/* Define to 1 if the system has the type `CC_SHA256_CTX'. */\n/* #undef HAVE_CC_SHA256_CTX */\n\n/* Define to 1 if you have the `CC_SHA256_Init' function. */\n/* #undef HAVE_CC_SHA256_INIT */\n\n/* Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the\n   CoreFoundation framework. */\n/* #undef HAVE_CFLOCALECOPYCURRENT */\n\n/* Define to 1 if you have the MacOS X function CFPreferencesCopyAppValue in\n   the CoreFoundation framework. */\n/* #undef HAVE_CFPREFERENCESCOPYAPPVALUE */\n\n/* Define to 1 if crc32 integrity check is enabled. */\n#define HAVE_CHECK_CRC32 1\n\n/* Define to 1 if crc64 integrity check is enabled. */\n#define HAVE_CHECK_CRC64 1\n\n/* Define to 1 if sha256 integrity check is enabled. */\n#define HAVE_CHECK_SHA256 1\n\n/* Define to 1 if you have the `clock_gettime' function. */\n#define HAVE_CLOCK_GETTIME 1\n\n/* Define to 1 if you have the <CommonCrypto/CommonDigest.h> header file. */\n/* #undef HAVE_COMMONCRYPTO_COMMONDIGEST_H */\n\n/* Define if the GNU dcgettext() function is already present or preinstalled.\n   */\n/* #undef HAVE_DCGETTEXT */\n\n/* Define to 1 if you have the declaration of `CLOCK_MONOTONIC', and to 0 if\n   you don't. */\n#define HAVE_DECL_CLOCK_MONOTONIC 1\n\n/* Define to 1 if you have the declaration of `program_invocation_name', and\n   to 0 if you don't. */\n#define HAVE_DECL_PROGRAM_INVOCATION_NAME 0\n\n/* Define to 1 if any of HAVE_DECODER_foo have been defined. */\n#define HAVE_DECODERS 1\n\n/* Define to 1 if arm decoder is enabled. */\n#define HAVE_DECODER_ARM 1\n\n/* Define to 1 if armthumb decoder is enabled. */\n#define HAVE_DECODER_ARMTHUMB 1\n\n/* Define to 1 if delta decoder is enabled. */\n#define HAVE_DECODER_DELTA 1\n\n/* Define to 1 if ia64 decoder is enabled. */\n#define HAVE_DECODER_IA64 1\n\n/* Define to 1 if lzma1 decoder is enabled. */\n#define HAVE_DECODER_LZMA1 1\n\n/* Define to 1 if lzma2 decoder is enabled. */\n#define HAVE_DECODER_LZMA2 1\n\n/* Define to 1 if powerpc decoder is enabled. */\n#define HAVE_DECODER_POWERPC 1\n\n/* Define to 1 if sparc decoder is enabled. */\n#define HAVE_DECODER_SPARC 1\n\n/* Define to 1 if x86 decoder is enabled. */\n#define HAVE_DECODER_X86 1\n\n/* Define to 1 if you have the <dlfcn.h> header file. */\n#define HAVE_DLFCN_H 1\n\n/* Define to 1 if any of HAVE_ENCODER_foo have been defined. */\n#define HAVE_ENCODERS 1\n\n/* Define to 1 if arm encoder is enabled. */\n#define HAVE_ENCODER_ARM 1\n\n/* Define to 1 if armthumb encoder is enabled. */\n#define HAVE_ENCODER_ARMTHUMB 1\n\n/* Define to 1 if delta encoder is enabled. */\n#define HAVE_ENCODER_DELTA 1\n\n/* Define to 1 if ia64 encoder is enabled. */\n#define HAVE_ENCODER_IA64 1\n\n/* Define to 1 if lzma1 encoder is enabled. */\n#define HAVE_ENCODER_LZMA1 1\n\n/* Define to 1 if lzma2 encoder is enabled. */\n#define HAVE_ENCODER_LZMA2 1\n\n/* Define to 1 if powerpc encoder is enabled. */\n#define HAVE_ENCODER_POWERPC 1\n\n/* Define to 1 if sparc encoder is enabled. */\n#define HAVE_ENCODER_SPARC 1\n\n/* Define to 1 if x86 encoder is enabled. */\n#define HAVE_ENCODER_X86 1\n\n/* Define to 1 if you have the <fcntl.h> header file. */\n#define HAVE_FCNTL_H 1\n\n/* Define to 1 if you have the `futimens' function. */\n#define HAVE_FUTIMENS 1\n\n/* Define to 1 if you have the `futimes' function. */\n/* #undef HAVE_FUTIMES */\n\n/* Define to 1 if you have the `futimesat' function. */\n/* #undef HAVE_FUTIMESAT */\n\n/* Define to 1 if you have the <getopt.h> header file. */\n#define HAVE_GETOPT_H 1\n\n/* Define to 1 if you have the `getopt_long' function. */\n#define HAVE_GETOPT_LONG 1\n\n/* Define if the GNU gettext() function is already present or preinstalled. */\n/* #undef HAVE_GETTEXT */\n\n/* Define if you have the iconv() function and it works. */\n/* #undef HAVE_ICONV */\n\n/* Define to 1 if you have the <immintrin.h> header file. */\n/* #undef HAVE_IMMINTRIN_H */\n\n/* Define to 1 if you have the <inttypes.h> header file. */\n#define HAVE_INTTYPES_H 1\n\n/* Define to 1 if you have the <limits.h> header file. */\n#define HAVE_LIMITS_H 1\n\n/* Define to 1 if mbrtowc and mbstate_t are properly declared. */\n#define HAVE_MBRTOWC 1\n\n/* Define to 1 if you have the <memory.h> header file. */\n#define HAVE_MEMORY_H 1\n\n/* Define to 1 to enable bt2 match finder. */\n#define HAVE_MF_BT2 1\n\n/* Define to 1 to enable bt3 match finder. */\n#define HAVE_MF_BT3 1\n\n/* Define to 1 to enable bt4 match finder. */\n#define HAVE_MF_BT4 1\n\n/* Define to 1 to enable hc3 match finder. */\n#define HAVE_MF_HC3 1\n\n/* Define to 1 to enable hc4 match finder. */\n#define HAVE_MF_HC4 1\n\n/* Define to 1 if you have the <minix/sha2.h> header file. */\n/* #undef HAVE_MINIX_SHA2_H */\n\n/* Define to 1 if getopt.h declares extern int optreset. */\n#define HAVE_OPTRESET 1\n\n/* Define to 1 if you have the `posix_fadvise' function. */\n#define HAVE_POSIX_FADVISE 1\n\n/* Define to 1 if you have the `pthread_condattr_setclock' function. */\n#define HAVE_PTHREAD_CONDATTR_SETCLOCK 1\n\n/* Have PTHREAD_PRIO_INHERIT. */\n/* #undef HAVE_PTHREAD_PRIO_INHERIT */\n\n/* Define to 1 if you have the `SHA256Init' function. */\n/* #undef HAVE_SHA256INIT */\n\n/* Define to 1 if the system has the type `SHA256_CTX'. */\n/* #undef HAVE_SHA256_CTX */\n\n/* Define to 1 if you have the <sha256.h> header file. */\n/* #undef HAVE_SHA256_H */\n\n/* Define to 1 if you have the `SHA256_Init' function. */\n/* #undef HAVE_SHA256_INIT */\n\n/* Define to 1 if the system has the type `SHA2_CTX'. */\n/* #undef HAVE_SHA2_CTX */\n\n/* Define to 1 if you have the <sha2.h> header file. */\n/* #undef HAVE_SHA2_H */\n\n/* Define to 1 if optimizing for size. */\n/* #undef HAVE_SMALL */\n\n/* Define to 1 if stdbool.h conforms to C99. */\n#define HAVE_STDBOOL_H 1\n\n/* Define to 1 if you have the <stdint.h> header file. */\n#define HAVE_STDINT_H 1\n\n/* Define to 1 if you have the <stdlib.h> header file. */\n#define HAVE_STDLIB_H 1\n\n/* Define to 1 if you have the <strings.h> header file. */\n#define HAVE_STRINGS_H 1\n\n/* Define to 1 if you have the <string.h> header file. */\n#define HAVE_STRING_H 1\n\n/* Define to 1 if `st_atimensec' is a member of `struct stat'. */\n#define HAVE_STRUCT_STAT_ST_ATIMENSEC 1\n\n/* Define to 1 if `st_atimespec.tv_nsec' is a member of `struct stat'. */\n/* #undef HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC */\n\n/* Define to 1 if `st_atim.st__tim.tv_nsec' is a member of `struct stat'. */\n/* #undef HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC */\n\n/* Define to 1 if `st_atim.tv_nsec' is a member of `struct stat'. */\n/* #undef HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC */\n\n/* Define to 1 if `st_uatime' is a member of `struct stat'. */\n/* #undef HAVE_STRUCT_STAT_ST_UATIME */\n\n/* Define to 1 if you have the <sys/byteorder.h> header file. */\n/* #undef HAVE_SYS_BYTEORDER_H */\n\n/* Define to 1 if you have the <sys/capsicum.h> header file. */\n/* #undef HAVE_SYS_CAPSICUM_H */\n\n/* Define to 1 if you have the <sys/endian.h> header file. */\n/* #undef HAVE_SYS_ENDIAN_H */\n\n/* Define to 1 if you have the <sys/param.h> header file. */\n#define HAVE_SYS_PARAM_H 1\n\n/* Define to 1 if you have the <sys/stat.h> header file. */\n#define HAVE_SYS_STAT_H 1\n\n/* Define to 1 if you have the <sys/time.h> header file. */\n#define HAVE_SYS_TIME_H 1\n\n/* Define to 1 if you have the <sys/types.h> header file. */\n#define HAVE_SYS_TYPES_H 1\n\n/* Define to 1 if the system has the type `uintptr_t'. */\n#define HAVE_UINTPTR_T 1\n\n/* Define to 1 if you have the <unistd.h> header file. */\n#define HAVE_UNISTD_H 1\n\n/* Define to 1 if you have the `utime' function. */\n/* #undef HAVE_UTIME */\n\n/* Define to 1 if you have the `utimes' function. */\n/* #undef HAVE_UTIMES */\n\n/* Define to 1 or 0, depending whether the compiler supports simple visibility\n   declarations. */\n#define HAVE_VISIBILITY 1\n\n/* Define to 1 if you have the `wcwidth' function. */\n#define HAVE_WCWIDTH 1\n\n/* Define to 1 if the system has the type `_Bool'. */\n#define HAVE__BOOL 1\n\n/* Define to 1 if _mm_movemask_epi8 is available. */\n/* #undef HAVE__MM_MOVEMASK_EPI8 */\n\n/* Define to the sub-directory where libtool stores uninstalled libraries. */\n#define LT_OBJDIR \".libs/\"\n\n/* Define to 1 when using POSIX threads (pthreads). */\n#define MYTHREAD_POSIX 1\n\n/* Define to 1 when using Windows Vista compatible threads. This uses features\n   that are not available on Windows XP. */\n/* #undef MYTHREAD_VISTA */\n\n/* Define to 1 when using Windows 95 (and thus XP) compatible threads. This\n   avoids use of features that were added in Windows Vista. */\n/* #undef MYTHREAD_WIN95 */\n\n/* Define to 1 to disable debugging code. */\n#define NDEBUG 1\n\n/* Name of package */\n#define PACKAGE \"xz\"\n\n/* Define to the address where bug reports for this package should be sent. */\n#define PACKAGE_BUGREPORT \"lasse.collin@tukaani.org\"\n\n/* Define to the full name of this package. */\n#define PACKAGE_NAME \"XZ Utils\"\n\n/* Define to the full name and version of this package. */\n#define PACKAGE_STRING \"XZ Utils 5.3.0alpha\"\n\n/* Define to the one symbol short name of this package. */\n#define PACKAGE_TARNAME \"xz\"\n\n/* Define to the home page for this package. */\n#define PACKAGE_URL \"http://tukaani.org/xz/\"\n\n/* Define to the version of this package. */\n#define PACKAGE_VERSION \"5.3.0alpha\"\n\n/* Define to necessary symbol if this constant uses a non-standard name on\n   your system. */\n/* #undef PTHREAD_CREATE_JOINABLE */\n\n/* The size of `size_t', as computed by sizeof. */\n#define SIZEOF_SIZE_T 4\n\n/* Define to 1 if you have the ANSI C header files. */\n#define STDC_HEADERS 1\n\n/* Define to 1 if the number of available CPU cores can be detected with\n   cpuset(2). */\n/* #undef TUKLIB_CPUCORES_CPUSET */\n\n/* Define to 1 if the number of available CPU cores can be detected with\n   pstat_getdynamic(). */\n/* #undef TUKLIB_CPUCORES_PSTAT_GETDYNAMIC */\n\n/* Define to 1 if the number of available CPU cores can be detected with\n   sysconf(_SC_NPROCESSORS_ONLN) or sysconf(_SC_NPROC_ONLN). */\n#define TUKLIB_CPUCORES_SYSCONF 1\n\n/* Define to 1 if the number of available CPU cores can be detected with\n   sysctl(). */\n/* #undef TUKLIB_CPUCORES_SYSCTL */\n\n/* Define to 1 if the system supports fast unaligned access to 16-bit and\n   32-bit integers. */\n/* #undef TUKLIB_FAST_UNALIGNED_ACCESS */\n\n/* Define to 1 if the amount of physical memory can be detected with\n   _system_configuration.physmem. */\n/* #undef TUKLIB_PHYSMEM_AIX */\n\n/* Define to 1 if the amount of physical memory can be detected with\n   getinvent_r(). */\n/* #undef TUKLIB_PHYSMEM_GETINVENT_R */\n\n/* Define to 1 if the amount of physical memory can be detected with\n   getsysinfo(). */\n/* #undef TUKLIB_PHYSMEM_GETSYSINFO */\n\n/* Define to 1 if the amount of physical memory can be detected with\n   pstat_getstatic(). */\n/* #undef TUKLIB_PHYSMEM_PSTAT_GETSTATIC */\n\n/* Define to 1 if the amount of physical memory can be detected with\n   sysconf(_SC_PAGESIZE) and sysconf(_SC_PHYS_PAGES). */\n#define TUKLIB_PHYSMEM_SYSCONF 1\n\n/* Define to 1 if the amount of physical memory can be detected with sysctl().\n   */\n/* #undef TUKLIB_PHYSMEM_SYSCTL */\n\n/* Define to 1 if the amount of physical memory can be detected with Linux\n   sysinfo(). */\n/* #undef TUKLIB_PHYSMEM_SYSINFO */\n\n/* Enable extensions on AIX 3, Interix.  */\n#ifndef _ALL_SOURCE\n# define _ALL_SOURCE 1\n#endif\n/* Enable GNU extensions on systems that have them.  */\n#ifndef _GNU_SOURCE\n# define _GNU_SOURCE 1\n#endif\n/* Enable threading extensions on Solaris.  */\n#ifndef _POSIX_PTHREAD_SEMANTICS\n# define _POSIX_PTHREAD_SEMANTICS 1\n#endif\n/* Enable extensions on HP NonStop.  */\n#ifndef _TANDEM_SOURCE\n# define _TANDEM_SOURCE 1\n#endif\n/* Enable general extensions on Solaris.  */\n#ifndef __EXTENSIONS__\n# define __EXTENSIONS__ 1\n#endif\n\n\n/* Version number of package */\n#define VERSION \"5.3.0alpha\"\n\n/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most\n   significant byte first (like Motorola and SPARC, unlike Intel). */\n#if defined AC_APPLE_UNIVERSAL_BUILD\n# if defined __BIG_ENDIAN__\n#  define WORDS_BIGENDIAN 1\n# endif\n#else\n# ifndef WORDS_BIGENDIAN\n/* #  undef WORDS_BIGENDIAN */\n# endif\n#endif\n\n/* Enable large inode numbers on Mac OS X 10.5.  */\n#ifndef _DARWIN_USE_64_BIT_INODE\n# define _DARWIN_USE_64_BIT_INODE 1\n#endif\n\n/* Number of bits in a file offset, on hosts where this is settable. */\n/* #undef _FILE_OFFSET_BITS */\n\n/* Define for large files, on AIX-style hosts. */\n/* #undef _LARGE_FILES */\n\n/* Define to 1 if on MINIX. */\n/* #undef _MINIX */\n\n/* Define to 2 if the system does not provide POSIX.1 features except with\n   this defined. */\n/* #undef _POSIX_1_SOURCE */\n\n/* Define to 1 if you need to in order for `stat' and other things to work. */\n/* #undef _POSIX_SOURCE */\n\n/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,\n   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the\n   #define below would cause a syntax error. */\n/* #undef _UINT32_T */\n\n/* Define for Solaris 2.5.1 so the uint64_t typedef from <sys/synch.h>,\n   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the\n   #define below would cause a syntax error. */\n/* #undef _UINT64_T */\n\n/* Define for Solaris 2.5.1 so the uint8_t typedef from <sys/synch.h>,\n   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the\n   #define below would cause a syntax error. */\n/* #undef _UINT8_T */\n\n/* Define to rpl_ if the getopt replacement functions and variables should be\n   used. */\n/* #undef __GETOPT_PREFIX */\n\n/* Define to the type of a signed integer type of width exactly 32 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef int32_t */\n\n/* Define to the type of a signed integer type of width exactly 64 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef int64_t */\n\n/* Define to the type of an unsigned integer type of width exactly 16 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint16_t */\n\n/* Define to the type of an unsigned integer type of width exactly 32 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint32_t */\n\n/* Define to the type of an unsigned integer type of width exactly 64 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint64_t */\n\n/* Define to the type of an unsigned integer type of width exactly 8 bits if\n   such a type exists and the standard includes do not define it. */\n/* #undef uint8_t */\n\n/* Define to the type of an unsigned integer type wide enough to hold a\n   pointer, if such a type exists, and if the system does not define it. */\n/* #undef uintptr_t */\n"
  },
  {
    "path": "native/src/include/codegen.rs",
    "content": "// This file hosts shared build script logic\n\nuse std::fmt::Display;\nuse std::fs::File;\nuse std::io::Write;\nuse std::path::Path;\nuse std::{fs, io, process};\n\nuse cxx_gen::{Include, IncludeKind, Opt};\n\ntrait ResultExt<T> {\n    fn ok_or_exit(self) -> T;\n}\n\nimpl<T, E: Display> ResultExt<T> for Result<T, E> {\n    fn ok_or_exit(self) -> T {\n        match self {\n            Ok(r) => r,\n            Err(e) => {\n                eprintln!(\"error occurred: {e}\");\n                process::exit(1);\n            }\n        }\n    }\n}\n\nfn write_if_diff<P: AsRef<Path>>(path: P, bytes: &[u8]) -> io::Result<()> {\n    let path = path.as_ref();\n    if let Ok(orig) = fs::read(path) {\n        // Do not modify the file if content is the same to make incremental build more optimal\n        if orig.as_slice() == bytes {\n            return Ok(());\n        }\n    }\n    let mut f = File::create(path)?;\n    f.write_all(bytes)\n}\n\npub fn gen_cxx_binding(name: &str) {\n    println!(\"cargo:rerun-if-changed=lib.rs\");\n    let mut opt = Opt::default();\n    opt.cxx_impl_annotations = Some(\"[[gnu::always_inline]]\".to_string());\n    opt.include.push(Include {\n        path: \"rust/cxx.h\".to_string(),\n        kind: IncludeKind::Bracketed,\n    });\n    let code = cxx_gen::generate_header_and_cc_with_path(\"lib.rs\", &opt);\n    write_if_diff(format!(\"{name}.cpp\"), code.implementation.as_slice()).ok_or_exit();\n    write_if_diff(format!(\"{name}.hpp\"), code.header.as_slice()).ok_or_exit();\n}\n"
  },
  {
    "path": "native/src/include/consts.hpp",
    "content": "#pragma once\n\n#define JAVA_PACKAGE_NAME \"com.topjohnwu.magisk\"\n#define SECURE_DIR      \"/data/adb\"\n#define MODULEROOT      SECURE_DIR \"/modules\"\n#define DATABIN         SECURE_DIR \"/magisk\"\n#define MAGISKDB        SECURE_DIR \"/magisk.db\"\n\n// tmpfs paths\n#define INTLROOT      \".magisk\"\n#define MIRRDIR       INTLROOT \"/mirror\"\n#define PREINITMIRR   INTLROOT \"/preinit\"\n#define DEVICEDIR     INTLROOT \"/device\"\n#define PREINITDEV    DEVICEDIR \"/preinit\"\n#define WORKERDIR     INTLROOT \"/worker\"\n#define BBPATH        INTLROOT \"/busybox\"\n#define ROOTOVL       INTLROOT \"/rootdir\"\n#define SHELLPTS      INTLROOT \"/pts\"\n#define MAIN_CONFIG   INTLROOT \"/config\"\n#define MAIN_SOCKET   DEVICEDIR \"/socket\"\n\nconstexpr const char *applet_names[] = { \"su\", \"resetprop\", nullptr };\n\n#define POST_FS_DATA_WAIT_TIME       40\n#define POST_FS_DATA_SCRIPT_MAX_TIME 35\n\n// Unconstrained domain the daemon and root processes run in\n#define SEPOL_PROC_DOMAIN   \"magisk\"\n#define MAGISK_PROC_CON     \"u:r:\" SEPOL_PROC_DOMAIN \":s0\"\n// Unconstrained file type that anyone can access\n#define SEPOL_FILE_TYPE     \"magisk_file\"\n#define MAGISK_FILE_CON     \"u:object_r:\" SEPOL_FILE_TYPE \":s0\"\n"
  },
  {
    "path": "native/src/include/consts.rs",
    "content": "#![allow(dead_code)]\nuse base::const_format::concatcp;\n\n#[path = \"../../out/generated/flags.rs\"]\nmod flags;\n\npub const POST_FS_DATA_WAIT_TIME: i32 = 40;\npub const APPLET_NAMES: &[&str] = &[\"su\", \"resetprop\"];\n\n// versions\npub use flags::*;\npub const MAGISK_FULL_VER: &str = concatcp!(MAGISK_VERSION, \"(\", MAGISK_VER_CODE, \")\");\n\npub const APP_PACKAGE_NAME: &str = \"com.topjohnwu.magisk\";\n\npub const LOGFILE: &str = \"/cache/magisk.log\";\n\n// data paths\npub const SECURE_DIR: &str = \"/data/adb\";\npub const MODULEROOT: &str = concatcp!(SECURE_DIR, \"/modules\");\npub const MODULEUPGRADE: &str = concatcp!(SECURE_DIR, \"/modules_update\");\npub const DATABIN: &str = concatcp!(SECURE_DIR, \"/magisk\");\npub const MAGISKDB: &str = concatcp!(SECURE_DIR, \"/magisk.db\");\n\n// tmpfs paths\npub const INTERNAL_DIR: &str = \".magisk\";\npub const MAIN_CONFIG: &str = concatcp!(INTERNAL_DIR, \"/config\");\npub const PREINITMIRR: &str = concatcp!(INTERNAL_DIR, \"/preinit\");\npub const MODULEMNT: &str = concatcp!(INTERNAL_DIR, \"/modules\");\npub const WORKERDIR: &str = concatcp!(INTERNAL_DIR, \"/worker\");\npub const BBPATH: &str = concatcp!(INTERNAL_DIR, \"/busybox\");\npub const DEVICEDIR: &str = concatcp!(INTERNAL_DIR, \"/device\");\npub const MAIN_SOCKET: &str = concatcp!(DEVICEDIR, \"/socket\");\npub const PREINITDEV: &str = concatcp!(DEVICEDIR, \"/preinit\");\npub const LOG_PIPE: &str = concatcp!(DEVICEDIR, \"/log\");\npub const ROOTOVL: &str = concatcp!(INTERNAL_DIR, \"/rootdir\");\npub const ROOTMNT: &str = concatcp!(ROOTOVL, \"/.mount_list\");\npub const SELINUXMOCK: &str = concatcp!(INTERNAL_DIR, \"/selinux\");\n\n// Unconstrained domain the daemon and root processes run in\npub const SEPOL_PROC_DOMAIN: &str = \"magisk\";\npub const MAGISK_PROC_CON: &str = concatcp!(\"u:r:\", SEPOL_PROC_DOMAIN, \":s0\");\n// Unconstrained file type that anyone can access\npub const SEPOL_FILE_TYPE: &str = \"magisk_file\";\npub const MAGISK_FILE_CON: &str = concatcp!(\"u:object_r:\", SEPOL_FILE_TYPE, \":s0\");\n// Log pipe that only root and zygote can open\npub const SEPOL_LOG_TYPE: &str = \"magisk_log_file\";\npub const MAGISK_LOG_CON: &str = concatcp!(\"u:object_r:\", SEPOL_LOG_TYPE, \":s0\");\n"
  },
  {
    "path": "native/src/init/Cargo.toml",
    "content": "[package]\nname = \"magiskinit\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\ncrate-type = [\"staticlib\"]\npath = \"lib.rs\"\n\n[lints]\nworkspace = true\n\n[build-dependencies]\ncxx-gen = { workspace = true }\n\n[dependencies]\nbase = { workspace = true }\nmagiskpolicy = { workspace = true, features = [\"no-main\"] }\ncxx = { workspace = true }\nnum-traits = { workspace = true }\n"
  },
  {
    "path": "native/src/init/build.rs",
    "content": "use crate::codegen::gen_cxx_binding;\n\n#[path = \"../include/codegen.rs\"]\nmod codegen;\n\nfn main() {\n    gen_cxx_binding(\"init-rs\");\n}\n"
  },
  {
    "path": "native/src/init/getinfo.cpp",
    "content": "#include <sys/sysmacros.h>\n#include <sys/types.h>\n#include <linux/input.h>\n#include <fcntl.h>\n#include <vector>\n\n#include <base.hpp>\n\n#include \"init.hpp\"\n\nusing namespace std;\n\ntemplate<char... cs> using chars = integer_sequence<char, cs...>;\n\n// If quoted, parsing ends when we find char in [breaks]\n// If not quoted, parsing ends when we find char in [breaks] + [escapes]\ntemplate<char... escapes, char... breaks>\nstatic string extract_quoted_str_until(chars<escapes...>, chars<breaks...>,\n        string_view str, size_t &pos, bool &quoted) {\n    string result;\n    char match_array[] = {escapes..., breaks..., '\"'};\n    string_view match(match_array, std::size(match_array));\n    for (size_t cur = pos;; ++cur) {\n        cur = str.find_first_of(match, cur);\n        if (cur == string_view::npos ||\n            ((str[cur] == breaks) || ...) ||\n            (!quoted && ((str[cur] == escapes) || ...))) {\n            result.append(str.substr(pos, cur - pos));\n            pos = cur;\n            return result;\n        }\n        if (str[cur] == '\"') {\n            quoted = !quoted;\n            result.append(str.substr(pos, cur - pos));\n            pos = cur + 1;\n        }\n    }\n}\n\n// Parse string into key value pairs.\n// The string format: [delim][key][padding][eq][padding][value][delim]\ntemplate<char delim, char eq, char... padding>\nstatic kv_pairs parse_impl(chars<padding...>, string_view str) {\n    kv_pairs kv;\n    char skip_array[] = {eq, padding...};\n    string_view skip(skip_array, std::size(skip_array));\n    bool quoted = false;\n    for (size_t pos = 0u; pos < str.size(); pos = str.find_first_not_of(delim, pos)) {\n        auto key = extract_quoted_str_until(\n                chars<padding..., delim>{}, chars<eq>{}, str, pos, quoted);\n        pos = str.find_first_not_of(skip, pos);\n        if (pos == string_view::npos || str[pos] == delim) {\n            kv.emplace_back(key, \"\");\n            continue;\n        }\n        auto value = extract_quoted_str_until(chars<delim>{}, chars<>{}, str, pos, quoted);\n        kv.emplace_back(key, value);\n    }\n    return kv;\n}\n\nstatic kv_pairs parse_cmdline(string_view str) {\n    return parse_impl<' ', '='>(chars<>{}, str);\n}\nstatic kv_pairs parse_bootconfig(string_view str) {\n    return parse_impl<'\\n', '='>(chars<' '>{}, str);\n}\nstatic kv_pairs parse_partition_map(std::string_view str) {\n    return parse_impl<';', ','>(chars<>{}, str);\n}\n\n#define test_bit(bit, array) (array[bit / 8] & (1 << (bit % 8)))\n\nstatic bool check_key_combo() {\n    LOGD(\"Running in recovery mode, waiting for key...\\n\");\n    uint8_t bitmask[(KEY_MAX + 1) / 8];\n    vector<int> events;\n    constexpr const char *name = \"/event\";\n\n    for (int minor = 64; minor < 96; ++minor) {\n        if (xmknod(name, S_IFCHR | 0444, makedev(13, minor)))\n            continue;\n        int fd = open(name, O_RDONLY | O_CLOEXEC);\n        unlink(name);\n        if (fd < 0)\n            continue;\n        memset(bitmask, 0, sizeof(bitmask));\n        ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(bitmask)), bitmask);\n        if (test_bit(KEY_VOLUMEUP, bitmask))\n            events.push_back(fd);\n        else\n            close(fd);\n    }\n    if (events.empty())\n        return false;\n\n    run_finally fin([&] { for_each(events.begin(), events.end(), close); });\n\n    // Return true if volume up key is held for more than 3 seconds\n    int count = 0;\n    for (int i = 0; i < 500; ++i) {\n        for (const int &fd : events) {\n            memset(bitmask, 0, sizeof(bitmask));\n            ioctl(fd, EVIOCGKEY(sizeof(bitmask)), bitmask);\n            if (test_bit(KEY_VOLUMEUP, bitmask)) {\n                count++;\n                break;\n            }\n        }\n        if (count >= 300) {\n            LOGD(\"KEY_VOLUMEUP detected: disable system-as-root\\n\");\n            return true;\n        }\n        // Check every 10ms\n        usleep(10000);\n    }\n    return false;\n}\n\nvoid BootConfig::set(const kv_pairs &kv) noexcept {\n    for (const auto &[key, value] : kv) {\n        if (key == \"androidboot.slot_suffix\") {\n            // Many Amlogic devices are A-only but have slot_suffix...\n            if (value == \"normal\") {\n                LOGW(\"Skip invalid androidboot.slot_suffix=[normal]\\n\");\n                continue;\n            }\n            strscpy(slot.data(), value.data(), slot.size());\n        } else if (key == \"androidboot.slot\") {\n            slot[0] = '_';\n            strscpy(slot.data() + 1, value.data(), slot.size() - 1);\n        } else if (key == \"skip_initramfs\") {\n            skip_initramfs = true;\n        } else if (key == \"androidboot.force_normal_boot\") {\n            force_normal_boot = !value.empty() && value[0] == '1';\n        } else if (key == \"rootwait\") {\n            rootwait = true;\n        } else if (key == \"androidboot.android_dt_dir\") {\n            strscpy(dt_dir.data(), value.data(), dt_dir.size());\n        } else if (key == \"androidboot.hardware\") {\n            strscpy(hardware.data(), value.data(), hardware.size());\n        } else if (key == \"androidboot.hardware.platform\") {\n            strscpy(hardware_plat.data(), value.data(), hardware_plat.size());\n        } else if (key == \"androidboot.fstab_suffix\") {\n            strscpy(fstab_suffix.data(), value.data(), fstab_suffix.size());\n        } else if (key == \"androidboot.mode\") {\n            strscpy(boot_mode.data(), value.data(), boot_mode.size());\n        } else if (key == \"qemu\") {\n            emulator = true;\n        } else if (key == \"androidboot.partition_map\") {\n            // androidboot.partition_map allows mapping a partition name to a raw block device.\n            // For example, \"androidboot.partition_map=vdb,metadata;vdc,userdata\" maps\n            // \"vdb\" to \"metadata\", and \"vdc\" to \"userdata\".\n            // https://android.googlesource.com/platform/system/core/+/refs/heads/android13-release/init/devices.cpp#191\n            for (const auto &[k, v]: parse_partition_map(value)) {\n                partition_map.emplace_back(k, v);\n            }\n        }\n    }\n}\n\n#define read_dt(name, key)                                          \\\nssprintf(file_name, sizeof(file_name), \"%s/\" name, dt_dir.data());  \\\nif (access(file_name, R_OK) == 0) {                                 \\\n    string data = full_read(file_name);                             \\\n    if (!data.empty()) {                                            \\\n        data.pop_back();                                            \\\n        strscpy(key.data(), data.data(), key.size());               \\\n    }                                                               \\\n}\n\nvoid BootConfig::init() noexcept {\n    set(parse_cmdline(full_read(\"/proc/cmdline\")));\n    set(parse_bootconfig(full_read(\"/proc/bootconfig\")));\n\n    parse_prop_file(\"/.backup/.magisk\", [&](auto key, auto value) -> bool {\n        if (key == \"RECOVERYMODE\" && value == \"true\") {\n            skip_initramfs = emulator || !check_key_combo();\n            return false;\n        }\n        return true;\n    });\n\n    if (dt_dir[0] == '\\0')\n        strscpy(dt_dir.data(), DEFAULT_DT_DIR, dt_dir.size());\n\n    char file_name[128];\n    read_dt(\"fstab_suffix\", fstab_suffix)\n    read_dt(\"hardware\", hardware)\n    read_dt(\"hardware.platform\", hardware_plat)\n\n    LOGD(\"Device config:\\n\");\n    print();\n}\n"
  },
  {
    "path": "native/src/init/getinfo.rs",
    "content": "use crate::ffi::{BootConfig, MagiskInit, backup_init};\nuse base::{BytesExt, MappedFile, cstr};\n\nimpl BootConfig {\n    #[allow(unused_imports, unused_unsafe)]\n    pub(crate) fn print(&self) {\n        use base::{Utf8CStr, debug};\n        debug!(\"skip_initramfs=[{}]\", self.skip_initramfs);\n        debug!(\"force_normal_boot=[{}]\", self.force_normal_boot);\n        debug!(\"rootwait=[{}]\", self.rootwait);\n        unsafe {\n            debug!(\n                \"boot_mode=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.boot_mode.as_ptr())\n            );\n            debug!(\n                \"slot=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.slot.as_ptr())\n            );\n            debug!(\n                \"dt_dir=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.dt_dir.as_ptr())\n            );\n            debug!(\n                \"fstab_suffix=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.fstab_suffix.as_ptr())\n            );\n            debug!(\n                \"hardware=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.hardware.as_ptr())\n            );\n            debug!(\n                \"hardware.platform=[{}]\",\n                Utf8CStr::from_ptr_unchecked(self.hardware_plat.as_ptr())\n            );\n        }\n        debug!(\"emulator=[{}]\", self.emulator);\n        debug!(\"partition_map=[{:?}]\", self.partition_map);\n    }\n}\n\nimpl MagiskInit {\n    pub(crate) fn check_two_stage(&self) -> bool {\n        cstr!(\"/first_stage_ramdisk\").exists() ||\n            cstr!(\"/second_stage_resources\").exists() ||\n            cstr!(\"/system/bin/init\").exists() ||\n            // Use the apex folder to determine whether 2SI (Android 10+)\n            cstr!(\"/apex\").exists() ||\n            // If we still have no indication, parse the original init and see what's up\n            MappedFile::open(Some(cstr!(\"/init.real\")).take_if(|p| p.exists()).unwrap_or(backup_init()))\n                .map(|data| data.contains(b\"selinux_setup\"))\n                .unwrap_or(false)\n    }\n}\n"
  },
  {
    "path": "native/src/init/init.hpp",
    "content": "#pragma once\n\n#define DEFAULT_DT_DIR \"/proc/device-tree/firmware/android\"\n#define REDIR_PATH \"/data/magiskinit\"\n\n#define PRELOAD_LIB    \"/dev/preload.so\"\n#define PRELOAD_POLICY \"/dev/sepolicy\"\n#define PRELOAD_ACK    \"/dev/ack\"\n\n#ifdef __cplusplus\n\n#include <base.hpp>\n#include <sepolicy.hpp>\n\nusing kv_pairs = std::vector<std::pair<std::string, std::string>>;\n\n#include \"init-rs.hpp\"\n\nint magisk_proxy_main(int, char *argv[]);\nUtf8CStr backup_init();\n\n// Expose some constants to Rust\n\nstatic inline Utf8CStr split_plat_cil() {\n    return SPLIT_PLAT_CIL;\n};\n\nstatic inline Utf8CStr preload_lib() {\n    return PRELOAD_LIB;\n}\n\nstatic inline Utf8CStr preload_policy() {\n    return PRELOAD_POLICY;\n}\n\nstatic inline Utf8CStr preload_ack() {\n    return PRELOAD_ACK;\n}\n\n\n#endif\n"
  },
  {
    "path": "native/src/init/init.rs",
    "content": "use crate::ffi::{BootConfig, MagiskInit, backup_init, magisk_proxy_main};\nuse crate::logging::setup_klog;\nuse crate::mount::is_rootfs;\nuse crate::twostage::hexpatch_init_for_second_stage;\nuse base::libc::{basename, getpid, mount, umask};\nuse base::{LibcReturn, LoggedResult, ResultExt, cstr, info, raw_cstr};\nuse std::ffi::{CStr, c_char};\nuse std::ptr::null;\n\nimpl MagiskInit {\n    fn new(argv: *mut *mut c_char) -> Self {\n        Self {\n            preinit_dev: String::new(),\n            mount_list: Vec::new(),\n            overlay_con: Vec::new(),\n            argv,\n            config: BootConfig {\n                skip_initramfs: false,\n                force_normal_boot: false,\n                rootwait: false,\n                emulator: false,\n                slot: [0; 3],\n                dt_dir: [0; 64],\n                fstab_suffix: [0; 32],\n                hardware: [0; 32],\n                hardware_plat: [0; 32],\n                boot_mode: [0; 16],\n                partition_map: Vec::new(),\n            },\n        }\n    }\n\n    fn first_stage(&self) {\n        info!(\"First Stage Init\");\n        self.prepare_data();\n\n        if !cstr!(\"/sdcard\").exists() && !cstr!(\"/first_stage_ramdisk/sdcard\").exists() {\n            self.hijack_init_with_switch_root();\n            self.restore_ramdisk_init();\n        } else {\n            self.restore_ramdisk_init();\n            // Fallback to hexpatch if /sdcard exists\n            hexpatch_init_for_second_stage(true);\n        }\n    }\n\n    fn second_stage(&mut self) {\n        info!(\"Second Stage Init\");\n\n        cstr!(\"/init\").unmount().ok();\n        cstr!(\"/system/bin/init\").unmount().ok(); // just in case\n        cstr!(\"/data/init\").remove().ok();\n\n        unsafe {\n            // Make sure init dmesg logs won't get messed up\n            *self.argv = raw_cstr!(\"/system/bin/init\") as *mut _;\n        }\n\n        // Some weird devices like meizu, uses 2SI but still have legacy rootfs\n        if is_rootfs() {\n            // We are still on rootfs, so make sure we will execute the init of the 2nd stage\n            let init_path = cstr!(\"/init\");\n            init_path.remove().ok();\n            init_path\n                .create_symlink_to(cstr!(\"/system/bin/init\"))\n                .log_ok();\n            self.patch_rw_root();\n        } else {\n            self.patch_ro_root();\n        }\n    }\n\n    fn legacy_system_as_root(&mut self) {\n        info!(\"Legacy SAR Init\");\n        self.prepare_data();\n        let is_two_stage = self.mount_system_root();\n        if is_two_stage {\n            hexpatch_init_for_second_stage(false);\n        } else {\n            self.patch_ro_root();\n        }\n    }\n\n    fn rootfs(&mut self) {\n        info!(\"RootFS Init\");\n        self.prepare_data();\n        self.restore_ramdisk_init();\n        self.patch_rw_root();\n    }\n\n    fn recovery_or_charger(&self) {\n        info!(\"Charger mode or ramdisk is recovery, abort\");\n        self.restore_ramdisk_init();\n        cstr!(\"/.backup\").remove_all().ok();\n    }\n\n    fn restore_ramdisk_init(&self) {\n        cstr!(\"/init\").remove().ok();\n\n        let orig_init = backup_init();\n\n        if orig_init.exists() {\n            orig_init.rename_to(cstr!(\"/init\")).log_ok();\n        } else {\n            // If the backup init is missing, this means that the boot ramdisk\n            // was created from scratch, and the real init is in a separate CPIO,\n            // which is guaranteed to be placed at /system/bin/init.\n            cstr!(\"/init\")\n                .create_symlink_to(cstr!(\"/system/bin/init\"))\n                .log_ok();\n        }\n    }\n\n    fn start(&mut self) -> LoggedResult<()> {\n        if !cstr!(\"/proc/cmdline\").exists() {\n            cstr!(\"/proc\").mkdir(0o755)?;\n            unsafe {\n                mount(\n                    raw_cstr!(\"proc\"),\n                    raw_cstr!(\"/proc\"),\n                    raw_cstr!(\"proc\"),\n                    0,\n                    null(),\n                )\n            }\n            .check_err()?;\n            self.mount_list.push(\"/proc\".to_string());\n        }\n        if !cstr!(\"/sys/block\").exists() {\n            cstr!(\"/sys\").mkdir(0o755)?;\n            unsafe {\n                mount(\n                    raw_cstr!(\"sysfs\"),\n                    raw_cstr!(\"/sys\"),\n                    raw_cstr!(\"sysfs\"),\n                    0,\n                    null(),\n                )\n            }\n            .check_err()?;\n            self.mount_list.push(\"/sys\".to_string());\n        }\n\n        setup_klog();\n\n        self.config.init();\n\n        let argv1 = unsafe { *self.argv.offset(1) };\n        if !argv1.is_null() && unsafe { CStr::from_ptr(argv1) == c\"selinux_setup\" } {\n            self.second_stage();\n        } else if self.config.skip_initramfs {\n            self.legacy_system_as_root();\n        } else if self.config.force_normal_boot {\n            self.first_stage();\n        } else if cstr!(\"/sbin/recovery\").exists()\n            || cstr!(\"/system/bin/recovery\").exists()\n            || unsafe { CStr::from_ptr(self.config.boot_mode.as_ptr()) } == c\"charger\"\n        {\n            self.recovery_or_charger();\n        } else if self.check_two_stage() {\n            self.first_stage();\n        } else {\n            self.rootfs();\n        }\n\n        // Finally execute the original init\n        self.exec_init();\n\n        Ok(())\n    }\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn main(\n    argc: i32,\n    argv: *mut *mut c_char,\n    _envp: *const *const c_char,\n) -> i32 {\n    unsafe {\n        umask(0);\n\n        let name = basename(*argv);\n\n        if CStr::from_ptr(name) == c\"magisk\" {\n            return magisk_proxy_main(argc, argv);\n        }\n\n        if getpid() == 1 {\n            MagiskInit::new(argv).start().log_ok();\n        }\n\n        1\n    }\n}\n"
  },
  {
    "path": "native/src/init/lib.rs",
    "content": "#![allow(clippy::missing_safety_doc)]\n\nuse logging::setup_klog;\n// Has to be pub so all symbols in that crate is included\npub use magiskpolicy;\nuse mount::{is_device_mounted, switch_root};\nuse rootdir::{OverlayAttr, inject_magisk_rc};\n\n#[path = \"../include/consts.rs\"]\nmod consts;\nmod getinfo;\nmod init;\nmod logging;\nmod mount;\nmod rootdir;\nmod selinux;\nmod twostage;\n\n#[cxx::bridge]\npub mod ffi {\n    #[derive(Debug)]\n    struct KeyValue {\n        key: String,\n        value: String,\n    }\n\n    struct BootConfig {\n        skip_initramfs: bool,\n        force_normal_boot: bool,\n        rootwait: bool,\n        emulator: bool,\n        slot: [c_char; 3],\n        dt_dir: [c_char; 64],\n        fstab_suffix: [c_char; 32],\n        hardware: [c_char; 32],\n        hardware_plat: [c_char; 32],\n        boot_mode: [c_char; 16],\n        partition_map: Vec<KeyValue>,\n    }\n\n    struct MagiskInit {\n        preinit_dev: String,\n        mount_list: Vec<String>,\n        argv: *mut *mut c_char,\n        config: BootConfig,\n        overlay_con: Vec<OverlayAttr>,\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"init.hpp\");\n\n        #[cxx_name = \"Utf8CStr\"]\n        type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;\n\n        unsafe fn magisk_proxy_main(argc: i32, argv: *mut *mut c_char) -> i32;\n        fn backup_init() -> Utf8CStrRef<'static>;\n\n        // Constants\n        fn split_plat_cil() -> Utf8CStrRef<'static>;\n        fn preload_lib() -> Utf8CStrRef<'static>;\n        fn preload_policy() -> Utf8CStrRef<'static>;\n        fn preload_ack() -> Utf8CStrRef<'static>;\n    }\n\n    #[namespace = \"rust\"]\n    extern \"Rust\" {\n        fn setup_klog();\n        fn inject_magisk_rc(fd: i32, tmp_dir: Utf8CStrRef);\n        fn switch_root(path: Utf8CStrRef);\n        fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool;\n    }\n\n    // BootConfig\n    extern \"Rust\" {\n        fn print(self: &BootConfig);\n    }\n    unsafe extern \"C++\" {\n        fn init(self: &mut BootConfig);\n        type kv_pairs;\n        fn set(self: &mut BootConfig, config: &kv_pairs);\n    }\n\n    // MagiskInit\n    extern \"Rust\" {\n        type OverlayAttr;\n        fn parse_config_file(self: &mut MagiskInit);\n        fn mount_overlay(self: &mut MagiskInit, dest: Utf8CStrRef);\n        fn handle_sepolicy(self: &mut MagiskInit);\n        fn restore_overlay_contexts(self: &MagiskInit);\n    }\n    unsafe extern \"C++\" {\n        // Used in Rust\n        fn mount_system_root(self: &mut MagiskInit) -> bool;\n        fn patch_rw_root(self: &mut MagiskInit);\n        fn patch_ro_root(self: &mut MagiskInit);\n\n        // Used in C++\n        unsafe fn setup_tmp(self: &mut MagiskInit, path: *const c_char);\n        fn collect_devices(self: &MagiskInit);\n        fn mount_preinit_dir(self: &mut MagiskInit);\n        unsafe fn find_block(self: &MagiskInit, partname: *const c_char) -> u64;\n        unsafe fn patch_fissiond(self: &mut MagiskInit, tmp_path: *const c_char);\n    }\n}\n"
  },
  {
    "path": "native/src/init/logging.rs",
    "content": "use base::nix::fcntl::OFlag;\nuse base::{LogLevel, SilentLogExt, Utf8CStr, cstr, libc, raw_cstr, update_logger};\nuse libc::{\n    O_CLOEXEC, S_IFCHR, STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO, SYS_dup3, makedev, mknod,\n    syscall,\n};\nuse std::fs::File;\nuse std::io::{IoSlice, Write};\nuse std::mem::ManuallyDrop;\nuse std::os::fd::{FromRawFd, IntoRawFd, RawFd};\n\n// SAFETY: magiskinit is single threaded\nstatic mut KMSG: RawFd = -1;\n\npub fn setup_klog() {\n    unsafe {\n        // Shut down first 3 fds\n        let mut fd = cstr!(\"/dev/null\")\n            .open(OFlag::O_RDWR | OFlag::O_CLOEXEC)\n            .silent();\n        if fd.is_err() {\n            mknod(raw_cstr!(\"/null\"), S_IFCHR | 0o666, makedev(1, 3));\n            fd = cstr!(\"/null\")\n                .open(OFlag::O_RDWR | OFlag::O_CLOEXEC)\n                .silent();\n            cstr!(\"/null\").remove().ok();\n        }\n        if let Ok(ref fd) = fd {\n            syscall(SYS_dup3, fd, STDIN_FILENO, O_CLOEXEC);\n            syscall(SYS_dup3, fd, STDOUT_FILENO, O_CLOEXEC);\n            syscall(SYS_dup3, fd, STDERR_FILENO, O_CLOEXEC);\n        }\n\n        // Then open kmsg fd\n        let mut fd = cstr!(\"/dev/kmsg\")\n            .open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)\n            .silent();\n        if fd.is_err() {\n            mknod(raw_cstr!(\"/kmsg\"), S_IFCHR | 0o666, makedev(1, 11));\n            fd = cstr!(\"/kmsg\")\n                .open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)\n                .silent();\n            cstr!(\"/kmsg\").remove().ok();\n        }\n        KMSG = fd.map(|fd| fd.into_raw_fd()).unwrap_or(-1);\n    }\n\n    // Disable kmsg rate limiting\n    if let Ok(mut rate) =\n        cstr!(\"/proc/sys/kernel/printk_devkmsg\").open(OFlag::O_WRONLY | OFlag::O_CLOEXEC)\n    {\n        writeln!(rate, \"on\").ok();\n    }\n\n    fn kmsg_log_write(_: LogLevel, msg: &Utf8CStr) {\n        let fd = unsafe { KMSG };\n        if fd >= 0 {\n            let io1 = IoSlice::new(\"magiskinit: \".as_bytes());\n            let io2 = IoSlice::new(msg.as_bytes());\n            let mut kmsg = ManuallyDrop::new(unsafe { File::from_raw_fd(fd) });\n            let _ = kmsg.write_vectored(&[io1, io2]).ok();\n        }\n    }\n\n    update_logger(|logger| logger.write = kmsg_log_write);\n}\n"
  },
  {
    "path": "native/src/init/mount.cpp",
    "content": "#include <set>\n#include <sys/mount.h>\n#include <sys/sysmacros.h>\n#include <libgen.h>\n\n#include <base.hpp>\n#include <consts.hpp>\n\n#include \"init.hpp\"\n\nusing namespace std;\n\nstruct devinfo {\n    int major;\n    int minor;\n    char devname[32];\n    char partname[32];\n    char dmname[32];\n    char devpath[PATH_MAX];\n};\n\nstatic vector<devinfo> dev_list;\n\n// When this boolean is set, this means we are currently\n// running magiskinit on legacy SAR AVD emulator\nbool avd_hack = false;\n\nstatic void parse_device(devinfo *dev, const char *uevent) {\n    dev->partname[0] = '\\0';\n    dev->devpath[0] = '\\0';\n    dev->dmname[0] = '\\0';\n    dev->devname[0] = '\\0';\n    parse_prop_file(uevent, [=](Utf8CStr key, Utf8CStr value) -> bool {\n        if (key == \"MAJOR\")\n            dev->major = parse_int(value);\n        else if (key == \"MINOR\")\n            dev->minor = parse_int(value);\n        else if (key == \"DEVNAME\")\n            strscpy(dev->devname, value.c_str(), sizeof(dev->devname));\n        else if (key == \"PARTNAME\")\n            strscpy(dev->partname, value.c_str(), sizeof(dev->devname));\n\n        return true;\n    });\n}\n\nvoid MagiskInit::collect_devices() const noexcept {\n    char path[PATH_MAX];\n    devinfo dev{};\n    if (auto dir = xopen_dir(\"/sys/dev/block\"); dir) {\n        for (dirent *entry; (entry = readdir(dir.get()));) {\n            if (entry->d_name == \".\"sv || entry->d_name == \"..\"sv)\n                continue;\n            sprintf(path, \"/sys/dev/block/%s/uevent\", entry->d_name);\n            parse_device(&dev, path);\n            sprintf(path, \"/sys/dev/block/%s/dm/name\", entry->d_name);\n            if (access(path, F_OK) == 0) {\n                auto name = rtrim(full_read(path));\n                strscpy(dev.dmname, name.data(), sizeof(dev.dmname));\n            }\n            if (auto it = std::ranges::find_if(config.partition_map, [&](const auto &i) {\n                return i.key == dev.devname;\n            }); dev.partname[0] == '\\0' && it != config.partition_map.end()) {\n                // use androidboot.partition_map as partname fallback.\n                strscpy(dev.partname, it->value.data(), sizeof(dev.partname));\n            }\n            sprintf(path, \"/sys/dev/block/%s\", entry->d_name);\n            xrealpath(path, dev.devpath, sizeof(dev.devpath));\n            dev_list.push_back(dev);\n        }\n    }\n}\n\nuint64_t MagiskInit::find_block(const char *partname) const noexcept {\n    if (dev_list.empty())\n        collect_devices();\n\n    for (int tries = 0; tries < 3; ++tries) {\n        for (auto &dev : dev_list) {\n            const char *name;\n            if (strcasecmp(dev.partname, partname) == 0)\n                name = dev.partname;\n            else if (strcasecmp(dev.dmname, partname) == 0)\n                name = dev.dmname;\n            else if (strcasecmp(dev.devname, partname) == 0)\n                name = dev.devname;\n            else if (std::string_view(dev.devpath).ends_with(\"/\"s + partname))\n                name = dev.devpath;\n            else\n                continue;\n\n            LOGD(\"Found %s: [%s] (%d, %d)\\n\", name, dev.devname, dev.major, dev.minor);\n            return makedev(dev.major, dev.minor);\n        }\n        // Wait 10ms and try again\n        usleep(10000);\n        dev_list.clear();\n        collect_devices();\n    }\n\n    // The requested partname does not exist\n    return 0;\n}\n\nvoid MagiskInit::mount_preinit_dir() noexcept {\n    if (preinit_dev.empty()) return;\n    auto dev = find_block(preinit_dev.c_str());\n    if (dev == 0) {\n        LOGE(\"Cannot find preinit %s, abort!\\n\", preinit_dev.c_str());\n        return;\n    }\n    xmknod(PREINITDEV, S_IFBLK | 0600, dev);\n    xmkdir(MIRRDIR, 0);\n    bool mounted = false;\n    // First, find if it is already mounted\n    std::string mnt_point;\n    if (rust::is_device_mounted(dev, mnt_point)) {\n        // Already mounted, just bind mount\n        xmount(mnt_point.data(), MIRRDIR, nullptr, MS_BIND, nullptr);\n        mounted = true;\n    }\n\n    // Since we are mounting the block device directly, make sure to ONLY mount the partitions\n    // as read-only, or else the kernel might crash due to crappy drivers.\n    // After the device boots up, magiskd will properly symlink the correct path at PREINITMIRR as writable.\n    if (mounted || mount(PREINITDEV, MIRRDIR, \"ext4\", MS_RDONLY, nullptr) == 0 ||\n        mount(PREINITDEV, MIRRDIR, \"f2fs\", MS_RDONLY, nullptr) == 0) {\n        string preinit_dir = resolve_preinit_dir(MIRRDIR);\n        // Create bind mount\n        xmkdirs(PREINITMIRR, 0);\n        if (access(preinit_dir.data(), F_OK)) {\n            LOGW(\"empty preinit: %s\\n\", preinit_dir.data());\n        } else {\n            LOGD(\"preinit: %s\\n\", preinit_dir.data());\n            xmount(preinit_dir.data(), PREINITMIRR, nullptr, MS_BIND, nullptr);\n        }\n        xumount2(MIRRDIR, MNT_DETACH);\n    } else {\n        PLOGE(\"Mount preinit %s\", preinit_dev.c_str());\n        // Do NOT delete the block device. Even though we cannot mount it here,\n        // it might get formatted later in the boot process.\n    }\n}\n\nbool MagiskInit::mount_system_root() noexcept {\n    LOGD(\"Mounting system_root\\n\");\n\n    // there's no /dev in stub cpio\n    xmkdir(\"/dev\", 0777);\n\n    dev_t dev;\n    do {\n        // Try legacy SAR dm-verity\n        dev = find_block(\"vroot\");\n        if (dev > 0)\n            goto mount_root;\n\n        // Try NVIDIA naming scheme\n        dev = find_block(\"APP\");\n        if (dev > 0)\n            goto mount_root;\n\n        // Try normal partname\n        char sys_part[32];\n        sprintf(sys_part, \"system%s\", config.slot.data());\n        dev = find_block(sys_part);\n        if (dev > 0)\n            goto mount_root;\n\n        // Poll forever if rootwait was given in cmdline\n    } while (config.rootwait);\n\n    // We don't really know what to do at this point...\n    LOGE(\"Cannot find root partition, abort\\n\");\n    exit(1);\n\nmount_root:\n    xmknod(\"/dev/root\", S_IFBLK | 0600, dev);\n    xmkdir(\"/system_root\", 0755);\n\n    if (xmount(\"/dev/root\", \"/system_root\", \"ext4\", MS_RDONLY, nullptr)) {\n        if (xmount(\"/dev/root\", \"/system_root\", \"erofs\", MS_RDONLY, nullptr)) {\n            // We don't really know what to do at this point...\n            LOGE(\"Cannot mount root partition, abort\\n\");\n            exit(1);\n        }\n    }\n\n    rust::switch_root(\"/system_root\");\n\n    // Make dev writable\n    xmount(\"tmpfs\", \"/dev\", \"tmpfs\", 0, \"mode=755\");\n    mount_list.emplace_back(\"/dev\");\n\n    bool is_two_stage = access(\"/system/bin/init\", F_OK) == 0;\n    LOGD(\"is_two_stage: [%d]\\n\", is_two_stage);\n\n    // For API 28 AVD, it uses legacy SAR setup that requires\n    // special hacks in magiskinit to work properly.\n    if (!is_two_stage && config.emulator) {\n        avd_hack = true;\n        // These values are hardcoded for API 28 AVD\n        auto vendor_dev = find_block(\"vendor\");\n        xmkdir(\"/dev/block\", 0755);\n        xmknod(\"/dev/block/vde1\", S_IFBLK | 0600, vendor_dev);\n        xmount(\"/dev/block/vde1\", \"/vendor\", \"ext4\", MS_RDONLY, nullptr);\n    }\n\n    return is_two_stage;\n}\n\nvoid MagiskInit::setup_tmp(const char *path) noexcept {\n    LOGD(\"Setup Magisk tmp at %s\\n\", path);\n    chdir(\"/data\");\n\n    xmkdir(INTLROOT, 0711);\n    xmkdir(DEVICEDIR, 0711);\n    xmkdir(WORKERDIR, 0);\n\n    mount_preinit_dir();\n\n    cp_afc(\".backup/.magisk\", MAIN_CONFIG);\n    rm_rf(\".backup\");\n\n    // Create applet symlinks\n    for (int i = 0; applet_names[i]; ++i)\n        xsymlink(\"./magisk\", applet_names[i]);\n    xsymlink(\"./magiskpolicy\", \"supolicy\");\n\n    xmount(\".\", path, nullptr, MS_BIND, nullptr);\n\n    chdir(path);\n\n    // Prepare worker\n    xmount(\"magisk\", WORKERDIR, \"tmpfs\", 0, \"mode=755\");\n\n    // Use isolated devpts if kernel support\n    if (access(\"/dev/pts/ptmx\", F_OK) == 0) {\n        xmkdirs(SHELLPTS, 0755);\n        xmount(\"devpts\", SHELLPTS, \"devpts\", MS_NOSUID | MS_NOEXEC, \"newinstance\");\n        xmount(nullptr, SHELLPTS, nullptr, MS_PRIVATE, nullptr);\n        if (access(SHELLPTS \"/ptmx\", F_OK)) {\n            umount2(SHELLPTS, MNT_DETACH);\n            rmdir(SHELLPTS);\n        }\n    }\n\n    chdir(\"/\");\n}\n"
  },
  {
    "path": "native/src/init/mount.rs",
    "content": "use crate::ffi::MagiskInit;\nuse base::{\n    Directory, FsPathBuilder, LibcReturn, LoggedResult, ResultExt, Utf8CStr, cstr, debug, libc,\n    nix, parse_mount_info, raw_cstr,\n};\nuse cxx::CxxString;\nuse nix::mount::MsFlags;\nuse nix::sys::statfs::{FsType, TMPFS_MAGIC, statfs};\nuse nix::unistd::{chdir, chroot};\nuse num_traits::AsPrimitive;\nuse std::collections::BTreeSet;\nuse std::ops::Bound::{Excluded, Unbounded};\nuse std::pin::Pin;\n\nunsafe extern \"C\" {\n    static environ: *const *mut libc::c_char;\n}\n\npub(crate) fn switch_root(path: &Utf8CStr) {\n    || -> LoggedResult<()> {\n        debug!(\"Switch root to {}\", path);\n        let mut mounts = BTreeSet::new();\n        let rootfs = Directory::open(cstr!(\"/\"))?;\n        for info in parse_mount_info(\"self\") {\n            if info.target == \"/\" || info.target.as_str() == path.as_str() {\n                continue;\n            }\n            if let Some(last_mount) = mounts\n                .range::<String, _>((Unbounded, Excluded(&info.target)))\n                .last()\n                && info.target.starts_with(&format!(\"{}/\", *last_mount))\n            {\n                continue;\n            }\n\n            let mut target = info.target.clone();\n            let target = Utf8CStr::from_string(&mut target);\n            let new_path = cstr::buf::default()\n                .join_path(path)\n                .join_path(info.target.trim_start_matches('/'));\n            new_path.mkdirs(0o755).ok();\n            target.move_mount_to(&new_path)?;\n            mounts.insert(info.target);\n        }\n        chdir(path)?;\n        path.move_mount_to(cstr!(\"/\"))?;\n        chroot(cstr!(\".\"))?;\n\n        debug!(\"Cleaning rootfs\");\n        rootfs.remove_all()?;\n        Ok(())\n    }()\n    .ok();\n}\n\npub(crate) fn is_device_mounted(dev: u64, target: Pin<&mut CxxString>) -> bool {\n    for mount in parse_mount_info(\"self\") {\n        if mount.root == \"/\" && mount.device == dev {\n            target.push_str(&mount.target);\n            return true;\n        }\n    }\n    false\n}\n\nconst RAMFS_MAGIC: u32 = 0x858458f6;\n\npub(crate) fn is_rootfs() -> bool {\n    if let Ok(s) = statfs(cstr!(\"/\")) {\n        s.filesystem_type() == FsType(RAMFS_MAGIC.as_()) || s.filesystem_type() == TMPFS_MAGIC\n    } else {\n        false\n    }\n}\n\nimpl MagiskInit {\n    pub(crate) fn prepare_data(&self) {\n        debug!(\"Setup data tmp\");\n        cstr!(\"/data\").mkdir(0o755).log_ok();\n        nix::mount::mount(\n            Some(cstr!(\"magisk\")),\n            cstr!(\"/data\"),\n            Some(cstr!(\"tmpfs\")),\n            MsFlags::empty(),\n            Some(cstr!(\"mode=755\")),\n        )\n        .check_os_err(\"mount\", Some(\"/data\"), Some(\"tmpfs\"))\n        .log_ok();\n\n        cstr!(\"/init\").copy_to(cstr!(\"/data/magiskinit\")).ok();\n        cstr!(\"/.backup\").copy_to(cstr!(\"/data/.backup\")).ok();\n        cstr!(\"/overlay.d\").copy_to(cstr!(\"/data/overlay.d\")).ok();\n    }\n\n    pub(crate) fn exec_init(&mut self) {\n        for path in self.mount_list.iter_mut().rev() {\n            let path = Utf8CStr::from_string(path);\n            if path.unmount().log().is_ok() {\n                debug!(\"Unmount [{}]\", path);\n            }\n        }\n        unsafe {\n            libc::execve(raw_cstr!(\"/init\"), self.argv.cast(), environ.cast())\n                .check_err()\n                .log_ok();\n        }\n        std::process::exit(1);\n    }\n}\n"
  },
  {
    "path": "native/src/init/preload.c",
    "content": "#include <stdlib.h>\n#include <fcntl.h>\n#include <unistd.h>\n\n#include \"init.hpp\"\n\n__attribute__((constructor))\nstatic void preload_init() {\n    // Make sure our next exec won't get bugged\n    unsetenv(\"LD_PRELOAD\");\n    unlink(PRELOAD_LIB);\n}\n\nint security_load_policy(void *data, size_t len) {\n    int policy = open(PRELOAD_POLICY, O_WRONLY | O_CREAT, 0644);\n    if (policy < 0) return -1;\n\n    // Write the policy\n    write(policy, data, len);\n    close(policy);\n\n    // Wait for ack\n    int ack = open(PRELOAD_ACK, O_RDONLY);\n    char c;\n    read(ack, &c, 1);\n    close(ack);\n\n    return 0;\n}\n"
  },
  {
    "path": "native/src/init/rootdir.cpp",
    "content": "#include <sys/mount.h>\n#include <libgen.h>\n\n#include <sepolicy.hpp>\n#include <consts.hpp>\n#include <base.hpp>\n#include <xz.h>\n\n#include \"init.hpp\"\n\nusing namespace std;\n\nstatic vector<string> rc_list;\n\n#define NEW_INITRC_DIR  \"/system/etc/init/hw\"\n#define INIT_RC         \"init.rc\"\n\nstatic bool unxz(int fd, rust::Slice<const uint8_t> bytes) {\n    uint8_t out[8192];\n    xz_crc32_init();\n    size_t size = bytes.size();\n    struct xz_dec *dec = xz_dec_init(XZ_DYNALLOC, 1 << 26);\n    run_finally finally([&] { xz_dec_end(dec); });\n    struct xz_buf b = {\n        .in = bytes.data(),\n        .in_pos = 0,\n        .in_size = size,\n        .out = out,\n        .out_pos = 0,\n        .out_size = sizeof(out)\n    };\n    enum xz_ret ret;\n    do {\n        ret = xz_dec_run(dec, &b);\n        if (ret != XZ_OK && ret != XZ_STREAM_END)\n            return false;\n        write(fd, out, b.out_pos);\n        b.out_pos = 0;\n    } while (b.in_pos != size);\n    return true;\n}\n\n// When return true, run patch_fissiond\nstatic bool patch_rc_scripts(const char *src_path, const char *tmp_path, bool writable) {\n    auto src_dir = xopen_dir(src_path);\n    if (!src_dir) return false;\n    int src_fd = dirfd(src_dir.get());\n\n    // If writable, directly modify the file in src_path, or else add to rootfs overlay\n    auto dest_dir = writable ? [&] {\n        return xopen_dir(src_path);\n    }() : [&] {\n        char buf[PATH_MAX] = {};\n        ssprintf(buf, sizeof(buf), ROOTOVL \"%s\", src_path);\n        xmkdirs(buf, 0755);\n        return xopen_dir(buf);\n    }();\n    if (!dest_dir) return false;\n    int dest_fd = dirfd(dest_dir.get());\n\n    // First patch init.rc\n    {\n        owned_fd src_rc = xopenat(src_fd, INIT_RC, O_RDONLY | O_CLOEXEC, 0);\n        if (src_rc < 0) return false;\n        if (writable) unlinkat(src_fd, INIT_RC, 0);\n        auto dest_rc = xopen_file(\n                xopenat(dest_fd, INIT_RC, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), \"we\");\n        if (!dest_rc) return false;\n        LOGD(\"Patching \" INIT_RC \" in %s\\n\", src_path);\n        file_readline(src_rc, [&dest_rc](Utf8CStr line) -> bool {\n            // Do not start vaultkeeper\n            if (line.sv().contains(\"start vaultkeeper\")) {\n                LOGD(\"Remove vaultkeeper\\n\");\n                return true;\n            }\n            // Do not run flash_recovery\n            if (line.sv().starts_with(\"service flash_recovery\")) {\n                LOGD(\"Remove flash_recovery\\n\");\n                fprintf(dest_rc.get(), \"service flash_recovery /system/bin/true\\n\");\n                return true;\n            }\n            // Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data\n            if (line.sv().starts_with(\"on property:persist.sys.zygote.early=\")) {\n                LOGD(\"Invalidate persist.sys.zygote.early\\n\");\n                fprintf(dest_rc.get(), \"on property:persist.sys.zygote.early.xxxxx=true\\n\");\n                return true;\n            }\n            // Else just write the line\n            fprintf(dest_rc.get(), \"%s\", line.c_str());\n            return true;\n        });\n\n        fprintf(dest_rc.get(), \"\\n\");\n\n        // Inject custom rc scripts\n        for (auto &script : rc_list) {\n            // Replace template arguments of rc scripts with dynamic paths\n            replace_all(script, \"${MAGISKTMP}\", tmp_path);\n            fprintf(dest_rc.get(), \"\\n%s\\n\", script.data());\n        }\n        rc_list.clear();\n\n        // Inject Magisk rc scripts\n        rust::inject_magisk_rc(fileno(dest_rc.get()), tmp_path);\n\n        fclone_attr(src_rc, fileno(dest_rc.get()));\n    }\n\n    // Then patch init.zygote*.rc\n    for (dirent *entry; (entry = readdir(src_dir.get()));) {\n        {\n            auto name = std::string_view(entry->d_name);\n            if (!name.starts_with(\"init.zygote\") || !name.ends_with(\".rc\")) continue;\n        }\n        owned_fd src_rc = xopenat(src_fd, entry->d_name, O_RDONLY | O_CLOEXEC, 0);\n        if (src_rc < 0) continue;\n        if (writable) unlinkat(src_fd, entry->d_name, 0);\n        auto dest_rc = xopen_file(\n                xopenat(dest_fd, entry->d_name, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), \"we\");\n        if (!dest_rc) continue;\n        LOGD(\"Patching %s in %s\\n\", entry->d_name, src_path);\n        file_readline(src_rc, [&dest_rc, &tmp_path](Utf8CStr line) -> bool {\n            if (line.sv().starts_with(\"service zygote \")) {\n                LOGD(\"Inject zygote restart\\n\");\n                fprintf(dest_rc.get(), \"%s\", line.c_str());\n                fprintf(dest_rc.get(),\n                        \"    onrestart exec \" MAGISK_PROC_CON \" 0 0 -- %s/magisk --zygote-restart\\n\", tmp_path);\n                return true;\n            }\n            fprintf(dest_rc.get(), \"%s\", line.c_str());\n            return true;\n        });\n        fclone_attr(src_rc, fileno(dest_rc.get()));\n    }\n\n    return faccessat(src_fd, \"init.fission_host.rc\", F_OK, 0) == 0;\n}\n\nvoid MagiskInit::patch_fissiond(const char *tmp_path) noexcept {\n    {\n        LOGD(\"Patching fissiond\\n\");\n        mmap_data fissiond(\"/system/bin/fissiond\", false);\n        for (size_t off : fissiond.patch(\n                \"ro.build.system.fission_single_os\",\n                \"ro.build.system.xxxxxxxxxxxxxxxxx\"))\n        {\n            LOGD(\"Patch @ %08zX [ro.build.system.fission_single_os] -> \"\n                 \"[ro.build.system.xxxxxxxxxxxxxxxxx]\\n\", off);\n        }\n        mkdirs(ROOTOVL \"/system/bin\", 0755);\n        if (auto target_fissiond = xopen_file(ROOTOVL \"/system/bin/fissiond\", \"we\")) {\n            fwrite(fissiond.data(), 1, fissiond.size(), target_fissiond.get());\n            clone_attr(\"/system/bin/fissiond\", ROOTOVL \"/system/bin/fissiond\");\n        }\n    }\n    LOGD(\"hijack isolated\\n\");\n    auto hijack = xopen_file(\"/sys/devices/system/cpu/isolated\", \"re\");\n    mkfifo(INTLROOT \"/isolated\", 0777);\n    xmount(INTLROOT \"/isolated\", \"/sys/devices/system/cpu/isolated\", nullptr, MS_BIND, nullptr);\n    if (!xfork()) {\n        auto dest = xopen_file(INTLROOT \"/isolated\", \"we\");\n        LOGD(\"hijacked isolated\\n\");\n        xumount2(\"/sys/devices/system/cpu/isolated\", MNT_DETACH);\n        unlink(INTLROOT \"/isolated\");\n        string content = full_read(fileno(hijack.get()));\n        {\n            string target = \"/dev/cells/cell2\"s + tmp_path;\n            xmkdirs(target.data(), 0);\n            xmount(tmp_path, target.data(), nullptr, MS_BIND | MS_REC, nullptr);\n            mount_overlay(\"/dev/cells/cell2\");\n        }\n        fprintf(dest.get(), \"%s\", content.data());\n        exit(0);\n    }\n}\n\nstatic void load_overlay_rc(const char *overlay) {\n    auto dir = open_dir(overlay);\n    if (!dir) return;\n\n    int dfd = dirfd(dir.get());\n    // Do not allow overwrite init.rc\n    unlinkat(dfd, INIT_RC, 0);\n\n    // '/' + name + '\\0'\n    char buf[NAME_MAX + 2];\n    buf[0] = '/';\n    for (dirent *entry; (entry = xreaddir(dir.get()));) {\n        if (!string_view(entry->d_name).ends_with(\".rc\")) {\n            continue;\n        }\n        strscpy(buf + 1, entry->d_name, sizeof(buf) - 1);\n        if (access(buf, F_OK) == 0) {\n            LOGD(\"Replace rc script [%s]\\n\", entry->d_name);\n        } else {\n            LOGD(\"Found rc script [%s]\\n\", entry->d_name);\n            int rc = xopenat(dfd, entry->d_name, O_RDONLY | O_CLOEXEC);\n            rc_list.push_back(full_read(rc));\n            close(rc);\n            unlinkat(dfd, entry->d_name, 0);\n        }\n    }\n}\n\nstatic void recreate_sbin(const char *mirror, bool use_bind_mount) {\n    auto dp = xopen_dir(mirror);\n    int src = dirfd(dp.get());\n    char buf[4096];\n    for (dirent *entry; (entry = xreaddir(dp.get()));) {\n        string sbin_path = \"/sbin/\"s + entry->d_name;\n        struct stat st;\n        fstatat(src, entry->d_name, &st, AT_SYMLINK_NOFOLLOW);\n        if (S_ISLNK(st.st_mode)) {\n            xreadlinkat(src, entry->d_name, buf, sizeof(buf));\n            xsymlink(buf, sbin_path.data());\n        } else {\n            sprintf(buf, \"%s/%s\", mirror, entry->d_name);\n            if (use_bind_mount) {\n                auto mode = st.st_mode & 0777;\n                // Create dummy\n                if (S_ISDIR(st.st_mode))\n                    xmkdir(sbin_path.data(), mode);\n                else\n                    close(xopen(sbin_path.data(), O_CREAT | O_WRONLY | O_CLOEXEC, mode));\n\n                xmount(buf, sbin_path.data(), nullptr, MS_BIND, nullptr);\n            } else {\n                xsymlink(buf, sbin_path.data());\n            }\n        }\n    }\n}\n\nstatic void extract_files(bool sbin) {\n    const char *magisk_xz = sbin ? \"/sbin/magisk.xz\" : \"magisk.xz\";\n    const char *stub_xz = sbin ? \"/sbin/stub.xz\" : \"stub.xz\";\n    const char *init_ld_xz = sbin ? \"/sbin/init-ld.xz\" : \"init-ld.xz\";\n\n    if (access(magisk_xz, F_OK) == 0) {\n        mmap_data magisk(magisk_xz);\n        unlink(magisk_xz);\n        int fd = xopen(\"magisk\", O_WRONLY | O_CREAT, 0755);\n        unxz(fd, magisk);\n        close(fd);\n    }\n    if (access(stub_xz, F_OK) == 0) {\n        mmap_data stub(stub_xz);\n        unlink(stub_xz);\n        int fd = xopen(\"stub.apk\", O_WRONLY | O_CREAT, 0);\n        unxz(fd, stub);\n        close(fd);\n    }\n    if (access(init_ld_xz, F_OK) == 0) {\n        mmap_data init_ld(init_ld_xz);\n        unlink(init_ld_xz);\n        int fd = xopen(\"init-ld\", O_WRONLY | O_CREAT, 0);\n        unxz(fd, init_ld);\n        close(fd);\n    }\n}\n\nvoid MagiskInit::patch_ro_root() noexcept {\n    mount_list.emplace_back(\"/data\");\n    parse_config_file();\n\n    string tmp_dir;\n\n    if (access(\"/sbin\", F_OK) == 0) {\n        tmp_dir = \"/sbin\";\n    } else {\n        tmp_dir = \"/debug_ramdisk\";\n        xmkdir(\"/data/debug_ramdisk\", 0);\n        xmount(\"/debug_ramdisk\", \"/data/debug_ramdisk\", nullptr, MS_MOVE, nullptr);\n    }\n\n    setup_tmp(tmp_dir.data());\n    chdir(tmp_dir.data());\n\n    if (tmp_dir == \"/sbin\") {\n        // Recreate original sbin structure\n        xmkdir(MIRRDIR, 0755);\n        xmount(\"/\", MIRRDIR, nullptr, MS_BIND, nullptr);\n        recreate_sbin(MIRRDIR \"/sbin\", true);\n        xumount2(MIRRDIR, MNT_DETACH);\n    } else {\n        // Restore debug_ramdisk\n        xmount(\"/data/debug_ramdisk\", \"/debug_ramdisk\", nullptr, MS_MOVE, nullptr);\n        rmdir(\"/data/debug_ramdisk\");\n    }\n\n    xrename(\"overlay.d\", ROOTOVL);\n\n    extern bool avd_hack;\n    // Handle avd hack\n    if (avd_hack) {\n        int src = xopen(\"/init\", O_RDONLY | O_CLOEXEC);\n        mmap_data init(\"/init\");\n        // Force disable early mount on original init\n        for (size_t off : init.patch(\"android,fstab\", \"xxx\")) {\n            LOGD(\"Patch @ %08zX [android,fstab] -> [xxx]\\n\", off);\n        }\n        int dest = xopen(ROOTOVL \"/init\", O_CREAT | O_WRONLY | O_CLOEXEC, 0);\n        xwrite(dest, init.data(), init.size());\n        fclone_attr(src, dest);\n        close(src);\n        close(dest);\n    }\n\n    load_overlay_rc(ROOTOVL);\n    if (access(ROOTOVL \"/sbin\", F_OK) == 0) {\n        // Move files in overlay.d/sbin into tmp_dir\n        mv_path(ROOTOVL \"/sbin\", \".\");\n    }\n\n    // Patch init.rc\n    bool p;\n    if (access(NEW_INITRC_DIR \"/\" INIT_RC, F_OK) == 0) {\n        // Android 11's new init.rc\n        p = patch_rc_scripts(NEW_INITRC_DIR, tmp_dir.data(), false);\n    } else {\n        p = patch_rc_scripts(\"/\", tmp_dir.data(), false);\n    }\n    if (p) patch_fissiond(tmp_dir.data());\n\n    // Extract overlay archives\n    extract_files(false);\n\n    handle_sepolicy();\n    unlink(\"init-ld\");\n\n    // Mount rootdir\n    mount_overlay(\"/\");\n\n    chdir(\"/\");\n}\n\n#define PRE_TMPSRC \"/magisk\"\n#define PRE_TMPDIR PRE_TMPSRC \"/tmp\"\n\nvoid MagiskInit::patch_rw_root() noexcept {\n    mount_list.emplace_back(\"/data\");\n    parse_config_file();\n\n    // Create hardlink mirror of /sbin to /root\n    mkdir(\"/root\", 0777);\n    clone_attr(\"/sbin\", \"/root\");\n    link_path(\"/sbin\", \"/root\");\n\n    // Handle overlays\n    load_overlay_rc(\"/overlay.d\");\n    mv_path(\"/overlay.d\", \"/\");\n    rm_rf(\"/data/overlay.d\");\n    rm_rf(\"/.backup\");\n\n    // Patch init.rc\n    if (patch_rc_scripts(\"/\", \"/sbin\", true))\n        patch_fissiond(\"/sbin\");\n\n    xmkdir(PRE_TMPSRC, 0);\n    xmount(\"tmpfs\", PRE_TMPSRC, \"tmpfs\", 0, \"mode=755\");\n    xmkdir(PRE_TMPDIR, 0);\n    setup_tmp(PRE_TMPDIR);\n    chdir(PRE_TMPDIR);\n\n    // Extract overlay archives\n    extract_files(true);\n\n    handle_sepolicy();\n    unlink(\"init-ld\");\n\n    chdir(\"/\");\n\n    // Dump magiskinit as magisk\n    cp_afc(REDIR_PATH, \"/sbin/magisk\");\n}\n\nint magisk_proxy_main(int, char *argv[]) {\n    rust::setup_klog();\n    LOGD(\"%s\\n\", __FUNCTION__);\n\n    // Mount rootfs as rw to do post-init rootfs patches\n    xmount(nullptr, \"/\", nullptr, MS_REMOUNT, nullptr);\n\n    unlink(\"/sbin/magisk\");\n\n    // Move tmpfs to /sbin\n    // make parent private before MS_MOVE\n    xmount(nullptr, PRE_TMPSRC, nullptr, MS_PRIVATE, nullptr);\n    xmount(PRE_TMPDIR, \"/sbin\", nullptr, MS_MOVE, nullptr);\n    xumount2(PRE_TMPSRC, MNT_DETACH);\n    rmdir(PRE_TMPDIR);\n    rmdir(PRE_TMPSRC);\n\n    // Create symlinks pointing back to /root\n    recreate_sbin(\"/root\", false);\n\n    // Tell magiskd to remount rootfs\n    setenv(\"REMOUNT_ROOT\", \"1\", 1);\n    execve(\"/sbin/magisk\", argv, environ);\n    return 1;\n}\n\nstatic void unxz_init(const char *init_xz, const char *init) {\n    LOGD(\"unxz %s -> %s\\n\", init_xz, init);\n    int fd = xopen(init, O_WRONLY | O_CREAT, 0777);\n    unxz(fd, mmap_data{init_xz});\n    close(fd);\n    clone_attr(init_xz, init);\n    unlink(init_xz);\n}\n\nUtf8CStr backup_init() {\n    if (access(\"/.backup/init.xz\", F_OK) == 0)\n        unxz_init(\"/.backup/init.xz\", \"/.backup/init\");\n    return \"/.backup/init\";\n}\n"
  },
  {
    "path": "native/src/init/rootdir.rs",
    "content": "use crate::consts::{ROOTMNT, ROOTOVL};\nuse crate::ffi::MagiskInit;\nuse base::nix::fcntl::OFlag;\nuse base::{\n    BufReadExt, Directory, FsPathBuilder, LoggedResult, ResultExt, Utf8CStr, Utf8CString,\n    clone_attr, cstr, debug,\n};\nuse std::fs::File;\nuse std::io::{BufReader, Write};\nuse std::mem;\nuse std::os::fd::{FromRawFd, RawFd};\n\npub fn inject_magisk_rc(fd: RawFd, tmp_dir: &Utf8CStr) {\n    debug!(\"Injecting magisk rc\");\n\n    let mut file = unsafe { File::from_raw_fd(fd) };\n\n    write!(\n        file,\n        r#\"\non post-fs-data\n    exec {0} 0 0 -- {1}/magisk --post-fs-data\n\non property:vold.decrypt=trigger_restart_framework\n    exec {0} 0 0 -- {1}/magisk --service\n\non nonencrypted\n    exec {0} 0 0 -- {1}/magisk --service\n\non property:sys.boot_completed=1\n    exec {0} 0 0 -- {1}/magisk --boot-complete\n\"#,\n        \"u:r:magisk:s0\", tmp_dir\n    )\n    .ok();\n\n    mem::forget(file)\n}\n\npub struct OverlayAttr(Utf8CString, Utf8CString);\n\nimpl MagiskInit {\n    pub(crate) fn parse_config_file(&mut self) {\n        if let Ok(fd) = cstr!(\"/data/.backup/.magisk\").open(OFlag::O_RDONLY) {\n            let mut reader = BufReader::new(fd);\n            reader.for_each_prop(|key, val| {\n                if key == \"PREINITDEVICE\" {\n                    self.preinit_dev = val.to_string();\n                    return false;\n                }\n                true\n            })\n        }\n    }\n\n    fn mount_impl(\n        &mut self,\n        src_dir: &Utf8CStr,\n        dest_dir: &Utf8CStr,\n        mount_list: &mut String,\n    ) -> LoggedResult<()> {\n        let mut dir = Directory::open(src_dir)?;\n        let mut con = cstr::buf::default();\n        loop {\n            match &dir.read()? {\n                None => return Ok(()),\n                Some(e) => {\n                    let name = e.name();\n                    let src = cstr::buf::dynamic(256).join_path(src_dir).join_path(name);\n                    let dest = cstr::buf::dynamic(256).join_path(dest_dir).join_path(name);\n                    if dest.exists() {\n                        if e.is_dir() {\n                            // Recursive\n                            self.mount_impl(&src, &dest, mount_list)?;\n                        } else {\n                            debug!(\"Mount [{}] -> [{}]\", src, dest);\n                            clone_attr(&dest, &src)?;\n                            dest.get_secontext(&mut con)?;\n                            src.bind_mount_to(&dest, false)?;\n                            self.overlay_con\n                                .push(OverlayAttr(dest.to_owned(), con.to_owned()));\n                            mount_list.push_str(dest.as_str());\n                            mount_list.push('\\n');\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    pub(crate) fn mount_overlay(&mut self, dest: &Utf8CStr) {\n        let mut mount_list = String::new();\n        self.mount_impl(cstr!(ROOTOVL), dest, &mut mount_list)\n            .log_ok();\n        if let Ok(mut fd) = cstr!(ROOTMNT).create(OFlag::O_CREAT | OFlag::O_WRONLY, 0) {\n            fd.write(mount_list.as_bytes()).log_ok();\n        }\n    }\n\n    pub(crate) fn restore_overlay_contexts(&self) {\n        self.overlay_con.iter().for_each(|attr| {\n            let OverlayAttr(path, con) = attr;\n            path.set_secontext(con).log_ok();\n        })\n    }\n}\n"
  },
  {
    "path": "native/src/init/selinux.rs",
    "content": "use crate::consts::{PREINITMIRR, SELINUXMOCK};\nuse crate::ffi::{MagiskInit, preload_ack, preload_lib, preload_policy, split_plat_cil};\nuse base::const_format::concatcp;\nuse base::nix::fcntl::OFlag;\nuse base::{\n    BytesExt, LibcReturn, LoggedResult, MappedFile, ResultExt, Utf8CStr, cstr, debug, error, info,\n    libc, raw_cstr,\n};\nuse magiskpolicy::ffi::SePolicy;\nuse std::io::{Read, Write};\nuse std::ptr;\nuse std::thread::sleep;\nuse std::time::Duration;\n\nconst MOCK_VERSION: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, \"/version\"));\nconst MOCK_LOAD: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, \"/load\"));\nconst MOCK_ENFORCE: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, \"/enforce\"));\nconst MOCK_REQPROT: &Utf8CStr = cstr!(concatcp!(SELINUXMOCK, \"/checkreqprot\"));\n\nconst SELINUX_MNT: &str = \"/sys/fs/selinux\";\nconst SELINUX_ENFORCE: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, \"/enforce\"));\nconst SELINUX_LOAD: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, \"/load\"));\nconst SELINUX_REQPROT: &Utf8CStr = cstr!(concatcp!(SELINUX_MNT, \"/checkreqprot\"));\n\nenum SePatchStrategy {\n    // 2SI, Android 10+\n    // On 2SI devices, the 2nd stage init is always a dynamic executable.\n    // This meant that instead of going through convoluted hacks, we can just\n    // LD_PRELOAD and replace security_load_policy with our own implementation.\n    LdPreload,\n    // Treble enabled, Android 8.0+\n    // selinuxfs is mounted in init.cpp. Errors when mounting selinuxfs is ignored,\n    // which means that we can directly mount selinuxfs ourselves and hijack nodes in it.\n    SelinuxFs,\n    // Dynamic patching, Android 6.0 - 7.1\n    // selinuxfs is mounted in libselinux's selinux_android_load_policy(). Errors when\n    // mounting selinuxfs is fatal, which means we need to block init's control flow after\n    // it mounted selinuxfs for us, then we can hijack nodes in it.\n    Legacy,\n}\n\n// Note for non-LD_PRELOAD strategy:\n//\n// We need to make sure the actual init process is blocked until sepolicy is loaded,\n// or else restorecon will fail and re-exec won't change context, causing boot failure.\n// We (ab)use the fact that init either reads the enforce node, or writes the checkreqprot\n// node, and because both has been replaced with FIFO files, init will block until we\n// handle it, effectively hijacking its control flow until the patched sepolicy is loaded.\n\nfn mock_fifo(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {\n    debug!(\"Hijack [{}]\", target);\n    mock.mkfifo(0o666)?;\n    mock.bind_mount_to(target, false).log()\n}\n\nfn mock_file(target: &Utf8CStr, mock: &Utf8CStr) -> LoggedResult<()> {\n    debug!(\"Hijack [{}]\", target);\n    drop(mock.create(OFlag::O_RDONLY, 0o666)?);\n    mock.bind_mount_to(target, false).log()\n}\n\nimpl MagiskInit {\n    pub(crate) fn handle_sepolicy(&mut self) {\n        self.handle_sepolicy_impl().ok();\n    }\n\n    fn cleanup_and_load(&self, rules: &str) {\n        // Cleanup the hijacks\n        cstr!(\"/init\").unmount().ok();\n        SELINUX_LOAD.unmount().log_ok();\n        SELINUX_ENFORCE.unmount().ok();\n        SELINUX_REQPROT.unmount().ok();\n\n        let mut sepol = SePolicy::from_file(MOCK_LOAD);\n        sepol.magisk_rules();\n        sepol.load_rules(rules);\n        sepol.to_file(SELINUX_LOAD);\n\n        // For some reason, restorecon on /init won't work in some cases\n        cstr!(\"/init\")\n            .follow_link()\n            .set_secontext(cstr!(\"u:object_r:init_exec:s0\"))\n            .ok();\n\n        // restore mounted files' context after sepolicy loaded\n        self.restore_overlay_contexts();\n    }\n\n    fn handle_sepolicy_impl(&mut self) -> LoggedResult<()> {\n        cstr!(SELINUXMOCK).mkdir(0o711)?;\n\n        let mut rules = String::new();\n        let mut policy_ver = cstr!(\"/selinux_version\");\n        let rule_file = cstr!(concatcp!(\"/data/\", PREINITMIRR, \"/sepolicy.rule\"));\n        if rule_file.exists() {\n            debug!(\"Loading custom sepolicy patch: [{}]\", rule_file);\n            rule_file\n                .open(OFlag::O_RDONLY)?\n                .read_to_string(&mut rules)?;\n        }\n\n        // Step 0: determine strategy\n\n        let strat: SePatchStrategy;\n\n        if cstr!(\"/system/bin/init\").exists() {\n            strat = SePatchStrategy::LdPreload;\n        } else {\n            let init = MappedFile::open(cstr!(\"/init\"))?;\n            if init.contains(split_plat_cil().as_str().as_bytes()) {\n                // Supports split policy\n                strat = SePatchStrategy::SelinuxFs;\n            } else if init.contains(policy_ver.as_bytes()) {\n                // Does not support split policy, hijack /selinux_version\n                strat = SePatchStrategy::Legacy;\n            } else if init.contains(cstr!(\"/sepolicy_version\").as_bytes()) {\n                // Samsung custom path\n                policy_ver = cstr!(\"/sepolicy_version\");\n                strat = SePatchStrategy::Legacy;\n            } else {\n                error!(\"Unknown sepolicy setup, abort...\");\n                return Ok(());\n            }\n        }\n\n        // Step 1: setup for intercepting init boot control flow\n\n        match strat {\n            SePatchStrategy::LdPreload => {\n                info!(\"SePatchStrategy: LD_PRELOAD\");\n\n                cstr!(\"init-ld\").copy_to(preload_lib())?;\n                unsafe {\n                    libc::setenv(raw_cstr!(\"LD_PRELOAD\"), preload_lib().as_ptr(), 1);\n                }\n                preload_ack().mkfifo(0o666)?;\n            }\n            SePatchStrategy::SelinuxFs => {\n                info!(\"SePatchStrategy: SELINUXFS\");\n\n                if !SELINUX_ENFORCE.exists() {\n                    // selinuxfs was not already mounted, mount it ourselves\n\n                    // Remount procfs with proper options\n                    cstr!(\"/proc\").remount_with_data(cstr!(\"hidepid=2,gid=3009\"))?;\n\n                    // Preserve sysfs and procfs\n                    self.mount_list.retain(|s| s != \"/proc\" && s != \"/sys\");\n\n                    // Mount selinuxfs\n                    unsafe {\n                        libc::mount(\n                            raw_cstr!(\"selinuxfs\"),\n                            raw_cstr!(SELINUX_MNT),\n                            raw_cstr!(\"selinuxfs\"),\n                            0,\n                            ptr::null(),\n                        )\n                        .check_err()?;\n                    }\n                }\n\n                mock_file(SELINUX_LOAD, MOCK_LOAD)?;\n                mock_fifo(SELINUX_ENFORCE, MOCK_ENFORCE)?;\n            }\n            SePatchStrategy::Legacy => {\n                info!(\"SePatchStrategy: LEGACY\");\n\n                if !policy_ver.exists() {\n                    // The file does not exist, create one\n                    drop(policy_ver.create(OFlag::O_RDONLY, 0o666)?);\n                }\n\n                // The only purpose of this is to block init's control flow after it mounts\n                // selinuxfs and before it calls security_load_policy().\n                // selinux_android_load_policy() -> set_policy_index() -> open(policy_ver)\n                mock_fifo(policy_ver, MOCK_VERSION)?;\n            }\n        }\n\n        // Create a new process waiting for init operations\n        let pid = unsafe { libc::fork() };\n        if pid != 0 {\n            return Ok(());\n        }\n\n        // Step 2: wait for selinuxfs to be mounted (only for LEGACY)\n\n        let wait = Duration::from_millis(100);\n\n        if matches!(strat, SePatchStrategy::Legacy) {\n            // Busy wait until selinuxfs is mounted\n            while !SELINUX_ENFORCE.exists() {\n                // Retry every 100ms\n                sleep(wait);\n            }\n\n            // On Android 6.0, init does not call security_getenforce() first; instead it directly\n            // call security_setenforce() after security_load_policy(). What's even worse, it opens\n            // the enforce node with O_RDWR, which will not block when opening FIFO files.\n            // As a workaround, we do not mock the enforce node, and block init with mocking\n            // checkreqprot instead.\n            // Android 7.0 - 7.1 doesn't have this issue, but for simplicity, let's just use the\n            // same blocking strategy for both since it also works just fine.\n\n            mock_file(SELINUX_LOAD, MOCK_LOAD)?;\n            mock_fifo(SELINUX_REQPROT, MOCK_REQPROT)?;\n\n            // This will unblock init at selinux_android_load_policy() -> set_policy_index().\n            drop(MOCK_VERSION.open(OFlag::O_WRONLY)?);\n\n            policy_ver.unmount()?;\n\n            // libselinux does not read /selinux_version after open; instead it mmap the file,\n            // which can never succeed on FIFO files. This is fine as set_policy_index() will just\n            // fallback to the default index 0.\n        }\n\n        // Step 3: obtain sepolicy, patch, and load the patched sepolicy\n\n        match strat {\n            SePatchStrategy::LdPreload => {\n                // This open will block until preload.so finish writing the sepolicy\n                let mut ack_fd = preload_ack().open(OFlag::O_WRONLY)?;\n\n                let mut sepol = SePolicy::from_file(preload_policy());\n\n                // Remove the files before loading the policy\n                preload_policy().remove()?;\n                preload_ack().remove()?;\n\n                sepol.magisk_rules();\n                sepol.load_rules(&rules);\n                sepol.to_file(SELINUX_LOAD);\n\n                self.restore_overlay_contexts();\n\n                // Write ack to restore preload.so's control flow\n                ack_fd.write_all(\"0\".as_bytes())?;\n            }\n            SePatchStrategy::SelinuxFs => {\n                // This open will block until init calls security_getenforce().\n                let mut mock_enforce = MOCK_ENFORCE.open(OFlag::O_WRONLY)?;\n\n                self.cleanup_and_load(&rules);\n\n                // security_getenforce was called, read from real and redirect to mock\n                let mut data = vec![];\n                SELINUX_ENFORCE\n                    .open(OFlag::O_RDONLY)?\n                    .read_to_end(&mut data)?;\n                mock_enforce.write_all(&data)?;\n            }\n            SePatchStrategy::Legacy => {\n                let mut sz = 0_usize;\n                // Busy wait until sepolicy is fully written.\n                loop {\n                    let attr = MOCK_LOAD.get_attr()?;\n                    if sz != 0 && sz == attr.st.st_size as usize {\n                        break;\n                    }\n                    sz = attr.st.st_size as usize;\n                    // Check every 100ms\n                    sleep(wait);\n                }\n\n                self.cleanup_and_load(&rules);\n\n                // init is blocked on checkreqprot, write to the real node first, then\n                // unblock init by opening the mock FIFO.\n                SELINUX_REQPROT\n                    .open(OFlag::O_WRONLY)?\n                    .write_all(\"0\".as_bytes())?;\n                let mut v = vec![];\n                MOCK_REQPROT.open(OFlag::O_RDONLY)?.read_to_end(&mut v)?;\n            }\n        }\n\n        // At this point, the init process will be unblocked\n        // and continue on with restorecon + re-exec.\n\n        // Terminate process\n        std::process::exit(0);\n    }\n}\n"
  },
  {
    "path": "native/src/init/twostage.rs",
    "content": "use crate::ffi::MagiskInit;\nuse base::nix::fcntl::OFlag;\nuse base::{LoggedResult, MappedFile, MutBytesExt, ResultExt, cstr, debug, error};\nuse std::io::Write;\n\npub(crate) fn hexpatch_init_for_second_stage(writable: bool) {\n    let init = if writable {\n        MappedFile::open_rw(cstr!(\"/init\"))\n    } else {\n        MappedFile::open(cstr!(\"/init\"))\n    };\n\n    let Ok(mut init) = init else {\n        error!(\"Failed to open /init for hexpatch\");\n        return;\n    };\n\n    // Redirect original init to magiskinit\n    let from = \"/system/bin/init\";\n    let to = \"/data/magiskinit\";\n    let v = init.patch(from.as_bytes(), to.as_bytes());\n    #[allow(unused_variables)]\n    for off in &v {\n        debug!(\"Patch @ {:#010X} [{}] -> [{}]\", off, from, to);\n    }\n\n    if !writable {\n        // If we cannot directly modify /init, we need to bind mount a replacement on top of it\n        let src = cstr!(\"/init\");\n        let dest = cstr!(\"/data/init\");\n        let _ = || -> LoggedResult<()> {\n            {\n                let mut fd = dest.create(OFlag::O_CREAT | OFlag::O_WRONLY, 0)?;\n                fd.write_all(init.as_ref())?;\n            }\n            let attr = src.follow_link().get_attr()?;\n            dest.set_attr(&attr)?;\n            dest.bind_mount_to(src, false)?;\n            Ok(())\n        }();\n    }\n}\n\nimpl MagiskInit {\n    pub(crate) fn hijack_init_with_switch_root(&self) {\n        // We make use of original init's `SwitchRoot` to help us bind mount\n        // magiskinit to /system/bin/init to hijack second stage init.\n        //\n        // Two important assumption about 2SI:\n        // - The second stage init is always /system/bin/init\n        // - After `SwitchRoot`, /sdcard is always a symlink to `/storage/self/primary`.\n        //\n        // `SwitchRoot` will perform the following:\n        // - Recursive move all mounts under `/` to `/system`\n        // - chroot to `/system`\n        //\n        // The trick here is that in Magisk's first stage init, we can mount magiskinit to /sdcard,\n        // and create a symlink at /storage/self/primary pointing to /system/system/bin/init.\n        //\n        // During init's `SwitchRoot`, it will mount move /sdcard (which is magiskinit)\n        // to /system/sdcard, which is a symlink to /storage/self/primary, which is a\n        // symlink to /system/system/bin/init, which will eventually become /system/bin/init after\n        // chroot to /system. The effective result is that we coerce the original init into bind\n        // mounting magiskinit to /system/bin/init, successfully hijacking the second stage init.\n        //\n        // An edge case is that some devices (like meizu) use 2SI but does not switch root.\n        // In that case, they must already have a /sdcard in ramfs, thus we can check if\n        // /sdcard exists and fallback to using hexpatch.\n\n        if self.config.force_normal_boot {\n            cstr!(\"/first_stage_ramdisk/storage/self\")\n                .mkdirs(0o755)\n                .log_ok();\n            cstr!(\"/first_stage_ramdisk/storage/self/primary\")\n                .create_symlink_to(cstr!(\"/system/system/bin/init\"))\n                .log_ok();\n            debug!(\"Symlink /first_stage_ramdisk/storage/self/primary -> /system/system/bin/init\");\n            cstr!(\"/first_stage_ramdisk/sdcard\")\n                .create(OFlag::O_RDONLY | OFlag::O_CREAT | OFlag::O_CLOEXEC, 0)\n                .log_ok();\n        } else {\n            cstr!(\"/storage/self\").mkdirs(0o755).log_ok();\n            cstr!(\"/storage/self/primary\")\n                .create_symlink_to(cstr!(\"/system/system/bin/init\"))\n                .log_ok();\n            debug!(\"Symlink /storage/self/primary -> /system/system/bin/init\");\n        }\n        cstr!(\"/init\").rename_to(cstr!(\"/sdcard\")).log_ok();\n\n        // First try to mount magiskinit from rootfs to workaround Samsung RKP\n        if cstr!(\"/sdcard\")\n            .bind_mount_to(cstr!(\"/sdcard\"), false)\n            .is_ok()\n        {\n            debug!(\"Bind mount /sdcard -> /sdcard\");\n        } else {\n            // Binding mounting from rootfs is not supported before Linux 3.12\n            cstr!(\"/data/magiskinit\")\n                .bind_mount_to(cstr!(\"/sdcard\"), false)\n                .log_ok();\n            debug!(\"Bind mount /data/magiskinit -> /sdcard\");\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/rustfmt.toml",
    "content": "unstable_features = true\nimports_granularity = \"Module\"\n"
  },
  {
    "path": "native/src/sepolicy/Cargo.toml",
    "content": "[package]\nname = \"magiskpolicy\"\nversion.workspace = true\nedition.workspace = true\n\n[lib]\ncrate-type = [\"staticlib\", \"rlib\"]\npath = \"lib.rs\"\n\n[features]\nno-main = []\n\n[lints]\nworkspace = true\n\n[build-dependencies]\ncxx-gen = { workspace = true }\n\n[dependencies]\nbase = { workspace = true }\ncxx = { workspace = true }\n"
  },
  {
    "path": "native/src/sepolicy/api.cpp",
    "content": "#include <base.hpp>\n\n#include \"include/sepolicy.hpp\"\n\nusing Str = rust::Str;\nusing StrVec = rust::Vec<rust::Str>;\nusing Xperms = rust::Vec<Xperm>;\n\n#if 0\ntemplate<typename Arg>\nstd::string as_str(const Arg &arg) {\n    if constexpr (std::is_same_v<Arg, Xperm>) {\n        return (std::string) SePolicy::xperm_to_string(arg);\n    } else if constexpr (std::is_same_v<Arg, rust::Str>) {\n        return arg.empty() ? \"*\" : (std::string) arg;\n    }\n}\n\n// Print out all rules going through public API for debugging\ntemplate<typename ...Args>\nstatic void print_rule(const char *action, Args ...args) {\n    std::string s;\n    s = (... + (\" \" + as_str(args)));\n    LOGD(\"%s%s\\n\", action, s.data());\n}\n#else\n#define print_rule(...) ((void) 0)\n#endif\n\ntemplate<typename F, typename ...T>\nrequires(std::invocable<F, T...>)\nstatic inline void expand(F &&f, T &&...args) {\n    f(std::forward<T>(args)...);\n}\n\ntemplate<typename ...T>\nstatic inline void expand(Str s, T &&...args) {\n    expand(std::forward<T>(args)..., s);\n}\n\ntemplate<typename ...T>\nstatic inline void expand(const StrVec &vec, T &&...args) {\n    if (vec.empty()) {\n        expand(std::forward<T>(args)..., rust::Str{});\n    } else {\n        for (auto s : vec) {\n            expand(std::forward<T>(args)..., s);\n        }\n    }\n}\n\ntemplate<typename ...T>\nstatic inline void expand(const Xperms &vec, T &&...args) {\n    for (auto &p : vec) {\n        expand(std::forward<T>(args)..., p);\n    }\n}\n\nvoid SePolicy::allow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept {\n    expand(src, tgt, cls, perm, [this](auto ...args) {\n        print_rule(\"allow\", args...);\n        impl->add_rule(args..., AVTAB_ALLOWED, false);\n    });\n}\n\nvoid SePolicy::deny(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept {\n    expand(src, tgt, cls, perm, [this](auto ...args) {\n        print_rule(\"deny\", args...);\n        impl->add_rule(args..., AVTAB_ALLOWED, true);\n    });\n}\n\nvoid SePolicy::auditallow(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept {\n    expand(src, tgt, cls, perm, [this](auto ...args) {\n        print_rule(\"auditallow\", args...);\n        impl->add_rule(args..., AVTAB_AUDITALLOW, false);\n    });\n}\n\nvoid SePolicy::dontaudit(StrVec src, StrVec tgt, StrVec cls, StrVec perm) noexcept {\n    expand(src, tgt, cls, perm, [this](auto ...args) {\n        print_rule(\"dontaudit\", args...);\n        impl->add_rule(args..., AVTAB_AUDITDENY, true);\n    });\n}\n\nvoid SePolicy::permissive(StrVec types) noexcept {\n    expand(types, [this](auto ...args) {\n        print_rule(\"permissive\", args...);\n        impl->set_type_state(args..., true);\n    });\n}\n\nvoid SePolicy::enforce(StrVec types) noexcept {\n    expand(types, [this](auto ...args) {\n        print_rule(\"enforce\", args...);\n        impl->set_type_state(args..., false);\n    });\n}\n\nvoid SePolicy::typeattribute(StrVec types, StrVec attrs) noexcept {\n    expand(types, attrs, [this](auto ...args) {\n        print_rule(\"typeattribute\", args...);\n        impl->add_typeattribute(args...);\n    });\n}\n\nvoid SePolicy::type(Str type, StrVec attrs) noexcept {\n    expand(type, attrs, [this](auto name, auto attr) {\n        print_rule(\"type\", name, attr);\n        impl->add_type(name, TYPE_TYPE) && impl->add_typeattribute(name, attr);\n    });\n}\n\nvoid SePolicy::attribute(Str name) noexcept {\n    expand(name, [this](auto ...args) {\n        print_rule(\"attribute\", args...);\n        impl->add_type(args..., TYPE_ATTRIB);\n    });\n}\n\nvoid SePolicy::type_transition(Str src, Str tgt, Str cls, Str def, Str obj) noexcept {\n    expand(src, tgt, cls, def, obj, [this](auto s, auto t, auto c, auto d, auto o) {\n        if (!o.empty()) {\n            print_rule(\"type_transition\", s, t, c, d, o);\n            impl->add_filename_trans(s, t, c, d, o);\n        } else {\n            print_rule(\"type_transition\", s, t, c, d);\n            impl->add_type_rule(s, t, c, d, AVTAB_TRANSITION);\n        }\n    });\n}\n\nvoid SePolicy::type_change(Str src, Str tgt, Str cls, Str def) noexcept {\n    expand(src, tgt, cls, def, [this](auto ...args) {\n        print_rule(\"type_change\", args...);\n        impl->add_type_rule(args..., AVTAB_CHANGE);\n    });\n}\n\nvoid SePolicy::type_member(Str src, Str tgt, Str cls, Str def) noexcept {\n    expand(src, tgt, cls, def, [this](auto ...args) {\n        print_rule(\"type_member\", args...);\n        impl->add_type_rule(args..., AVTAB_MEMBER);\n    });\n}\n\nvoid SePolicy::genfscon(Str fs_name, Str path, Str ctx) noexcept {\n    expand(fs_name, path, ctx, [this](auto ...args) {\n        print_rule(\"genfscon\", args...);\n        impl->add_genfscon(args...);\n    });\n}\n\nvoid SePolicy::allowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept {\n    expand(src, tgt, cls, xperm, [this](auto ...args) {\n        print_rule(\"allowxperm\", args...);\n        impl->add_xperm_rule(args..., AVTAB_XPERMS_ALLOWED);\n    });\n}\n\nvoid SePolicy::auditallowxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept {\n    expand(src, tgt, cls, xperm, [this](auto ...args) {\n        print_rule(\"auditallowxperm\", args...);\n        impl->add_xperm_rule(args..., AVTAB_XPERMS_AUDITALLOW);\n    });\n}\n\nvoid SePolicy::dontauditxperm(StrVec src, StrVec tgt, StrVec cls, Xperms xperm) noexcept {\n    expand(src, tgt, cls, xperm, [this](auto ...args) {\n        print_rule(\"dontauditxperm\", args...);\n        impl->add_xperm_rule(args..., AVTAB_XPERMS_DONTAUDIT);\n    });\n}\n"
  },
  {
    "path": "native/src/sepolicy/build.rs",
    "content": "use crate::codegen::gen_cxx_binding;\n\n#[path = \"../include/codegen.rs\"]\nmod codegen;\n\nfn main() {\n    gen_cxx_binding(\"policy-rs\");\n}\n"
  },
  {
    "path": "native/src/sepolicy/cli.rs",
    "content": "use crate::ffi::SePolicy;\nuse crate::statement::format_statement_help;\nuse argh::FromArgs;\nuse base::libc::umask;\nuse base::{\n    CmdArgs, EarlyExitExt, FmtAdaptor, LoggedResult, Utf8CString, argh, cmdline_logging, cstr,\n    log_err,\n};\nuse std::ffi::c_char;\nuse std::io::stderr;\n\n#[derive(FromArgs)]\nstruct Cli {\n    #[argh(switch)]\n    live: bool,\n\n    #[argh(switch)]\n    magisk: bool,\n\n    #[argh(switch)]\n    compile_split: bool,\n\n    #[argh(switch)]\n    load_split: bool,\n\n    #[argh(switch)]\n    print_rules: bool,\n\n    #[argh(option)]\n    load: Option<Utf8CString>,\n\n    #[argh(option)]\n    save: Option<Utf8CString>,\n\n    #[argh(option)]\n    apply: Vec<Utf8CString>,\n\n    #[argh(positional)]\n    polices: Vec<String>,\n}\n\nfn print_usage(cmd: &str) {\n    eprintln!(\n        r#\"MagiskPolicy - SELinux Policy Patch Tool\n\nUsage: {cmd} [--options...] [policy statements...]\n\nOptions:\n   --help            show help message for policy statements\n   --load FILE       load monolithic sepolicy from FILE\n   --load-split      load from precompiled sepolicy or compile\n                     split cil policies\n   --compile-split   compile split cil policies\n   --save FILE       dump monolithic sepolicy to FILE\n   --live            immediately load sepolicy into the kernel\n   --magisk          apply built-in Magisk sepolicy rules\n   --apply FILE      apply rules from FILE, read and parsed\n                     line by line as policy statements\n                     (multiple --apply are allowed)\n   --print-rules     print all rules in the loaded sepolicy\n\nIf neither --load, --load-split, nor --compile-split is specified,\nit will load from current live policies (/sys/fs/selinux/policy)\n\"#\n    );\n\n    format_statement_help(&mut FmtAdaptor(&mut stderr())).ok();\n    eprintln!();\n}\n\n#[unsafe(no_mangle)]\npub unsafe extern \"C\" fn main(\n    argc: i32,\n    argv: *const *const c_char,\n    _envp: *const *const c_char,\n) -> i32 {\n    cmdline_logging();\n    unsafe {\n        umask(0);\n    }\n\n    let res = || -> LoggedResult<()> {\n        let cmds = CmdArgs::new(argc, argv);\n        let cmds = cmds.as_slice();\n        if argc < 2 {\n            print_usage(cmds.first().unwrap_or(&\"magiskpolicy\"));\n            return log_err!();\n        }\n        let cli = Cli::from_args(&[cmds[0]], &cmds[1..]).on_early_exit(|| print_usage(cmds[0]));\n\n        let mut sepol = match (cli.load, cli.load_split, cli.compile_split) {\n            (Some(file), false, false) => SePolicy::from_file(&file),\n            (None, true, false) => SePolicy::from_split(),\n            (None, false, true) => SePolicy::compile_split(),\n            (None, false, false) => SePolicy::from_file(cstr!(\"/sys/fs/selinux/policy\")),\n            _ => log_err!(\"Multiple load source supplied\")?,\n        };\n        if sepol._impl.is_null() {\n            log_err!(\"Cannot load policy\")?;\n        }\n\n        if cli.print_rules {\n            if cli.magisk\n                || !cli.apply.is_empty()\n                || !cli.polices.is_empty()\n                || cli.live\n                || cli.save.is_some()\n            {\n                log_err!(\"Cannot print rules with other options\")?;\n            }\n            sepol.print_rules();\n            return Ok(());\n        }\n\n        if cli.magisk {\n            sepol.magisk_rules();\n        }\n\n        for file in cli.apply {\n            sepol.load_rule_file(&file);\n        }\n\n        for statement in &cli.polices {\n            sepol.load_rules(statement);\n        }\n\n        if cli.live && !sepol.to_file(cstr!(\"/sys/fs/selinux/load\")) {\n            log_err!(\"Cannot apply policy\")?;\n        }\n\n        if let Some(file) = cli.save\n            && !sepol.to_file(&file)\n        {\n            log_err!(\"Cannot dump policy to {}\", file)?;\n        }\n        Ok(())\n    }();\n    if res.is_ok() { 0 } else { 1 }\n}\n"
  },
  {
    "path": "native/src/sepolicy/include/sepolicy.hpp",
    "content": "#pragma once\n\n#include <cstdlib>\n#include <string>\n\n#include <base.hpp>\n\n#include \"../policy-rs.hpp\"\n\n// sepolicy paths\n#define PLAT_POLICY_DIR     \"/system/etc/selinux/\"\n#define VEND_POLICY_DIR     \"/vendor/etc/selinux/\"\n#define PROD_POLICY_DIR     \"/product/etc/selinux/\"\n#define ODM_POLICY_DIR      \"/odm/etc/selinux/\"\n#define SYSEXT_POLICY_DIR   \"/system_ext/etc/selinux/\"\n#define SPLIT_PLAT_CIL      PLAT_POLICY_DIR \"plat_sepolicy.cil\"\n\n// selinuxfs paths\n#define SELINUX_MNT         \"/sys/fs/selinux\"\n#define SELINUX_VERSION     SELINUX_MNT \"/policyvers\"\n"
  },
  {
    "path": "native/src/sepolicy/lib.rs",
    "content": "pub use base;\nuse std::fmt::Write;\n\nuse crate::ffi::SePolicy;\n\n#[path = \"../include/consts.rs\"]\nmod consts;\n\n#[cfg(not(feature = \"no-main\"))]\nmod cli;\nmod rules;\nmod statement;\n\n#[cxx::bridge]\npub mod ffi {\n    struct Xperm {\n        low: u16,\n        high: u16,\n        reset: bool,\n    }\n\n    struct SePolicy {\n        #[cxx_name = \"impl\"]\n        _impl: UniquePtr<sepol_impl>,\n    }\n\n    unsafe extern \"C++\" {\n        include!(\"policy.hpp\");\n        include!(\"../base/include/base.hpp\");\n\n        #[cxx_name = \"Utf8CStr\"]\n        type Utf8CStrRef<'a> = base::Utf8CStrRef<'a>;\n\n        type sepol_impl;\n\n        fn allow(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>);\n        fn deny(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>);\n        fn auditallow(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>);\n        fn dontaudit(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<&str>);\n        fn allowxperm(self: &mut SePolicy, s: Vec<&str>, t: Vec<&str>, c: Vec<&str>, p: Vec<Xperm>);\n        fn auditallowxperm(\n            self: &mut SePolicy,\n            s: Vec<&str>,\n            t: Vec<&str>,\n            c: Vec<&str>,\n            p: Vec<Xperm>,\n        );\n        fn dontauditxperm(\n            self: &mut SePolicy,\n            s: Vec<&str>,\n            t: Vec<&str>,\n            c: Vec<&str>,\n            p: Vec<Xperm>,\n        );\n        fn permissive(self: &mut SePolicy, t: Vec<&str>);\n        fn enforce(self: &mut SePolicy, t: Vec<&str>);\n        fn typeattribute(self: &mut SePolicy, t: Vec<&str>, a: Vec<&str>);\n        #[cxx_name = \"type\"]\n        fn type_(self: &mut SePolicy, t: &str, a: Vec<&str>);\n        fn attribute(self: &mut SePolicy, t: &str);\n        fn type_transition(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str, o: &str);\n        fn type_change(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str);\n        fn type_member(self: &mut SePolicy, s: &str, t: &str, c: &str, d: &str);\n        fn genfscon(self: &mut SePolicy, s: &str, t: &str, c: &str);\n        #[allow(dead_code)]\n        fn strip_dontaudit(self: &mut SePolicy);\n\n        fn print_rules(self: &SePolicy);\n        fn to_file(self: &SePolicy, file: Utf8CStrRef) -> bool;\n\n        #[Self = SePolicy]\n        fn from_file(file: Utf8CStrRef) -> SePolicy;\n        #[Self = SePolicy]\n        fn from_split() -> SePolicy;\n        #[Self = SePolicy]\n        fn compile_split() -> SePolicy;\n        #[Self = SePolicy]\n        fn from_data(data: &[u8]) -> SePolicy;\n    }\n\n    extern \"Rust\" {\n        #[Self = SePolicy]\n        fn xperm_to_string(perm: &Xperm) -> String;\n    }\n}\n\nimpl SePolicy {\n    fn xperm_to_string(perm: &ffi::Xperm) -> String {\n        let mut s = String::new();\n        if perm.reset {\n            s.push('~');\n        }\n        if perm.low == perm.high {\n            s.write_fmt(format_args!(\"{{ {:#06X} }}\", perm.low)).ok();\n        } else {\n            s.write_fmt(format_args!(\"{{ {:#06X}-{:#06X} }}\", perm.low, perm.high))\n                .ok();\n        }\n        s\n    }\n}\n"
  },
  {
    "path": "native/src/sepolicy/policy.hpp",
    "content": "#pragma once\n\n// Internal APIs, do not use directly\n\n#include <map>\n#include <string_view>\n#include <rust/cxx.h>\n\n#include <sepol/policydb/policydb.h>\n\nusing Str = rust::Str;\n\nstruct Xperm;\n\nclass sepol_impl {\n    avtab_ptr_t find_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms);\n    avtab_ptr_t insert_avtab_node(avtab_key_t *key);\n    avtab_ptr_t get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms);\n    void print_type(FILE *fp, type_datum_t *type);\n    void print_avtab(FILE *fp, avtab_ptr_t node);\n    void print_filename_trans(FILE *fp, hashtab_ptr_t node);\n\n    bool add_rule(Str s, Str t, Str c, Str p, int effect, bool invert);\n    void add_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, perm_datum_t *perm, int effect, bool invert);\n    void add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const Xperm &p, int effect);\n    bool add_xperm_rule(Str s, Str t, Str c, const Xperm &p, int effect);\n    bool add_type_rule(Str s, Str t, Str c, Str d, int effect);\n    bool add_filename_trans(Str s, Str t, Str c, Str d, Str o);\n    bool add_genfscon(Str fs_name, Str path, Str context);\n    bool add_type(Str type_name, uint32_t flavor);\n    bool set_type_state(Str type_name, bool permissive);\n    void add_typeattribute(type_datum_t *type, type_datum_t *attr);\n    bool add_typeattribute(Str type, Str attr);\n\n    policydb *db;\n\n    std::map<std::string_view, std::array<const char *, 32>> class_perm_names;\n\n    friend struct SePolicy;\n\npublic:\n    sepol_impl(policydb *db) : db(db) {}\n    ~sepol_impl();\n};\n"
  },
  {
    "path": "native/src/sepolicy/policydb.cpp",
    "content": "#include \"include/sepolicy.hpp\"\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n\n#include <cil/cil.h>\n\n#include <base.hpp>\n#include <flags.h>\n\nusing namespace std;\n\n#define SHALEN 64\nstatic bool cmp_sha256(const char *a, const char *b) {\n    char id_a[SHALEN] = {0};\n    char id_b[SHALEN] = {0};\n    if (int fd = xopen(a, O_RDONLY | O_CLOEXEC); fd >= 0) {\n        xread(fd, id_a, SHALEN);\n        close(fd);\n    } else {\n        return false;\n    }\n\n    if (int fd = xopen(b, O_RDONLY | O_CLOEXEC); fd >= 0) {\n        xread(fd, id_b, SHALEN);\n        close(fd);\n    } else {\n        return false;\n    }\n    LOGD(\"%s=[%.*s]\\n\", a, SHALEN, id_a);\n    LOGD(\"%s=[%.*s]\\n\", b, SHALEN, id_b);\n    return memcmp(id_a, id_b, SHALEN) == 0;\n}\n\nstatic bool check_precompiled(const char *precompiled) {\n    bool ok = false;\n    const char *actual_sha;\n    char compiled_sha[128];\n\n    actual_sha = PLAT_POLICY_DIR \"plat_and_mapping_sepolicy.cil.sha256\";\n    if (access(actual_sha, R_OK) == 0) {\n        ok = true;\n        sprintf(compiled_sha, \"%s.plat_and_mapping.sha256\", precompiled);\n        if (!cmp_sha256(actual_sha, compiled_sha))\n            return false;\n    }\n\n    actual_sha = PLAT_POLICY_DIR \"plat_sepolicy_and_mapping.sha256\";\n    if (access(actual_sha, R_OK) == 0) {\n        ok = true;\n        sprintf(compiled_sha, \"%s.plat_sepolicy_and_mapping.sha256\", precompiled);\n        if (!cmp_sha256(actual_sha, compiled_sha))\n            return false;\n    }\n\n    actual_sha = PROD_POLICY_DIR \"product_sepolicy_and_mapping.sha256\";\n    if (access(actual_sha, R_OK) == 0) {\n        ok = true;\n        sprintf(compiled_sha, \"%s.product_sepolicy_and_mapping.sha256\", precompiled);\n        if (!cmp_sha256(actual_sha, compiled_sha) != 0)\n            return false;\n    }\n\n    actual_sha = SYSEXT_POLICY_DIR \"system_ext_sepolicy_and_mapping.sha256\";\n    if (access(actual_sha, R_OK) == 0) {\n        ok = true;\n        sprintf(compiled_sha, \"%s.system_ext_sepolicy_and_mapping.sha256\", precompiled);\n        if (!cmp_sha256(actual_sha, compiled_sha) != 0)\n            return false;\n    }\n\n    return ok;\n}\n\nstatic void load_cil(struct cil_db *db, const char *file) {\n    mmap_data d(file);\n    cil_add_file(db, file, (const char *) d.data(), d.size());\n    LOGD(\"cil_add [%s]\\n\", file);\n}\n\nSePolicy SePolicy::from_data(rust::Slice<const uint8_t> data) noexcept {\n    LOGD(\"Load policy from data\\n\");\n\n    policy_file_t pf;\n    policy_file_init(&pf);\n    pf.data = (char *) data.data();\n    pf.len = data.size();\n    pf.type = PF_USE_MEMORY;\n\n    auto db = static_cast<policydb_t *>(malloc(sizeof(policydb_t)));\n    if (policydb_init(db) || policydb_read(db, &pf, 0)) {\n        LOGE(\"Fail to load policy from data\\n\");\n        free(db);\n        return {};\n    }\n\n    return {std::make_unique<sepol_impl>(db)};\n}\n\nSePolicy SePolicy::from_file(::Utf8CStr file) noexcept {\n    LOGD(\"Load policy from: %.*s\\n\", static_cast<int>(file.size()), file.data());\n\n    policy_file_t pf;\n    policy_file_init(&pf);\n    auto fp = xopen_file(file.data(), \"re\");\n    pf.fp = fp.get();\n    pf.type = PF_USE_STDIO;\n\n    auto db = static_cast<policydb_t *>(malloc(sizeof(policydb_t)));\n    if (policydb_init(db) || policydb_read(db, &pf, 0)) {\n        LOGE(\"Fail to load policy from %.*s\\n\", static_cast<int>(file.size()), file.data());\n        free(db);\n        return {};\n    }\n\n    return {std::make_unique<sepol_impl>(db)};\n}\n\nSePolicy SePolicy::compile_split() noexcept {\n    char path[128], plat_ver[10];\n    cil_db_t *db = nullptr;\n    sepol_policydb_t *pdb = nullptr;\n    FILE *f;\n    int policy_ver;\n    const char *cil_file;\n#if MAGISK_DEBUG\n    cil_set_log_level(CIL_INFO);\n#endif\n    cil_set_log_handler(+[](int lvl, const char *msg) {\n        if (lvl == CIL_ERR) {\n            LOGE(\"cil: %s\", msg);\n        } else if (lvl == CIL_WARN) {\n            LOGW(\"cil: %s\", msg);\n        } else if (lvl == CIL_INFO) {\n            LOGI(\"cil: %s\", msg);\n        } else {\n            LOGD(\"cil: %s\", msg);\n        }\n    });\n\n    cil_db_init(&db);\n    run_finally fin([db_ptr = &db]{ cil_db_destroy(db_ptr); });\n    cil_set_mls(db, 1);\n    cil_set_multiple_decls(db, 1);\n    cil_set_disable_neverallow(db, 1);\n    cil_set_target_platform(db, SEPOL_TARGET_SELINUX);\n    cil_set_attrs_expand_generated(db, 1);\n\n    f = xfopen(SELINUX_VERSION, \"re\");\n    fscanf(f, \"%d\", &policy_ver);\n    fclose(f);\n    cil_set_policy_version(db, policy_ver);\n\n    // Get mapping version\n    f = xfopen(VEND_POLICY_DIR \"plat_sepolicy_vers.txt\", \"re\");\n    fscanf(f, \"%s\", plat_ver);\n    fclose(f);\n\n    // plat\n    load_cil(db, SPLIT_PLAT_CIL);\n\n    sprintf(path, PLAT_POLICY_DIR \"mapping/%s.cil\", plat_ver);\n    load_cil(db, path);\n\n    sprintf(path, PLAT_POLICY_DIR \"mapping/%s.compat.cil\", plat_ver);\n    if (access(path, R_OK) == 0)\n        load_cil(db, path);\n\n    // system_ext\n    sprintf(path, SYSEXT_POLICY_DIR \"mapping/%s.cil\", plat_ver);\n    if (access(path, R_OK) == 0)\n        load_cil(db, path);\n\n    sprintf(path, SYSEXT_POLICY_DIR \"mapping/%s.compat.cil\", plat_ver);\n    if (access(path, R_OK) == 0)\n        load_cil(db, path);\n\n    cil_file = SYSEXT_POLICY_DIR \"system_ext_sepolicy.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    // product\n    sprintf(path, PROD_POLICY_DIR \"mapping/%s.cil\", plat_ver);\n    if (access(path, R_OK) == 0)\n        load_cil(db, path);\n\n    cil_file = PROD_POLICY_DIR \"product_sepolicy.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    // vendor\n    cil_file = VEND_POLICY_DIR \"nonplat_sepolicy.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    cil_file = VEND_POLICY_DIR \"plat_pub_versioned.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    cil_file = VEND_POLICY_DIR \"vendor_sepolicy.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    // odm\n    cil_file = ODM_POLICY_DIR \"odm_sepolicy.cil\";\n    if (access(cil_file, R_OK) == 0)\n        load_cil(db, cil_file);\n\n    if (cil_compile(db))\n        return {};\n    if (cil_build_policydb(db, &pdb))\n        return {};\n    return {std::make_unique<sepol_impl>(&pdb->p)};\n}\n\nSePolicy SePolicy::from_split() noexcept {\n    const char *odm_pre = ODM_POLICY_DIR \"precompiled_sepolicy\";\n    const char *vend_pre = VEND_POLICY_DIR \"precompiled_sepolicy\";\n    if (access(odm_pre, R_OK) == 0 && check_precompiled(odm_pre))\n        return SePolicy::from_file(odm_pre);\n    else if (access(vend_pre, R_OK) == 0 && check_precompiled(vend_pre))\n        return SePolicy::from_file(vend_pre);\n    else\n        return SePolicy::compile_split();\n}\n\nsepol_impl::~sepol_impl() {\n    policydb_destroy(db);\n    free(db);\n}\n\nstatic int vec_write(void *v, const char *buf, int len) {\n    auto vec = static_cast<vector<char> *>(v);\n    vec->insert(vec->end(), buf, buf + len);\n    return len;\n}\n\nbool SePolicy::to_file(::Utf8CStr file) const noexcept {\n    // No partial writes are allowed to /sys/fs/selinux/load, thus the reason why we\n    // first dump everything into memory, then directly call write system call\n    vector<char> out;\n    FILE *fp = funopen(&out, nullptr, vec_write, nullptr, nullptr);\n    // Since we're directly writing to memory, disable buffering\n    setbuf(fp, nullptr);\n\n    policy_file_t pf;\n    policy_file_init(&pf);\n    pf.type = PF_USE_STDIO;\n    pf.fp = fp;\n    if (policydb_write(impl->db, &pf)) {\n        LOGE(\"Fail to create policy image\\n\");\n        fclose(fp);\n        return false;\n    }\n    fclose(fp);\n\n    int fd = xopen(file.data(), O_WRONLY | O_CREAT | O_CLOEXEC, 0644);\n    if (fd < 0)\n        return false;\n    if (struct stat st{}; xfstat(fd, &st) == 0 && st.st_size > 0) {\n        ftruncate(fd, 0);\n    }\n    xwrite(fd, out.data(), out.size());\n\n    close(fd);\n    return true;\n}\n"
  },
  {
    "path": "native/src/sepolicy/rules.rs",
    "content": "use crate::SePolicy;\nuse crate::consts::{SEPOL_FILE_TYPE, SEPOL_LOG_TYPE, SEPOL_PROC_DOMAIN};\nuse crate::ffi::Xperm;\nuse base::{LogLevel, set_log_level_state};\n\nmacro_rules! rules {\n    (@args all) => {\n        vec![]\n    };\n    (@args xall) => {\n        vec![Xperm { low: 0x0000, high: 0xFFFF, reset: false }]\n    };\n    (@args svcmgr) => {\n        vec![\"servicemanager\", \"vndservicemanager\", \"hwservicemanager\"]\n    };\n    (@args [proc]) => {\n        vec![SEPOL_PROC_DOMAIN]\n    };\n    (@args [file]) => {\n        vec![SEPOL_FILE_TYPE]\n    };\n    (@args [log]) => {\n        vec![SEPOL_LOG_TYPE]\n    };\n    (@args proc) => {\n        SEPOL_PROC_DOMAIN\n    };\n    (@args file) => {\n        SEPOL_FILE_TYPE\n    };\n    (@args log) => {\n        SEPOL_LOG_TYPE\n    };\n    (@args [$($arg:tt)*]) => {\n        vec![$($arg)*]\n    };\n    (@args $arg:expr) => {\n        $arg\n    };\n    (@stmt $self:ident) => {};\n    (@stmt $self:ident $action:ident($($args:tt),*); $($res:tt)*) => {\n        $self.$action($(rules!(@args $args)),*);\n        rules!{@stmt $self $($res)* }\n    };\n    (use $self:ident; $($res:tt)*) => {{\n        rules!{@stmt $self $($res)* }\n    }};\n}\n\nimpl SePolicy {\n    pub fn magisk_rules(&mut self) {\n        // Temp suppress warnings\n        set_log_level_state(LogLevel::Warn, false);\n        rules! {\n            use self;\n            // Prevent anything to change sepolicy except ourselves\n            deny(all, [\"kernel\"], [\"security\"], [\"load_policy\"]);\n            type_(proc, [\"domain\"]);\n            typeattribute([proc], [\"mlstrustedsubject\", \"netdomain\", \"appdomain\"]);\n            type_(file, [\"file_type\"]);\n            typeattribute([file], [\"mlstrustedobject\"]);\n            type_(log, [\"file_type\"]);\n            typeattribute([log], [\"mlstrustedobject\"]);\n\n            // Create unconstrained file type\n            allow([\"domain\"], [file],\n                [\"file\", \"dir\", \"fifo_file\", \"chr_file\", \"lnk_file\", \"sock_file\"], all);\n\n            // Only allow zygote to open log pipe\n            allow([\"zygote\"], [log], [\"fifo_file\"], [\"open\", \"read\"]);\n            // Allow all processes to output logs\n            allow([\"domain\"], [log], [\"fifo_file\"], [\"write\"]);\n\n            // Make our root domain unconstrained\n            allow([proc], [\n                \"fs_type\", \"dev_type\", \"file_type\", \"domain\",\n                \"service_manager_type\", \"hwservice_manager_type\", \"vndservice_manager_type\",\n                \"port_type\", \"node_type\", \"property_type\"\n            ], all, all);\n\n            // Just in case, make the domain permissive\n            permissive([proc]);\n\n            // Allow us to do any ioctl\n            allowxperm([proc], [\"fs_type\", \"dev_type\", \"file_type\", \"domain\"],\n                [\"blk_file\", \"fifo_file\", \"chr_file\"], xall);\n            allowxperm([proc], [proc], [\"tcp_socket\", \"udp_socket\", \"rawip_socket\"], xall);\n\n            // Let binder work with our processes\n            allow(svcmgr, [proc], [\"dir\"], [\"search\"]);\n            allow(svcmgr, [proc], [\"file\"], [\"open\", \"read\", \"map\"]);\n            allow(svcmgr, [proc], [\"process\"], [\"getattr\"]);\n            allow([\"domain\"], [proc], [\"binder\"], [\"call\", \"transfer\"]);\n\n            // Other common IPC\n            allow([\"domain\"], [proc], [\"process\"], [\"sigchld\"]);\n            allow([\"domain\"], [proc], [\"fd\"], [\"use\"]);\n            allow([\"domain\"], [proc], [\"fifo_file\"], [\"write\", \"read\", \"open\", \"getattr\"]);\n\n            // Allow these processes to access MagiskSU and output logs\n            allow([\"zygote\", \"shell\", \"platform_app\",\n                \"system_app\", \"priv_app\", \"untrusted_app\", \"untrusted_app_all\"],\n                [proc], [\"unix_stream_socket\"], [\"connectto\", \"getopt\"]);\n\n            // Let selected domains access tmpfs files\n            // For tmpfs overlay on 2SI, Zygisk on lower Android versions and AVD scripts\n            allow([\"init\", \"zygote\", \"shell\"], [\"tmpfs\"], [\"file\"], all);\n\n            // Allow magiskinit daemon to log to kmsg\n            allow([\"kernel\"], [\"rootfs\", \"tmpfs\"], [\"chr_file\"], [\"write\"]);\n\n            // Allow magiskinit daemon to handle mock selinuxfs\n            allow([\"kernel\"], [\"tmpfs\"], [\"fifo_file\"], [\"open\", \"read\", \"write\"]);\n\n            // For relabelling files\n            allow([\"rootfs\"], [\"labeledfs\", \"tmpfs\"], [\"filesystem\"], [\"associate\"]);\n            allow([file], [\"pipefs\", \"devpts\"], [\"filesystem\"], [\"associate\"]);\n            allow([\"kernel\"], all, [\"file\"], [\"relabelto\"]);\n            allow([\"kernel\"], [\"tmpfs\"], [\"file\"], [\"relabelfrom\"]);\n\n            // Let init transit to SEPOL_PROC_DOMAIN\n            allow([\"kernel\"], [\"kernel\"], [\"process\"], [\"setcurrent\"]);\n            allow([\"kernel\"], [proc], [\"process\"], [\"dyntransition\"]);\n\n            // Let init run stuffs\n            allow([\"init\"], [proc], [\"process\"], all);\n\n            // Zygisk rules\n            allow([\"zygote\"], [\"zygote\"], [\"process\"], [\"execmem\"]);\n            allow([\"zygote\"], [\"fs_type\"], [\"filesystem\"], [\"unmount\"]);\n            allow([\"system_server\"], [\"system_server\"], [\"process\"], [\"execmem\"]);\n\n            // Shut llkd up\n            dontaudit([\"llkd\"], [proc], [\"process\"], [\"ptrace\"]);\n\n            // Keep /data/adb/* context\n            deny([\"init\"], [\"adb_data_file\"], [\"dir\"], [\"search\"]);\n            deny([\"vendor_init\"], [\"adb_data_file\"], [\"dir\"], [\"search\"]);\n        }\n\n        #[cfg(any())]\n        self.strip_dontaudit();\n\n        set_log_level_state(LogLevel::Warn, true);\n    }\n}\n"
  },
  {
    "path": "native/src/sepolicy/sepolicy.cpp",
    "content": "#include <base.hpp>\n\n#include \"include/sepolicy.hpp\"\n\nusing namespace std;\n\n// Invert is adding rules for auditdeny; in other cases, invert is removing rules\n#define strip_av(effect, invert) ((effect == AVTAB_AUDITDENY) == !invert)\n\n// libsepol internal APIs\n__BEGIN_DECLS\nint policydb_index_decls(sepol_handle_t * handle, policydb_t * p);\nint avtab_hash(struct avtab_key *keyp, uint32_t mask);\nint type_set_expand(type_set_t * set, ebitmap_t * t, policydb_t * p, unsigned char alwaysexpand);\nint context_from_string(\n        sepol_handle_t * handle,\n        const policydb_t * policydb,\n        context_struct_t ** cptr,\n        const char *con_str, size_t con_str_len);\nint context_to_string(\n        sepol_handle_t * handle,\n        const policydb_t * policydb,\n        const context_struct_t * context,\n        char **result, size_t * result_len);\n__END_DECLS\n\ntemplate <typename T>\nstruct auto_cast_wrapper {\n    auto_cast_wrapper(T *ptr) : ptr(ptr) {}\n    template <typename U>\n    operator U*() const { return static_cast<U*>(ptr); }\nprivate:\n    T *ptr;\n};\n\ntemplate <typename T>\nstatic auto_cast_wrapper<T> auto_cast(T *p) {\n    return auto_cast_wrapper<T>(p);\n}\n\ntemplate <size_t T>\nstatic size_t copy_str(std::array<char, T> &dest, rust::Str src) {\n    if (T == 0) return 0;\n    size_t len = std::min(T - 1, src.size());\n    memcpy(dest.data(), src.data(), len);\n    dest[len] = '\\0';\n    return len;\n}\n\nstatic char *dup_str(rust::Str src) {\n    size_t len = src.size();\n    char *s = static_cast<char *>(malloc(len + 1));\n    memcpy(s, src.data(), len);\n    s[len] = '\\0';\n    return s;\n}\n\nstatic bool str_eq(string_view a, rust::Str b) {\n    return a.size() == b.size() && memcmp(a.data(), b.data(), a.size()) == 0;\n}\n\nstatic auto hashtab_find(hashtab_t h, Str key) {\n    array<char, 256> buf{};\n    copy_str(buf, key);\n    return auto_cast(hashtab_search(h, buf.data()));\n}\n\ntemplate <class Node, class Func>\nstatic void list_for_each(Node *node_ptr, const Func &fn) {\n    auto cur = node_ptr;\n    while (cur) {\n        auto next = cur->next;\n        fn(cur);\n        cur = next;\n    }\n}\n\ntemplate <class Node, class Func>\nstatic Node *list_find(Node *node_ptr, const Func &fn) {\n    for (auto cur = node_ptr; cur; cur = cur->next) {\n        if (fn(cur)) {\n            return cur;\n        }\n    }\n    return nullptr;\n}\n\ntemplate <class Node, class Func>\nstatic void hash_for_each(Node **node_ptr, int n_slot, const Func &fn) {\n    for (int i = 0; i < n_slot; ++i) {\n        list_for_each(node_ptr[i], fn);\n    }\n}\n\ntemplate <class Func>\nstatic void hashtab_for_each(hashtab_t htab, const Func &fn) {\n    hash_for_each(htab->htable, htab->size, fn);\n}\n\ntemplate <class Func>\nstatic void avtab_for_each(avtab_t *avtab, const Func &fn) {\n    hash_for_each(avtab->htable, avtab->nslot, fn);\n}\n\ntemplate <class Func>\nstatic void for_each_attr(hashtab_t htab, const Func &fn) {\n    hashtab_for_each(htab, [&](hashtab_ptr_t node) {\n        auto type = static_cast<type_datum_t *>(node->datum);\n        if (type->flavor == TYPE_ATTRIB)\n            fn(type);\n    });\n}\n\nstatic int avtab_remove_node(avtab_t *h, avtab_ptr_t node) {\n    if (!h || !h->htable)\n        return SEPOL_ENOMEM;\n    int hvalue = avtab_hash(&node->key, h->mask);\n    avtab_ptr_t prev = nullptr;\n    avtab_ptr_t cur = h->htable[hvalue];\n    while (cur) {\n        if (cur == node)\n            break;\n        prev = cur;\n        cur = cur->next;\n    }\n    if (cur == nullptr)\n        return SEPOL_ENOENT;\n\n    // Detach from link list\n    if (prev)\n        prev->next = node->next;\n    else\n        h->htable[hvalue] = node->next;\n    h->nel--;\n\n    // Free memory\n    free(node->datum.xperms);\n    free(node);\n    return 0;\n}\n\nstatic bool is_redundant(avtab_ptr_t node) {\n    switch (node->key.specified) {\n    case AVTAB_AUDITDENY:\n        return node->datum.data == ~0U;\n    case AVTAB_XPERMS:\n        return node->datum.xperms == nullptr;\n    default:\n        return node->datum.data == 0U;\n    }\n}\n\navtab_ptr_t sepol_impl::find_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms) {\n    avtab_ptr_t node;\n\n    // AVTAB_XPERMS entries are not necessarily unique\n    if (key->specified & AVTAB_XPERMS) {\n        if (xperms == nullptr)\n            return nullptr;\n        node = avtab_search_node(&db->te_avtab, key);\n        while (node) {\n            if ((node->datum.xperms->specified == xperms->specified) &&\n                (node->datum.xperms->driver == xperms->driver)) {\n                node = nullptr;\n                break;\n            }\n            node = avtab_search_node_next(node, key->specified);\n        }\n    } else {\n        node = avtab_search_node(&db->te_avtab, key);\n    }\n\n    return node;\n}\n\navtab_ptr_t sepol_impl::insert_avtab_node(avtab_key_t *key) {\n    avtab_datum_t avdatum{};\n    // AUDITDENY, aka DONTAUDIT, are &= assigned, versus |= for others.\n    // Initialize the data accordingly.\n    avdatum.data = key->specified == AVTAB_AUDITDENY ? ~0U : 0U;\n    return avtab_insert_nonunique(&db->te_avtab, key, &avdatum);\n}\n\navtab_ptr_t sepol_impl::get_avtab_node(avtab_key_t *key, avtab_extended_perms_t *xperms) {\n    avtab_ptr_t node = find_avtab_node(key, xperms);\n    if (!node) {\n        node = insert_avtab_node(key);\n    }\n    return node;\n}\n\nvoid sepol_impl::add_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, perm_datum_t *perm, int effect, bool invert) {\n    if (src == nullptr) {\n        if (strip_av(effect, invert)) {\n            // Stripping av, have to go through all types for correct results\n            hashtab_for_each(db->p_types.table, [&](hashtab_ptr_t node) {\n                add_rule(auto_cast(node->datum), tgt, cls, perm, effect, invert);\n            });\n        } else {\n            // If we are not stripping av, go through all attributes instead of types for optimization\n            for_each_attr(db->p_types.table, [&](type_datum_t *type) {\n                add_rule(type, tgt, cls, perm, effect, invert);\n            });\n        }\n    } else if (tgt == nullptr) {\n        if (strip_av(effect, invert)) {\n            hashtab_for_each(db->p_types.table, [&](hashtab_ptr_t node) {\n                add_rule(src, auto_cast(node->datum), cls, perm, effect, invert);\n            });\n        } else {\n            for_each_attr(db->p_types.table, [&](type_datum_t *type) {\n                add_rule(src, type, cls, perm, effect, invert);\n            });\n        }\n    } else if (cls == nullptr) {\n        hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node) {\n            add_rule(src, tgt, auto_cast(node->datum), perm, effect, invert);\n        });\n    } else {\n        avtab_key_t key;\n        key.source_type = src->s.value;\n        key.target_type = tgt->s.value;\n        key.target_class = cls->s.value;\n        key.specified = effect;\n\n        avtab_ptr_t node = get_avtab_node(&key, nullptr);\n        if (invert) {\n            if (perm)\n                node->datum.data &= ~(1U << (perm->s.value - 1));\n            else\n                node->datum.data = 0U;\n        } else {\n            if (perm)\n                node->datum.data |= 1U << (perm->s.value - 1);\n            else\n                node->datum.data = ~0U;\n        }\n\n        if (is_redundant(node))\n            avtab_remove_node(&db->te_avtab, node);\n    }\n}\n\nbool sepol_impl::add_rule(Str s, Str t, Str c, Str p, int effect, bool invert) {\n    type_datum_t *src = nullptr, *tgt = nullptr;\n    class_datum_t *cls = nullptr;\n    perm_datum_t *perm = nullptr;\n\n    if (!s.empty()) {\n        src = hashtab_find(db->p_types.table, s);\n        if (src == nullptr) {\n            LOGW(\"source type %.*s does not exist\\n\", (int) s.size(), s.data());\n            return false;\n        }\n    }\n\n    if (!t.empty()) {\n        tgt = hashtab_find(db->p_types.table, t);\n        if (tgt == nullptr) {\n            LOGW(\"target type %.*s does not exist\\n\", (int) t.size(), t.data());\n            return false;\n        }\n    }\n\n    if (!c.empty()) {\n        cls = hashtab_find(db->p_classes.table, c);\n        if (cls == nullptr) {\n            LOGW(\"class %.*s does not exist\\n\", (int) c.size(), c.data());\n            return false;\n        }\n    }\n\n    if (!p.empty()) {\n        if (c.empty()) {\n            LOGW(\"No class is specified, cannot add perm [%.*s] \\n\", (int) p.size(), p.data());\n            return false;\n        }\n\n        perm = hashtab_find(cls->permissions.table, p);\n        if (perm == nullptr && cls->comdatum != nullptr) {\n            perm = hashtab_find(cls->comdatum->permissions.table, p);\n        }\n        if (perm == nullptr) {\n            LOGW(\"perm %.*s does not exist in class %.*s\\n\",\n                 (int) p.size(), p.data(), (int) c.size(), c.data());\n            return false;\n        }\n    }\n    add_rule(src, tgt, cls, perm, effect, invert);\n    return true;\n}\n\n#define ioctl_driver(x) (x>>8 & 0xFF)\n#define ioctl_func(x) (x & 0xFF)\n\nvoid sepol_impl::add_xperm_rule(type_datum_t *src, type_datum_t *tgt, class_datum_t *cls, const Xperm &p, int effect) {\n    if (db->policyvers < POLICYDB_VERSION_XPERMS_IOCTL) {\n        LOGW(\"policy version %u does not support ioctl extended permissions rules\\n\", db->policyvers);\n        return;\n    }\n    if (src == nullptr) {\n        for_each_attr(db->p_types.table, [&](type_datum_t *type) {\n            add_xperm_rule(type, tgt, cls, p, effect);\n        });\n    } else if (tgt == nullptr) {\n        for_each_attr(db->p_types.table, [&](type_datum_t *type) {\n            add_xperm_rule(src, type, cls, p, effect);\n        });\n    } else if (cls == nullptr) {\n        hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node) {\n            add_xperm_rule(src, tgt, auto_cast(node->datum), p, effect);\n        });\n    } else {\n        avtab_key_t key;\n        key.source_type = src->s.value;\n        key.target_type = tgt->s.value;\n        key.target_class = cls->s.value;\n        key.specified = effect;\n\n        // Each key may contain 1 driver node and 256 function nodes\n        avtab_ptr_t node_list[257] = { nullptr };\n#define driver_node (node_list[256])\n\n        // Find all rules with key\n        for (avtab_ptr_t node = avtab_search_node(&db->te_avtab, &key); node;) {\n            if (node->datum.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) {\n                driver_node = node;\n            } else if (node->datum.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) {\n                node_list[node->datum.xperms->driver] = node;\n            }\n            node = avtab_search_node_next(node, key.specified);\n        }\n\n        if (p.reset) {\n            for (int i = 0; i <= 0xFF; ++i) {\n                if (node_list[i]) {\n                    avtab_remove_node(&db->te_avtab, node_list[i]);\n                    node_list[i] = nullptr;\n                }\n            }\n            if (driver_node) {\n                memset(driver_node->datum.xperms->perms, 0, sizeof(avtab_extended_perms_t::perms));\n            }\n        }\n\n        auto new_driver_node = [&]() -> avtab_ptr_t {\n            auto node = insert_avtab_node(&key);\n            node->datum.xperms = auto_cast(calloc(1, sizeof(avtab_extended_perms_t)));\n            node->datum.xperms->specified = AVTAB_XPERMS_IOCTLDRIVER;\n            node->datum.xperms->driver = 0;\n            return node;\n        };\n\n        auto new_func_node = [&](uint8_t driver) -> avtab_ptr_t {\n            auto node = insert_avtab_node(&key);\n            node->datum.xperms = auto_cast(calloc(1, sizeof(avtab_extended_perms_t)));\n            node->datum.xperms->specified = AVTAB_XPERMS_IOCTLFUNCTION;\n            node->datum.xperms->driver = driver;\n            return node;\n        };\n\n        if (!p.reset) {\n            if (ioctl_driver(p.low) != ioctl_driver(p.high)) {\n                if (driver_node == nullptr) {\n                    driver_node = new_driver_node();\n                }\n                for (int i = ioctl_driver(p.low); i <= ioctl_driver(p.high); ++i) {\n                    xperm_set(i, driver_node->datum.xperms->perms);\n                }\n            } else {\n                uint8_t driver = ioctl_driver(p.low);\n                auto node = node_list[driver];\n                if (node == nullptr) {\n                    node = new_func_node(driver);\n                    node_list[driver] = node;\n                }\n                for (int i = ioctl_func(p.low); i <= ioctl_func(p.high); ++i) {\n                    xperm_set(i, node->datum.xperms->perms);\n                }\n            }\n        } else {\n            if (driver_node == nullptr) {\n                driver_node = new_driver_node();\n            }\n            // Fill the driver perms\n            memset(driver_node->datum.xperms->perms, ~0, sizeof(avtab_extended_perms_t::perms));\n\n            if (ioctl_driver(p.low) != ioctl_driver(p.high)) {\n                for (int i = ioctl_driver(p.low); i <= ioctl_driver(p.high); ++i) {\n                    xperm_clear(i, driver_node->datum.xperms->perms);\n                }\n            } else {\n                uint8_t driver = ioctl_driver(p.low);\n                auto node = node_list[driver];\n                if (node == nullptr) {\n                    node = new_func_node(driver);\n                    // Fill the func perms\n                    memset(node->datum.xperms->perms, ~0, sizeof(avtab_extended_perms_t::perms));\n                    node_list[driver] = node;\n                }\n                xperm_clear(driver, driver_node->datum.xperms->perms);\n                for (int i = ioctl_func(p.low); i <= ioctl_func(p.high); ++i) {\n                    xperm_clear(i, node->datum.xperms->perms);\n                }\n            }\n        }\n    }\n}\n\nbool sepol_impl::add_xperm_rule(Str s, Str t, Str c, const Xperm &p, int effect) {\n    type_datum_t *src = nullptr, *tgt = nullptr;\n    class_datum_t *cls = nullptr;\n\n    if (!s.empty()) {\n        src = hashtab_find(db->p_types.table, s);\n        if (src == nullptr) {\n            LOGW(\"source type %.*s does not exist\\n\", (int) s.size(), s.data());\n            return false;\n        }\n    }\n\n    if (!t.empty()) {\n        tgt = hashtab_find(db->p_types.table, t);\n        if (tgt == nullptr) {\n            LOGW(\"target type %.*s does not exist\\n\", (int) t.size(), t.data());\n            return false;\n        }\n    }\n\n    if (!c.empty()) {\n        cls = hashtab_find(db->p_classes.table, c);\n        if (cls == nullptr) {\n            LOGW(\"class %.*s does not exist\\n\", (int) c.size(), c.data());\n            return false;\n        }\n    }\n\n    add_xperm_rule(src, tgt, cls, p, effect);\n    return true;\n}\n\nbool sepol_impl::add_type_rule(Str s, Str t, Str c, Str d, int effect) {\n    type_datum_t *src, *tgt, *def;\n    class_datum_t *cls;\n\n    src = hashtab_find(db->p_types.table, s);\n    if (src == nullptr) {\n        LOGW(\"source type %.*s does not exist\\n\", (int) s.size(), s.data());\n        return false;\n    }\n    tgt = hashtab_find(db->p_types.table, t);\n    if (tgt == nullptr) {\n        LOGW(\"target type %.*s does not exist\\n\", (int) t.size(), t.data());\n        return false;\n    }\n    cls = hashtab_find(db->p_classes.table, c);\n    if (cls == nullptr) {\n        LOGW(\"class %.*s does not exist\\n\", (int) c.size(), c.data());\n        return false;\n    }\n    def = hashtab_find(db->p_types.table, d);\n    if (def == nullptr) {\n        LOGW(\"default type %.*s does not exist\\n\", (int) d.size(), d.data());\n        return false;\n    }\n\n    avtab_key_t key;\n    key.source_type = src->s.value;\n    key.target_type = tgt->s.value;\n    key.target_class = cls->s.value;\n    key.specified = effect;\n\n    avtab_ptr_t node = get_avtab_node(&key, nullptr);\n    node->datum.data = def->s.value;\n\n    return true;\n}\n\nbool sepol_impl::add_filename_trans(Str s, Str t, Str c, Str d, Str o) {\n    type_datum_t *src, *tgt, *def;\n    class_datum_t *cls;\n\n    src = hashtab_find(db->p_types.table, s);\n    if (src == nullptr) {\n        LOGW(\"source type %.*s does not exist\\n\", (int) s.size(), s.data());\n        return false;\n    }\n    tgt = hashtab_find(db->p_types.table, t);\n    if (tgt == nullptr) {\n        LOGW(\"target type %.*s does not exist\\n\", (int) t.size(), t.data());\n        return false;\n    }\n    cls = hashtab_find(db->p_classes.table, c);\n    if (cls == nullptr) {\n        LOGW(\"class %.*s does not exist\\n\", (int) c.size(), c.data());\n        return false;\n    }\n    def = hashtab_find(db->p_types.table, d);\n    if (def == nullptr) {\n        LOGW(\"default type %.*s does not exist\\n\", (int) d.size(), d.data());\n        return false;\n    }\n\n    array<char, 256> key_name{};\n    copy_str(key_name, o);\n    filename_trans_key_t key;\n    key.ttype = tgt->s.value;\n    key.tclass = cls->s.value;\n    key.name = key_name.data();\n\n    filename_trans_datum_t *trans = hashtab_find(db->filename_trans, (hashtab_key_t) &key);\n    filename_trans_datum_t *last = nullptr;\n    while (trans) {\n        if (ebitmap_get_bit(&trans->stypes, src->s.value - 1)) {\n            // Duplicate, overwrite existing data and return\n            trans->otype = def->s.value;\n            return true;\n        }\n        if (trans->otype == def->s.value)\n            break;\n        last = trans;\n        trans = trans->next;\n    }\n    if (trans == nullptr) {\n        trans = auto_cast(calloc(sizeof(*trans), 1));\n        ebitmap_init(&trans->stypes);\n        trans->otype = def->s.value;\n    }\n    if (last) {\n        last->next = trans;\n    } else {\n        filename_trans_key_t *new_key = auto_cast(malloc(sizeof(*new_key)));\n        memcpy(new_key, &key, sizeof(key));\n        new_key->name = strdup(key.name);\n        hashtab_insert(db->filename_trans, (hashtab_key_t) new_key, trans);\n    }\n\n    db->filename_trans_count++;\n    return ebitmap_set_bit(&trans->stypes, src->s.value - 1, 1) == 0;\n}\n\nbool sepol_impl::add_genfscon(Str fs_name, Str path, Str context) {\n    // First try to create context\n    context_struct_t *ctx;\n    if (context_from_string(nullptr, db, &ctx, context.data(), context.size())) {\n        LOGW(\"Failed to create context from string [%.*s]\\n\", (int) context.size(), context.data());\n        return false;\n    }\n\n    // Find genfs node\n    genfs_t *fs = list_find(db->genfs, [&](genfs_t *n) {\n        return str_eq(n->fstype, fs_name);\n    });\n    if (fs == nullptr) {\n        fs = auto_cast(calloc(sizeof(*fs), 1));\n        fs->fstype = dup_str(fs_name);\n        fs->next = db->genfs;\n        db->genfs = fs;\n    }\n\n    // Find context node\n    ocontext_t *o_ctx = list_find(fs->head, [&](ocontext_t *n) {\n        return str_eq(n->u.name, path);\n    });\n    if (o_ctx == nullptr) {\n        o_ctx = auto_cast(calloc(sizeof(*o_ctx), 1));\n        o_ctx->u.name = dup_str(path);\n        o_ctx->next = fs->head;\n        fs->head = o_ctx;\n    }\n    memset(o_ctx->context, 0, sizeof(o_ctx->context));\n    memcpy(&o_ctx->context[0], ctx, sizeof(*ctx));\n    free(ctx);\n\n    return true;\n}\n\nbool sepol_impl::add_type(Str type_name, uint32_t flavor) {\n    type_datum_t *type = hashtab_find(db->p_types.table, type_name);\n    if (type) {\n        LOGW(\"Type %.*s already exists\\n\", (int) type_name.size(), type_name.data());\n        return true;\n    }\n\n    type = auto_cast(malloc(sizeof(*type)));\n    type_datum_init(type);\n    type->primary = 1;\n    type->flavor = flavor;\n\n    uint32_t value = 0;\n    auto ty_name = dup_str(type_name);\n    if (symtab_insert(db, SYM_TYPES, ty_name, type, SCOPE_DECL, 1, &value)) {\n        free(ty_name);\n        return false;\n    }\n    type->s.value = value;\n    ebitmap_set_bit(&db->global->branch_list->declared.p_types_scope, value - 1, 1);\n\n    auto new_size = sizeof(ebitmap_t) * db->p_types.nprim;\n    db->type_attr_map = auto_cast(realloc(db->type_attr_map, new_size));\n    db->attr_type_map = auto_cast(realloc(db->attr_type_map, new_size));\n    ebitmap_init(&db->type_attr_map[value - 1]);\n    ebitmap_init(&db->attr_type_map[value - 1]);\n    ebitmap_set_bit(&db->type_attr_map[value - 1], value - 1, 1);\n\n    // Re-index stuffs\n    if (policydb_index_decls(nullptr, db) ||\n        policydb_index_classes(db) || policydb_index_others(nullptr, db, 0))\n        return false;\n\n    // Add the type to all roles\n    for (int i = 0; i < db->p_roles.nprim; ++i) {\n        // Not sure all those three calls are needed\n        ebitmap_set_bit(&db->role_val_to_struct[i]->types.negset, value - 1, 0);\n        ebitmap_set_bit(&db->role_val_to_struct[i]->types.types, value - 1, 1);\n        type_set_expand(&db->role_val_to_struct[i]->types, &db->role_val_to_struct[i]->cache, db, 0);\n    }\n\n    return true;\n}\n\nbool sepol_impl::set_type_state(Str type_name, bool permissive) {\n    type_datum_t *type;\n    if (type_name.empty()) {\n        hashtab_for_each(db->p_types.table, [&](hashtab_ptr_t node) {\n            type = auto_cast(node->datum);\n            if (ebitmap_set_bit(&db->permissive_map, type->s.value, permissive))\n                LOGW(\"Could not set bit in permissive map\\n\");\n        });\n    } else {\n        type = hashtab_find(db->p_types.table, type_name);\n        if (type == nullptr) {\n            LOGW(\"type %.*s does not exist\\n\", (int) type_name.size(), type_name.data());\n            return false;\n        }\n        if (ebitmap_set_bit(&db->permissive_map, type->s.value, permissive)) {\n            LOGW(\"Could not set bit in permissive map\\n\");\n            return false;\n        }\n    }\n    return true;\n}\n\nvoid sepol_impl::add_typeattribute(type_datum_t *type, type_datum_t *attr) {\n    ebitmap_set_bit(&db->type_attr_map[type->s.value - 1], attr->s.value - 1, 1);\n    ebitmap_set_bit(&db->attr_type_map[attr->s.value - 1], type->s.value - 1, 1);\n\n    hashtab_for_each(db->p_classes.table, [&](hashtab_ptr_t node){\n        auto cls = static_cast<class_datum_t *>(node->datum);\n        list_for_each(cls->constraints, [&](constraint_node_t *n) {\n            list_for_each(n->expr, [&](constraint_expr_t *e) {\n                if (e->expr_type == CEXPR_NAMES &&\n                    ebitmap_get_bit(&e->type_names->types, attr->s.value - 1)) {\n                    ebitmap_set_bit(&e->names, type->s.value - 1, 1);\n                }\n            });\n        });\n    });\n}\n\nbool sepol_impl::add_typeattribute(Str type, Str attr) {\n    type_datum_t *type_d = hashtab_find(db->p_types.table, type);\n    if (type_d == nullptr) {\n        LOGW(\"type %.*s does not exist\\n\", (int) type.size(), type.data());\n        return false;\n    } else if (type_d->flavor == TYPE_ATTRIB) {\n        LOGW(\"type %.*s is an attribute\\n\", (int) type.size(), type.data());\n        return false;\n    }\n\n    type_datum *attr_d = hashtab_find(db->p_types.table, attr);\n    if (attr_d == nullptr) {\n        LOGW(\"attribute %.*s does not exist\\n\", (int) attr.size(), attr.data());\n        return false;\n    } else if (attr_d->flavor != TYPE_ATTRIB) {\n        LOGW(\"type %.*s is not an attribute \\n\", (int) attr.size(), attr.data());\n        return false;\n    }\n\n    add_typeattribute(type_d, attr_d);\n    return true;\n}\n\nvoid SePolicy::strip_dontaudit() noexcept {\n    avtab_for_each(&impl->db->te_avtab, [this](avtab_ptr_t node) {\n        if (node->key.specified == AVTAB_AUDITDENY || node->key.specified == AVTAB_XPERMS_DONTAUDIT)\n            avtab_remove_node(&impl->db->te_avtab, node);\n    });\n}\n\nvoid SePolicy::print_rules() const noexcept {\n    hashtab_for_each(impl->db->p_types.table, [this](hashtab_ptr_t node) {\n        type_datum_t *type = auto_cast(node->datum);\n        if (type->flavor == TYPE_ATTRIB) {\n            impl->print_type(stdout, type);\n        }\n    });\n    hashtab_for_each(impl->db->p_types.table, [this](hashtab_ptr_t node) {\n        type_datum_t *type = auto_cast(node->datum);\n        if (type->flavor == TYPE_TYPE) {\n            impl->print_type(stdout, type);\n        }\n    });\n    avtab_for_each(&impl->db->te_avtab, [this](avtab_ptr_t node) {\n        impl->print_avtab(stdout, node);\n    });\n    hashtab_for_each(impl->db->filename_trans, [this](hashtab_ptr_t node) {\n        impl->print_filename_trans(stdout, node);\n    });\n    list_for_each(impl->db->genfs, [this](genfs_t *genfs) {\n        list_for_each(genfs->head, [&](ocontext *context) {\n            char *ctx = nullptr;\n            size_t len = 0;\n            if (context_to_string(nullptr, impl->db, &context->context[0], &ctx, &len) == 0) {\n                fprintf(stdout, \"genfscon %s %s %s\\n\", genfs->fstype, context->u.name, ctx);\n                free(ctx);\n            }\n        });\n    });\n}\n\nvoid sepol_impl::print_type(FILE *fp, type_datum_t *type) {\n    const char *name = db->p_type_val_to_name[type->s.value - 1];\n    if (name == nullptr)\n        return;\n    if (type->flavor == TYPE_ATTRIB) {\n        fprintf(fp, \"attribute %s\\n\", name);\n    } else if (type->flavor == TYPE_TYPE) {\n        bool first = true;\n        ebitmap_t *bitmap = &db->type_attr_map[type->s.value - 1];\n        for (uint32_t i = 0; i <= bitmap->highbit; ++i) {\n            if (ebitmap_get_bit(bitmap, i)) {\n                auto attr_type = db->type_val_to_struct[i];\n                if (attr_type->flavor == TYPE_ATTRIB) {\n                    if (const char *attr = db->p_type_val_to_name[i]) {\n                        if (first) {\n                            fprintf(fp, \"type %s {\", name);\n                            first = false;\n                        }\n                        fprintf(fp, \" %s\", attr);\n                    }\n                }\n            }\n        }\n        if (!first) {\n            fprintf(fp, \" }\\n\");\n        }\n    }\n    if (ebitmap_get_bit(&db->permissive_map, type->s.value)) {\n        fprintf(fp, \"permissive %s\\n\", name);\n    }\n}\n\nvoid sepol_impl::print_avtab(FILE *fp, avtab_ptr_t node) {\n    const char *src = db->p_type_val_to_name[node->key.source_type - 1];\n    const char *tgt = db->p_type_val_to_name[node->key.target_type - 1];\n    const char *cls = db->p_class_val_to_name[node->key.target_class - 1];\n    if (src == nullptr || tgt == nullptr || cls == nullptr)\n        return;\n\n    if (node->key.specified & AVTAB_AV) {\n        uint32_t data = node->datum.data;\n        const char *name;\n        switch (node->key.specified) {\n            case AVTAB_ALLOWED:\n                name = \"allow\";\n                break;\n            case AVTAB_AUDITALLOW:\n                name = \"auditallow\";\n                break;\n            case AVTAB_AUDITDENY:\n                name = \"dontaudit\";\n                // Invert the rules for dontaudit\n                data = ~data;\n                break;\n            default:\n                return;\n        }\n\n        class_datum_t *clz = db->class_val_to_struct[node->key.target_class - 1];\n        if (clz == nullptr)\n            return;\n\n        auto it = class_perm_names.find(cls);\n        if (it == class_perm_names.end()) {\n            it = class_perm_names.try_emplace(cls).first;\n            // Find all permission names and cache the value\n            hashtab_for_each(clz->permissions.table, [&](hashtab_ptr_t node) {\n                perm_datum_t *perm = auto_cast(node->datum);\n                it->second[perm->s.value - 1] = node->key;\n            });\n            if (clz->comdatum) {\n                hashtab_for_each(clz->comdatum->permissions.table, [&](hashtab_ptr_t node) {\n                    perm_datum_t *perm = auto_cast(node->datum);\n                    it->second[perm->s.value - 1] = node->key;\n                });\n            }\n        }\n\n        bool first = true;\n        for (int i = 0; i < 32; ++i) {\n            if (data & (1u << i)) {\n                if (const char *perm = it->second[i]) {\n                    if (first) {\n                        fprintf(fp, \"%s %s %s %s {\", name, src, tgt, cls);\n                        first = false;\n                    }\n                    fprintf(fp, \" %s\", perm);\n                }\n            }\n        }\n        if (!first) {\n            fprintf(fp, \" }\\n\");\n        }\n    } else if (node->key.specified & AVTAB_TYPE) {\n        const char *name;\n        switch (node->key.specified) {\n            case AVTAB_TRANSITION:\n                name = \"type_transition\";\n                break;\n            case AVTAB_MEMBER:\n                name = \"type_member\";\n                break;\n            case AVTAB_CHANGE:\n                name = \"type_change\";\n                break;\n            default:\n                return;\n        }\n        if (const char *def = db->p_type_val_to_name[node->datum.data - 1]) {\n            fprintf(fp, \"%s %s %s %s %s\\n\", name, src, tgt, cls, def);\n        }\n    } else if (node->key.specified & AVTAB_XPERMS) {\n        const char *name;\n        switch (node->key.specified) {\n            case AVTAB_XPERMS_ALLOWED:\n                name = \"allowxperm\";\n                break;\n            case AVTAB_XPERMS_AUDITALLOW:\n                name = \"auditallowxperm\";\n                break;\n            case AVTAB_XPERMS_DONTAUDIT:\n                name = \"dontauditxperm\";\n                break;\n            default:\n                return;\n        }\n        avtab_extended_perms_t *xperms = node->datum.xperms;\n        if (xperms == nullptr)\n            return;\n\n        vector<pair<uint8_t, uint8_t>> ranges;\n        {\n            int low = -1;\n            for (int i = 0; i < 256; ++i) {\n                if (xperm_test(i, xperms->perms)) {\n                    if (low < 0) {\n                        low = i;\n                    }\n                    if (i == 255) {\n                        ranges.emplace_back(low, 255);\n                    }\n                } else if (low >= 0) {\n                    ranges.emplace_back(low, i - 1);\n                    low = -1;\n                }\n            }\n        }\n\n        auto to_value = [&](uint8_t val) -> uint16_t {\n            if (xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) {\n                return (((uint16_t) xperms->driver) << 8) | val;\n            } else {\n                return ((uint16_t) val) << 8;\n            }\n        };\n\n        if (!ranges.empty()) {\n            fprintf(fp, \"%s %s %s %s ioctl {\", name, src, tgt, cls);\n            for (auto [l, h] : ranges) {\n                uint16_t low = to_value(l);\n                uint16_t high = to_value(h);\n                if (low == high) {\n                    fprintf(fp, \" 0x%04X\", low);\n                } else {\n                    fprintf(fp, \" 0x%04X-0x%04X\", low, high);\n                }\n            }\n            fprintf(fp, \" }\\n\");\n        }\n    }\n}\n\nvoid sepol_impl::print_filename_trans(FILE *fp, hashtab_ptr_t node) {\n    auto key = reinterpret_cast<filename_trans_key_t *>(node->key);\n    filename_trans_datum_t *trans = auto_cast(node->datum);\n\n    const char *tgt = db->p_type_val_to_name[key->ttype - 1];\n    const char *cls = db->p_class_val_to_name[key->tclass - 1];\n    const char *def = db->p_type_val_to_name[trans->otype - 1];\n    if (tgt == nullptr || cls == nullptr || def == nullptr || key->name == nullptr)\n        return;\n\n    for (uint32_t i = 0; i <= trans->stypes.highbit; ++i) {\n        if (ebitmap_get_bit(&trans->stypes, i)) {\n            if (const char *src = db->p_type_val_to_name[i]) {\n                fprintf(fp, \"type_transition %s %s %s %s %s\\n\", src, tgt, cls, def, key->name);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "native/src/sepolicy/statement.rs",
    "content": "use std::fmt::{Display, Formatter, Write};\nuse std::io::{BufRead, BufReader, Cursor};\nuse std::iter::Peekable;\nuse std::vec::IntoIter;\n\nuse crate::SePolicy;\nuse crate::ffi::Xperm;\nuse base::nix::fcntl::OFlag;\nuse base::{BufReadExt, LoggedResult, Utf8CStr, error, warn};\n\npub enum Token<'a> {\n    AL,\n    DN,\n    AA,\n    DA,\n    AX,\n    AY,\n    DX,\n    PM,\n    EF,\n    TA,\n    TY,\n    AT,\n    TT,\n    TC,\n    TM,\n    GF,\n    LB,\n    RB,\n    CM,\n    ST,\n    TL,\n    HP,\n    HX(u16),\n    ID(&'a str),\n}\n\ntype Tokens<'a> = Peekable<IntoIter<Token<'a>>>;\ntype ParseResult<'a, T> = Result<T, ParseError<'a>>;\n\nenum ParseError<'a> {\n    General,\n    AvtabAv(Token<'a>),\n    AvtabXperms(Token<'a>),\n    AvtabType(Token<'a>),\n    TypeState(Token<'a>),\n    TypeAttr,\n    TypeTrans,\n    NewType,\n    NewAttr,\n    GenfsCon,\n    ShowHelp,\n    UnknownAction(Token<'a>),\n}\n\nmacro_rules! throw {\n    () => {\n        Err(ParseError::General)?\n    };\n}\n\nfn parse_id<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, &'a str> {\n    match tokens.next() {\n        Some(Token::ID(name)) => Ok(name),\n        _ => throw!(),\n    }\n}\n\n// names ::= ID(n) { vec![n] };\n// names ::= names(mut v) ID(n) { v.push(n); v };\n// term ::= ID(n) { vec![n] }\n// term ::= LB names(n) RB { n };\nfn parse_term<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {\n    match tokens.next() {\n        Some(Token::ID(name)) => Ok(vec![name]),\n        Some(Token::LB) => {\n            let mut names = Vec::new();\n            loop {\n                match tokens.next() {\n                    Some(Token::ID(name)) => names.push(name),\n                    Some(Token::RB) => break,\n                    _ => throw!(),\n                }\n            }\n            Ok(names)\n        }\n        _ => throw!(),\n    }\n}\n\n// names ::= ST { vec![] }\n// names ::= ID(n) { vec![n] };\n// names ::= names(mut v) ID(n) { v.push(n); v };\n// names ::= names(n) ST { vec![] };\n// sterm ::= ST { vec![] }\n// sterm ::= ID(n) { vec![n] }\n// sterm ::= LB names(n) RB { n };\nfn parse_sterm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<&'a str>> {\n    match tokens.next() {\n        Some(Token::ID(name)) => Ok(vec![name]),\n        Some(Token::ST) => Ok(vec![]),\n        Some(Token::LB) => {\n            let mut names = Some(Vec::new());\n            loop {\n                match tokens.next() {\n                    Some(Token::ID(name)) => {\n                        if let Some(ref mut names) = names {\n                            names.push(name)\n                        }\n                    }\n                    Some(Token::ST) => names = None,\n                    Some(Token::RB) => break,\n                    _ => throw!(),\n                }\n            }\n            Ok(names.unwrap_or(vec![]))\n        }\n        _ => throw!(),\n    }\n}\n\n// xperm ::= HX(low) { Xperm{low, high: low, reset: false} };\n// xperm ::= HX(low) HP HX(high) { Xperm{low, high, reset: false} };\nfn parse_xperm<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Xperm> {\n    let low = match tokens.next() {\n        Some(Token::HX(low)) => low,\n        _ => throw!(),\n    };\n    let high = match tokens.peek() {\n        Some(Token::HP) => {\n            tokens.next();\n            match tokens.next() {\n                Some(Token::HX(high)) => high,\n                _ => throw!(),\n            }\n        }\n        _ => low,\n    };\n    Ok(Xperm {\n        low,\n        high,\n        reset: false,\n    })\n}\n\n// xperms ::= HX(low) { if low > 0 { vec![Xperm{low, high: low, reset: false}] } else { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: true}] }};\n// xperms ::= LB xperm_list(l) RB { l };\n// xperms ::= TL LB xperm_list(mut l) RB { l.iter_mut().for_each(|x| { x.reset = true; }); l };\n// xperms ::= ST { vec![Xperm{low: 0x0000, high: 0xFFFF, reset: false}] };\n//\n// xperm_list ::= xperm(p) { vec![p] }\n// xperm_list ::= xperm_list(mut l) xperm(p) { l.push(p); l }\nfn parse_xperms<'a>(tokens: &mut Tokens<'a>) -> ParseResult<'a, Vec<Xperm>> {\n    let mut xperms = Vec::new();\n    let reset = match tokens.peek() {\n        Some(Token::TL) => {\n            tokens.next();\n            if !matches!(tokens.peek(), Some(Token::LB)) {\n                throw!();\n            }\n            true\n        }\n        _ => false,\n    };\n    match tokens.next() {\n        Some(Token::LB) => {\n            // parse xperm_list\n            loop {\n                let mut xperm = parse_xperm(tokens)?;\n                xperm.reset = reset;\n                xperms.push(xperm);\n                if matches!(tokens.peek(), Some(Token::RB)) {\n                    tokens.next();\n                    break;\n                }\n            }\n        }\n        Some(Token::ST) => {\n            xperms.push(Xperm {\n                low: 0x0000,\n                high: 0xFFFF,\n                reset,\n            });\n        }\n        Some(Token::HX(low)) => {\n            if low > 0 {\n                xperms.push(Xperm {\n                    low,\n                    high: low,\n                    reset,\n                });\n            } else {\n                xperms.push(Xperm {\n                    low: 0x0000,\n                    high: 0xFFFF,\n                    reset,\n                });\n            }\n        }\n        _ => throw!(),\n    }\n    Ok(xperms)\n}\n\nfn match_string<'a>(tokens: &mut Tokens<'a>, pattern: &str) -> ParseResult<'a, ()> {\n    if let Some(Token::ID(s)) = tokens.next()\n        && s == pattern\n    {\n        return Ok(());\n    }\n    Err(ParseError::General)\n}\n\nfn extract_token<'a>(s: &'a str, tokens: &mut Vec<Token<'a>>) {\n    match s {\n        \"allow\" => tokens.push(Token::AL),\n        \"deny\" => tokens.push(Token::DN),\n        \"auditallow\" => tokens.push(Token::AA),\n        \"dontaudit\" => tokens.push(Token::DA),\n        \"allowxperm\" => tokens.push(Token::AX),\n        \"auditallowxperm\" => tokens.push(Token::AY),\n        \"dontauditxperm\" => tokens.push(Token::DX),\n        \"permissive\" => tokens.push(Token::PM),\n        \"enforce\" => tokens.push(Token::EF),\n        \"typeattribute\" => tokens.push(Token::TA),\n        \"type\" => tokens.push(Token::TY),\n        \"attribute\" => tokens.push(Token::AT),\n        \"type_transition\" => tokens.push(Token::TT),\n        \"type_change\" => tokens.push(Token::TC),\n        \"type_member\" => tokens.push(Token::TM),\n        \"genfscon\" => tokens.push(Token::GF),\n        \"*\" => tokens.push(Token::ST),\n        \"\" => {}\n        _ => {\n            if let Some(idx) = s.find('{') {\n                let (a, b) = s.split_at(idx);\n                extract_token(a, tokens);\n                tokens.push(Token::LB);\n                extract_token(&b[1..], tokens);\n            } else if let Some(idx) = s.find('}') {\n                let (a, b) = s.split_at(idx);\n                extract_token(a, tokens);\n                tokens.push(Token::RB);\n                extract_token(&b[1..], tokens);\n            } else if let Some(idx) = s.find(',') {\n                let (a, b) = s.split_at(idx);\n                extract_token(a, tokens);\n                tokens.push(Token::CM);\n                extract_token(&b[1..], tokens);\n            } else if let Some(idx) = s.find('-') {\n                let (a, b) = s.split_at(idx);\n                extract_token(a, tokens);\n                tokens.push(Token::HP);\n                extract_token(&b[1..], tokens);\n            } else if let Some(s) = s.strip_prefix('~') {\n                tokens.push(Token::TL);\n                extract_token(s, tokens);\n            } else if let Some(s) = s.strip_prefix(\"0x\") {\n                tokens.push(Token::HX(s.parse().unwrap_or(0)));\n            } else {\n                tokens.push(Token::ID(s));\n            }\n        }\n    }\n}\n\nfn tokenize_statement(statement: &str) -> Vec<Token<'_>> {\n    let mut tokens = Vec::new();\n    for s in statement.split_whitespace() {\n        extract_token(s, &mut tokens);\n    }\n    tokens\n}\n\nimpl SePolicy {\n    pub fn load_rules(&mut self, rules: &str) {\n        let mut cursor = Cursor::new(rules.as_bytes());\n        self.load_rules_from_reader(&mut cursor);\n    }\n\n    pub fn load_rule_file(&mut self, filename: &Utf8CStr) {\n        let result = || -> LoggedResult<()> {\n            let file = filename.open(OFlag::O_RDONLY | OFlag::O_CLOEXEC)?;\n            let mut reader = BufReader::new(file);\n            self.load_rules_from_reader(&mut reader);\n            Ok(())\n        }();\n        result.ok();\n    }\n\n    fn load_rules_from_reader<T: BufRead>(&mut self, reader: &mut T) {\n        reader.for_each_line(|line| {\n            self.parse_statement(line);\n            true\n        });\n    }\n\n    fn parse_statement(&mut self, statement: &str) {\n        let statement = statement.trim();\n        if statement.is_empty() || statement.starts_with('#') {\n            return;\n        }\n        let mut tokens = tokenize_statement(statement).into_iter().peekable();\n        let result = self.exec_statement(&mut tokens);\n        if let Err(e) = result {\n            warn!(\"Syntax error in: \\\"{}\\\"\", statement);\n            error!(\"Hint: {}\", e);\n        }\n    }\n\n    // statement ::= AL sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.allow(s, t, c, p); };\n    // statement ::= DN sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.deny(s, t, c, p); };\n    // statement ::= AA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.auditallow(s, t, c, p); };\n    // statement ::= DA sterm(s) sterm(t) sterm(c) sterm(p) { sepolicy.dontaudit(s, t, c, p); };\n    // statement ::= AX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.allowxperm(s, t, c, p); };\n    // statement ::= AY sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.auditallowxperm(s, t, c, p); };\n    // statement ::= DX sterm(s) sterm(t) sterm(c) ID(i) xperms(p) { sepolicy.dontauditxperm(s, t, c, p); };\n    // statement ::= PM sterm(t) { sepolicy.permissive(t); };\n    // statement ::= EF sterm(t) { sepolicy.enforce(t); };\n    // statement ::= TA term(t) term(a) { sepolicy.typeattribute(t, a); };\n    // statement ::= TY ID(t) { sepolicy.type_(t, vec![]);};\n    // statement ::= TY ID(t) term(a) { sepolicy.type_(t, a);};\n    // statement ::= AT ID(t) { sepolicy.attribute(t); };\n    // statement ::= TT ID(s) ID(t) ID(c) ID(d) { sepolicy.type_transition(s, t, c, d, vec![]); };\n    // statement ::= TT ID(s) ID(t) ID(c) ID(d) ID(o) { sepolicy.type_transition(s, t, c, d, vec![o]); };\n    // statement ::= TC ID(s) ID(t) ID(c) ID(d) { sepolicy.type_change(s, t, c, d); };\n    // statement ::= TM ID(s) ID(t) ID(c) ID(d) { sepolicy.type_member(s, t, c, d);};\n    // statement ::= GF ID(s) ID(t) ID(c) { sepolicy.genfscon(s, t, c); };\n    fn exec_statement<'a>(&mut self, tokens: &mut Tokens<'a>) -> ParseResult<'a, ()> {\n        let action = match tokens.next() {\n            Some(token) => token,\n            _ => Err(ParseError::ShowHelp)?,\n        };\n        let check_additional_args = |tokens: &mut Tokens<'a>| {\n            if tokens.peek().is_none() {\n                Ok(())\n            } else {\n                Err(ParseError::General)\n            }\n        };\n        match action {\n            Token::AL | Token::DN | Token::AA | Token::DA => {\n                let result = || -> ParseResult<()> {\n                    let s = parse_sterm(tokens)?;\n                    let t = parse_sterm(tokens)?;\n                    let c = parse_sterm(tokens)?;\n                    let p = parse_sterm(tokens)?;\n                    check_additional_args(tokens)?;\n                    match action {\n                        Token::AL => self.allow(s, t, c, p),\n                        Token::DN => self.deny(s, t, c, p),\n                        Token::AA => self.auditallow(s, t, c, p),\n                        Token::DA => self.dontaudit(s, t, c, p),\n                        _ => unreachable!(),\n                    }\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::AvtabAv(action))?\n                }\n            }\n            Token::AX | Token::AY | Token::DX => {\n                let result = || -> ParseResult<()> {\n                    let s = parse_sterm(tokens)?;\n                    let t = parse_sterm(tokens)?;\n                    let c = parse_sterm(tokens)?;\n                    match_string(tokens, \"ioctl\")?;\n                    let p = parse_xperms(tokens)?;\n                    check_additional_args(tokens)?;\n                    match action {\n                        Token::AX => self.allowxperm(s, t, c, p),\n                        Token::AY => self.auditallowxperm(s, t, c, p),\n                        Token::DX => self.dontauditxperm(s, t, c, p),\n                        _ => unreachable!(),\n                    }\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::AvtabXperms(action))?\n                }\n            }\n            Token::PM | Token::EF => {\n                let result = || -> ParseResult<()> {\n                    let t = parse_sterm(tokens)?;\n                    check_additional_args(tokens)?;\n                    match action {\n                        Token::PM => self.permissive(t),\n                        Token::EF => self.enforce(t),\n                        _ => unreachable!(),\n                    }\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::TypeState(action))?\n                }\n            }\n            Token::TA => {\n                let result = || -> ParseResult<()> {\n                    let t = parse_term(tokens)?;\n                    let a = parse_term(tokens)?;\n                    check_additional_args(tokens)?;\n                    self.typeattribute(t, a);\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::TypeAttr)?\n                }\n            }\n            Token::TY => {\n                let result = || -> ParseResult<()> {\n                    let t = parse_id(tokens)?;\n                    let a = if tokens.peek().is_none() {\n                        vec![]\n                    } else {\n                        parse_term(tokens)?\n                    };\n                    check_additional_args(tokens)?;\n                    self.type_(t, a);\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::NewType)?\n                }\n            }\n            Token::AT => {\n                let result = || -> ParseResult<()> {\n                    let t = parse_id(tokens)?;\n                    check_additional_args(tokens)?;\n                    self.attribute(t);\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::NewAttr)?\n                }\n            }\n            Token::TC | Token::TM => {\n                let result = || -> ParseResult<()> {\n                    let s = parse_id(tokens)?;\n                    let t = parse_id(tokens)?;\n                    let c = parse_id(tokens)?;\n                    let d = parse_id(tokens)?;\n                    check_additional_args(tokens)?;\n                    match action {\n                        Token::TC => self.type_change(s, t, c, d),\n                        Token::TM => self.type_member(s, t, c, d),\n                        _ => unreachable!(),\n                    }\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::AvtabType(action))?\n                }\n            }\n            Token::TT => {\n                let result = || -> ParseResult<()> {\n                    let s = parse_id(tokens)?;\n                    let t = parse_id(tokens)?;\n                    let c = parse_id(tokens)?;\n                    let d = parse_id(tokens)?;\n                    let o = if tokens.peek().is_none() {\n                        \"\"\n                    } else {\n                        parse_id(tokens)?\n                    };\n                    check_additional_args(tokens)?;\n                    self.type_transition(s, t, c, d, o);\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::TypeTrans)?\n                }\n            }\n            Token::GF => {\n                let result = || -> ParseResult<()> {\n                    let s = parse_id(tokens)?;\n                    let t = parse_id(tokens)?;\n                    let c = parse_id(tokens)?;\n                    check_additional_args(tokens)?;\n                    self.genfscon(s, t, c);\n                    Ok(())\n                }();\n                if result.is_err() {\n                    Err(ParseError::GenfsCon)?\n                }\n            }\n            _ => Err(ParseError::UnknownAction(action))?,\n        }\n        Ok(())\n    }\n}\n\n// Token to string\nimpl Display for Token<'_> {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            Token::AL => f.write_str(\"allow\"),\n            Token::DN => f.write_str(\"deny\"),\n            Token::AA => f.write_str(\"auditallow\"),\n            Token::DA => f.write_str(\"dontaudit\"),\n            Token::AX => f.write_str(\"allowxperm\"),\n            Token::AY => f.write_str(\"auditallowxperm\"),\n            Token::DX => f.write_str(\"dontauditxperm\"),\n            Token::PM => f.write_str(\"permissive\"),\n            Token::EF => f.write_str(\"enforce\"),\n            Token::TA => f.write_str(\"typeattribute\"),\n            Token::TY => f.write_str(\"type\"),\n            Token::AT => f.write_str(\"attribute\"),\n            Token::TT => f.write_str(\"type_transition\"),\n            Token::TC => f.write_str(\"type_change\"),\n            Token::TM => f.write_str(\"type_member\"),\n            Token::GF => f.write_str(\"genfscon\"),\n            Token::LB => f.write_char('{'),\n            Token::RB => f.write_char('}'),\n            Token::CM => f.write_char(','),\n            Token::ST => f.write_char('*'),\n            Token::TL => f.write_char('~'),\n            Token::HP => f.write_char('-'),\n            Token::HX(n) => f.write_fmt(format_args!(\"{n:06X}\")),\n            Token::ID(s) => f.write_str(s),\n        }\n    }\n}\n\nimpl Display for ParseError<'_> {\n    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {\n        match self {\n            ParseError::General => Ok(()),\n            ParseError::ShowHelp => format_statement_help(f),\n            ParseError::AvtabAv(action) => {\n                write!(f, \"{action} *source_type *target_type *class *perm_set\")\n            }\n            ParseError::AvtabXperms(action) => {\n                write!(\n                    f,\n                    \"{action} *source_type *target_type *class operation xperm_set\"\n                )\n            }\n            ParseError::AvtabType(action) => {\n                write!(f, \"{action} source_type target_type class default_type\")\n            }\n            ParseError::TypeState(action) => {\n                write!(f, \"{action} *type\")\n            }\n            ParseError::TypeAttr => f.write_str(\"typeattribute ^type ^attribute\"),\n            ParseError::TypeTrans => f.write_str(\n                \"type_transition source_type target_type class default_type (object_name)\",\n            ),\n            ParseError::NewType => f.write_str(\"type type_name ^(attribute)\"),\n            ParseError::NewAttr => f.write_str(\"attribute attribute_name\"),\n            ParseError::GenfsCon => f.write_str(\"genfscon fs_name partial_path fs_context\"),\n            ParseError::UnknownAction(action) => write!(f, \"Unknown action: \\\"{action}\\\"\"),\n        }\n    }\n}\n\npub(crate) fn format_statement_help(f: &mut dyn Write) -> std::fmt::Result {\n    write!(\n        f,\n        r#\"** Policy statements:\n\nOne policy statement should be treated as a single parameter;\nthis means each policy statement should be enclosed in quotes.\nMultiple policy statements can be provided in a single command.\n\nStatements has a format of \"<rule_name> [args...]\".\nArguments labeled with (^) can accept one or more entries.\nMultiple entries consist of a space separated list enclosed in braces ({{}}).\nArguments labeled with (*) are the same as (^), but additionally\nsupport the match-all operator (*).\n\nExample: \"allow {{ s1 s2 }} {{ t1 t2 }} class *\"\nWill be expanded to:\n\nallow s1 t1 class {{ all-permissions-of-class }}\nallow s1 t2 class {{ all-permissions-of-class }}\nallow s2 t1 class {{ all-permissions-of-class }}\nallow s2 t2 class {{ all-permissions-of-class }}\n\n** Extended permissions:\n\nThe only supported operation for extended permissions right now is 'ioctl'.\nxperm_set is one or multiple hexadecimal numeric values ranging from 0x0000 to 0xFFFF.\nMultiple values consist of a space separated list enclosed in braces ({{}}).\nUse the complement operator (~) to specify all permissions except those explicitly listed.\nUse the range operator (-) to specify all permissions within the low – high range.\nUse the match all operator (*) to match all ioctl commands (0x0000-0xFFFF).\nThe special value 0 is used to clear all rules.\n\nSome examples:\nallowxperm source target class ioctl 0x8910\nallowxperm source target class ioctl {{ 0x8910-0x8926 0x892A-0x8935 }}\nallowxperm source target class ioctl ~{{ 0x8910 0x892A }}\nallowxperm source target class ioctl *\n\n** Supported policy statements:\n\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n\"#,\n        ParseError::AvtabAv(Token::AL),\n        ParseError::AvtabAv(Token::DN),\n        ParseError::AvtabAv(Token::AA),\n        ParseError::AvtabAv(Token::DA),\n        ParseError::AvtabXperms(Token::AX),\n        ParseError::AvtabXperms(Token::AY),\n        ParseError::AvtabXperms(Token::DX),\n        ParseError::TypeState(Token::PM),\n        ParseError::TypeState(Token::EF),\n        ParseError::TypeAttr,\n        ParseError::NewType,\n        ParseError::NewAttr,\n        ParseError::TypeTrans,\n        ParseError::AvtabType(Token::TC),\n        ParseError::AvtabType(Token::TM),\n        ParseError::GenfsCon\n    )\n}\n"
  },
  {
    "path": "scripts/addon.d.sh",
    "content": "#!/sbin/sh\n# ADDOND_VERSION=2\n########################################################\n#\n# Magisk Survival Script for ROMs with addon.d support\n# by topjohnwu and osm0sis\n#\n########################################################\n\ntrampoline() {\n  mount /data 2>/dev/null\n  if [ -f $MAGISKBIN/addon.d.sh ]; then\n    exec sh $MAGISKBIN/addon.d.sh \"$@\"\n    exit $?\n  elif [ \"$1\" = post-restore ]; then\n    BOOTMODE=false\n    ps | grep zygote | grep -v grep >/dev/null && BOOTMODE=true\n    $BOOTMODE || ps -A 2>/dev/null | grep zygote | grep -v grep >/dev/null && BOOTMODE=true\n\n    if ! $BOOTMODE; then\n      # update-binary|updater <RECOVERY_API_VERSION> <OUTFD> <ZIPFILE>\n      OUTFD=$(ps | grep -v 'grep' | grep -oE 'update(.*) 3 [0-9]+' | cut -d\" \" -f3)\n      [ -z $OUTFD ] && OUTFD=$(ps -Af | grep -v 'grep' | grep -oE 'update(.*) 3 [0-9]+' | cut -d\" \" -f3)\n      # update_engine_sideload --payload=file://<ZIPFILE> --offset=<OFFSET> --headers=<HEADERS> --status_fd=<OUTFD>\n      [ -z $OUTFD ] && OUTFD=$(ps | grep -v 'grep' | grep -oE 'status_fd=[0-9]+' | cut -d= -f2)\n      [ -z $OUTFD ] && OUTFD=$(ps -Af | grep -v 'grep' | grep -oE 'status_fd=[0-9]+' | cut -d= -f2)\n    fi\n    ui_print() {\n      if $BOOTMODE; then\n        log -t Magisk -- \"$1\"\n      else\n        echo -e \"ui_print $1\\nui_print\" >> /proc/self/fd/$OUTFD\n      fi\n    }\n\n    ui_print \"***********************\"\n    ui_print \" Magisk addon.d failed\"\n    ui_print \"***********************\"\n    ui_print \"! Cannot find Magisk binaries - was data wiped or not decrypted?\"\n    ui_print \"! Reflash OTA from decrypted recovery or reflash Magisk\"\n  fi\n  exit 1\n}\n\n# Always use the script in /data\nMAGISKBIN=/data/adb/magisk\n[ \"$0\" = $MAGISKBIN/addon.d.sh ] || trampoline \"$@\"\n\nV1_FUNCS=/tmp/backuptool.functions\nV2_FUNCS=/postinstall/tmp/backuptool.functions\n\nif [ -f $V1_FUNCS ]; then\n  . $V1_FUNCS\n  backuptool_ab=false\nelif [ -f $V2_FUNCS ]; then\n  . $V2_FUNCS\nelse\n  return 1\nfi\n\ninitialize() {\n  # Load utility functions\n  . $MAGISKBIN/util_functions.sh\n\n  if $BOOTMODE; then\n    # Override ui_print when booted\n    ui_print() { log -t Magisk -- \"$1\"; }\n  fi\n  OUTFD=\n  setup_flashable\n}\n\nmain() {\n  if ! $backuptool_ab; then\n    # Restore PREINITDEVICE from previous A-only partition\n    if [ -f config.orig ]; then\n      PREINITDEVICE=$(grep_prop PREINITDEVICE config.orig)\n      rm config.orig\n    fi\n\n    # Wait for post addon.d-v1 processes to finish\n    sleep 5\n  fi\n\n  # Ensure we aren't in /tmp/addon.d anymore (since it's been deleted by addon.d)\n  mkdir -p $TMPDIR\n  cd $TMPDIR\n\n  if echo $MAGISK_VER | grep -q '\\.'; then\n    PRETTY_VER=$MAGISK_VER\n  else\n    PRETTY_VER=\"$MAGISK_VER($MAGISK_VER_CODE)\"\n  fi\n  print_title \"Magisk $PRETTY_VER addon.d\"\n\n  mount_partitions\n  check_data\n  get_flags\n\n  if $backuptool_ab; then\n    # Swap the slot for addon.d-v2\n    if [ ! -z $SLOT ]; then\n      case $SLOT in\n        _a) SLOT=_b;;\n        _b) SLOT=_a;;\n      esac\n    fi\n  fi\n\n  find_boot_image\n  [ -z $BOOTIMAGE ] && abort \"! Unable to detect target image\"\n  ui_print \"- Target image: $BOOTIMAGE\"\n\n  api_level_arch_detect\n  ui_print \"- Device platform: $ABI\"\n\n  remove_system_su\n  install_magisk\n\n  # Cleanups\n  cd /\n  $BOOTMODE || recovery_cleanup\n  rm -rf $TMPDIR\n\n  ui_print \"- Done\"\n  exit 0\n}\n\ncase \"$1\" in\n  backup)\n    # Stub\n  ;;\n  restore)\n    # Stub\n  ;;\n  pre-backup)\n    # Back up PREINITDEVICE from existing partition before OTA on A-only devices\n    if ! $backuptool_ab; then\n      initialize\n      # Suppress ui_print for this stage\n      ui_print() { return; }\n      get_flags\n      find_boot_image\n      $MAGISKBIN/magiskboot unpack \"$BOOTIMAGE\"\n      $MAGISKBIN/magiskboot cpio ramdisk.cpio \"extract .backup/.magisk config.orig\"\n      $MAGISKBIN/magiskboot cleanup\n    fi\n  ;;\n  post-backup)\n    # Stub\n  ;;\n  pre-restore)\n    # Stub\n  ;;\n  post-restore)\n    initialize\n    if $backuptool_ab; then\n      su=sh\n      $BOOTMODE && su=su\n      exec $su -c \"sh $0 addond-v2\"\n    else\n      # Run in background, hack for addon.d-v1\n      (main) &\n    fi\n  ;;\n  addond-v2)\n    initialize\n    main\n  ;;\nesac\n"
  },
  {
    "path": "scripts/app_functions.sh",
    "content": "##################################\n# Magisk app internal scripts\n##################################\n\n# $1 = delay\n# $2 = command\nrun_delay() {\n  (sleep $1; $2)&\n}\n\n# $1 = version string\n# $2 = version code\nenv_check() {\n  for file in busybox magiskboot magiskinit util_functions.sh boot_patch.sh; do\n    [ -f \"$MAGISKBIN/$file\" ] || return 1\n  done\n  if [ \"$2\" -ge 25000 ]; then\n    [ -f \"$MAGISKBIN/magiskpolicy\" ] || return 1\n  fi\n  if [ \"$2\" -ge 25210 ]; then\n    [ -b \"$MAGISKTMP/.magisk/device/preinit\" ] || [ -b \"$MAGISKTMP/.magisk/block/preinit\" ] || return 2\n  fi\n  grep -xqF \"MAGISK_VER='$1'\" \"$MAGISKBIN/util_functions.sh\" || return 3\n  grep -xqF \"MAGISK_VER_CODE=$2\" \"$MAGISKBIN/util_functions.sh\" || return 3\n  return 0\n}\n\n# $1 = dir to copy\n# $2 = destination (optional)\ncp_readlink() {\n  if [ -z $2 ]; then\n    cd $1\n  else\n    cp -af $1/. $2\n    cd $2\n  fi\n  for file in *; do\n    if [ -L $file ]; then\n      local full=$(readlink -f $file)\n      rm $file\n      cp -af $full $file\n    fi\n  done\n  chmod -R 755 .\n  cd /\n}\n\n# $1 = install dir\nfix_env() {\n  # Cleanup and make dirs\n  rm -rf $MAGISKBIN/*\n  mkdir -p $MAGISKBIN 2>/dev/null\n  chmod 700 /data/adb\n  cp_readlink $1 $MAGISKBIN\n  rm -rf $1\n  chown -R 0:0 $MAGISKBIN\n}\n\n# $1 = install dir\n# $2 = boot partition\ndirect_install() {\n  echo \"- Flashing new boot image\"\n  flash_image $1/new-boot.img $2\n  case $? in\n    1)\n      echo \"! Insufficient partition size\"\n      return 1\n      ;;\n    2)\n      echo \"! $2 is read only\"\n      return 2\n      ;;\n  esac\n\n  rm -f $1/new-boot.img\n  fix_env $1\n  run_migrations\n\n  return 0\n}\n\n# $1 = uninstaller zip\nrun_uninstaller() {\n  rm -rf /dev/tmp\n  mkdir -p /dev/tmp/install\n  unzip -o \"$1\" \"assets/*\" \"lib/*\" -d /dev/tmp/install\n  INSTALLER=/dev/tmp/install sh /dev/tmp/install/assets/uninstaller.sh dummy 1 \"$1\"\n}\n\n# $1 = boot partition\nrestore_imgs() {\n  local SHA1=$(grep_prop SHA1 $MAGISKTMP/.magisk/config)\n  local BACKUPDIR=/data/magisk_backup_$SHA1\n  [ -d $BACKUPDIR ] || return 1\n  [ -f $BACKUPDIR/boot.img.gz ] || return 1\n  flash_image $BACKUPDIR/boot.img.gz $1\n}\n\n# $1 = path to bootctl executable\npost_ota() {\n  cd /data/adb\n  cp -f $1 bootctl\n  rm -f $1\n  chmod 755 bootctl\n  if ! ./bootctl hal-info; then\n    rm -f bootctl\n    return\n  fi\n  SLOT_NUM=0\n  [ $(./bootctl get-current-slot) -eq 0 ] && SLOT_NUM=1\n  ./bootctl set-active-boot-slot $SLOT_NUM\n  cat << EOF > post-fs-data.d/post_ota.sh\n/data/adb/bootctl mark-boot-successful\nrm -f /data/adb/bootctl\nrm -f /data/adb/post-fs-data.d/post_ota.sh\nEOF\n  chmod 755 post-fs-data.d/post_ota.sh\n  cd /\n}\n\n# $1 = APK\n# $2 = package name\nadb_pm_install() {\n  local tmp=/data/local/tmp/temp.apk\n  cp -f \"$1\" $tmp\n  chmod 644 $tmp\n  su 2000 -c pm install -g $tmp || pm install -g $tmp || su 1000 -c pm install -g $tmp\n  local res=$?\n  rm -f $tmp\n  if [ $res = 0 ]; then\n    appops set \"$2\" REQUEST_INSTALL_PACKAGES allow\n  fi\n  return $res\n}\n\ncheck_boot_ramdisk() {\n  # Create boolean ISAB\n  ISAB=true\n  [ -z $SLOT ] && ISAB=false\n\n  # If we are A/B, then we must have ramdisk\n  $ISAB && return 0\n\n  # If we are using legacy SAR, but not A/B, assume we do not have ramdisk\n  if $LEGACYSAR; then\n    # Override recovery mode to true\n    RECOVERYMODE=true\n    return 1\n  fi\n\n  return 0\n}\n\ncheck_encryption() {\n  if $ISENCRYPTED; then\n    if [ $SDK_INT -lt 24 ]; then\n      CRYPTOTYPE=\"block\"\n    else\n      # First see what the system tells us\n      CRYPTOTYPE=$(getprop ro.crypto.type)\n      if [ -z $CRYPTOTYPE ]; then\n        # If not mounting through device mapper, we are FBE\n        if grep ' /data ' /proc/mounts | grep -qv 'dm-'; then\n          CRYPTOTYPE=\"file\"\n        else\n          # We are either FDE or metadata encryption (which is also FBE)\n          CRYPTOTYPE=\"block\"\n          grep -q ' /metadata ' /proc/mounts && CRYPTOTYPE=\"file\"\n        fi\n      fi\n    fi\n  else\n    CRYPTOTYPE=\"N/A\"\n  fi\n}\n\nprintvar() {\n  eval echo $1=\\$$1\n}\n\nrun_action() {\n  local MODID=\"$1\"\n  cd \"/data/adb/modules/$MODID\"\n  sh ./action.sh\n  local RES=$?\n  cd /\n  return $RES\n}\n\n##########################\n# Non-root util_functions\n##########################\n\nmount_partitions() {\n  [ \"$(getprop ro.build.ab_update)\" = \"true\" ] && SLOT=$(getprop ro.boot.slot_suffix)\n  # Check whether non rootfs root dir exists\n  SYSTEM_AS_ROOT=false\n  grep ' / ' /proc/mounts | grep -qv 'rootfs' && SYSTEM_AS_ROOT=true\n\n  LEGACYSAR=false\n  grep ' / ' /proc/mounts | grep -q '/dev/root' && LEGACYSAR=true\n}\n\nget_flags() {\n  KEEPVERITY=$SYSTEM_AS_ROOT\n  ISENCRYPTED=false\n  [ \"$(getprop ro.crypto.state)\" = \"encrypted\" ] && ISENCRYPTED=true\n  KEEPFORCEENCRYPT=$ISENCRYPTED\n  if [ -n \"$(getprop ro.boot.vbmeta.device)\" -o -n \"$(getprop ro.boot.vbmeta.size)\" ]; then\n    PATCHVBMETAFLAG=false\n  elif getprop ro.product.ab_ota_partitions | grep -wq vbmeta; then\n    PATCHVBMETAFLAG=false\n  else\n    PATCHVBMETAFLAG=true\n  fi\n  [ -z $RECOVERYMODE ] && RECOVERYMODE=false\n  [ -z $VENDORBOOT ] && VENDORBOOT=false\n}\n\nrun_migrations() { return; }\n\ngrep_prop() { return; }\n\n#############\n# Initialize\n#############\n\napp_init() {\n  mount_partitions >/dev/null\n  RAMDISKEXIST=false\n  check_boot_ramdisk && RAMDISKEXIST=true\n  get_flags >/dev/null\n  run_migrations >/dev/null\n  check_encryption\n\n  # Dump variables\n  printvar SLOT\n  printvar SYSTEM_AS_ROOT\n  printvar RAMDISKEXIST\n  printvar ISAB\n  printvar CRYPTOTYPE\n  printvar PATCHVBMETAFLAG\n  printvar LEGACYSAR\n  printvar RECOVERYMODE\n  printvar KEEPVERITY\n  printvar KEEPFORCEENCRYPT\n  printvar VENDORBOOT\n}\n\nexport BOOTMODE=true\n"
  },
  {
    "path": "scripts/avd.sh",
    "content": "#!/usr/bin/env bash\n\nset -e\nshopt -s extglob\n. scripts/test_common.sh\n\nemu_args_base=\"-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect -read-only -no-snapshot -cores $core_count\"\nlog_args=\"-show-kernel -logcat '' -logcat-output logcat.log\"\nemu_args=\nemu_pid=\n\natd_min_api=30\natd_max_api=36\nhuge_ram_min_api=26\n\ncase $(uname -m) in\n  'arm64'|'aarch64')\n    if [ -n \"$FORCE_32_BIT\" ]; then\n      echo \"! ARM32 is not supported\"\n      exit 1\n    fi\n    arch=arm64-v8a\n    ;;\n  *)\n    if [ -n \"$FORCE_32_BIT\" ]; then\n      arch=x86\n    else\n      arch=x86_64\n    fi\n\n    ;;\nesac\n\ncleanup() {\n  rm -f magisk-*.img\n  \"$avd\" delete avd -n test\n}\n\ntest_error() {\n  trap - EXIT\n  print_error \"! An error occurred\"\n  pkill -INT -P $$\n  wait\n  cleanup\n  exit 1\n}\n\nwait_for_boot() {\n  set -e\n  adb wait-for-device\n  while true; do\n    local result=\"$(adb exec-out getprop sys.boot_completed)\"\n    if [ $? -ne 0 ]; then\n      exit 1\n    elif [ \"$result\" = \"1\" ]; then\n      break\n    fi\n    sleep 2\n  done\n}\n\nwait_emu() {\n  local which_pid\n\n  timeout $boot_timeout bash -c wait_for_boot &\n  local wait_pid=$!\n\n  # Handle the case when emulator dies earlier than timeout\n  wait -p which_pid -n $emu_pid $wait_pid\n  [ $which_pid -eq $wait_pid ]\n}\n\ndump_vars() {\n  local val\n  for name in $@; do\n    eval val=\\$$name\n    echo local $name=\\\"$val\\\"\\;\n  done\n}\n\nparse_args() {\n  set +x\n  local return_vals=\"$1\"\n  shift\n\n  local ver=\n  local type=\n\n  while getopts \":v:t:l\" opt; do\n    case $opt in\n      v )\n        ver=\"$OPTARG\"\n        ;;\n      t )\n        type=\"$OPTARG\"\n        ;;\n      l )\n        export AVD_TEST_LOG=1\n        ;;\n      \\? )\n        echo \"Error: Invalid option: -$OPTARG\" 1>&2\n        exit 1\n        ;;\n      : )\n        # Missing a required argument is fine as we perform validations later\n        ;;\n    esac\n  done\n\n  if [ -z $ver ]; then\n    print_error \"! No system image version specified\"\n    exit 1\n  fi\n\n  # Determine API level\n  local api\n  case $ver in\n    +([0-9\\.])) api=$ver ;;\n    TiramisuPrivacySandbox) api=33 ;;\n    UpsideDownCakePrivacySandbox) api=34 ;;\n    VanillaIceCream) api=35 ;;\n    Baklava) api=36 ;;\n    CinnamonBun) api=37 ;;\n    *CANARY) api=10000 ;;\n    *)\n      print_error \"! Unknown system image version '$ver'\"\n      exit 1\n      ;;\n  esac\n\n  # Determine default image type\n  if [ -z $type ]; then\n    if [ $(bc <<< \"$api >= $atd_min_api && $api <= $atd_max_api\") = 1 ]; then\n      # Use the lightweight ATD images if possible\n      type='aosp_atd'\n    elif [ $(bc <<< \"$api > $atd_max_api\") = 1 ]; then\n      # Preview/beta release, no AOSP version available\n      type='google_apis'\n    else\n      type='default'\n    fi\n  fi\n\n  # Old Linux kernels will not boot with memory larger than 3GB\n  local memory\n  if [ $(bc <<< \"$api < $huge_ram_min_api\") = 1 ]; then\n    memory=3072\n  else\n    memory=8192\n  fi\n\n  emu_args=\"$emu_args_base -memory $memory\"\n\n  # System image variable and paths\n  local avd_pkg=\"system-images;android-$ver;$type;$arch\"\n  local sys_img_dir=\"$ANDROID_HOME/system-images/android-$ver/$type/$arch\"\n  local ramdisk=\"$sys_img_dir/ramdisk.img\"\n\n  # Dump global variables\n  echo emu_args=\\\"$emu_args\\\"\n  echo OPTIND=$OPTIND\n\n  # Dump local variables\n  dump_vars $return_vals\n}\n\ndl_emu() {\n  local avd_pkg=$1\n  yes | \"$sdk\" --licenses > /dev/null 2>&1\n  \"$sdk\" --channel=3 platform-tools emulator $avd_pkg\n}\n\nsetup_emu() {\n  local avd_pkg=$1\n  local ver=$2\n  dl_emu $avd_pkg\n  echo no | \"$avd\" create avd -f -n test -k $avd_pkg\n\n  # avdmanager is outdated, it might not set the proper target\n  local ini=$ANDROID_AVD_HOME/test.ini\n  sed \"s:^target\\s*=.*:target=android-$ver:g\" $ini > $ini.new\n  mv $ini.new $ini\n}\n\ntest_emu() {\n  local apk=$1\n  local image=$2\n\n  local magisk_args=\"-ramdisk $image -feature -SystemAsRoot\"\n\n  if [ -n \"$AVD_TEST_LOG\" ]; then\n    rm -f logcat.log\n    \"$emu\" @test $emu_args $log_args $magisk_args > kernel.log 2>&1 &\n  else\n    \"$emu\" @test $emu_args $magisk_args > /dev/null 2>&1 &\n  fi\n\n  emu_pid=$!\n  wait_emu\n\n  run_setup $apk\n\n  adb reboot\n  wait_emu\n\n  run_tests\n\n  kill -INT $emu_pid\n  wait $emu_pid\n}\n\ntest_main() {\n  local vars\n  vars=$(parse_args \"ver avd_pkg ramdisk\" \"$@\")\n  eval \"$vars\"\n\n  # Shift off the options we just parsed so that \"$@\" only contains the remaining positional arguments\n  shift $((OPTIND - 1))\n  local apks=($(print_apks \"$@\"))\n\n  # Specify an explicit port so that tests can run with other emulators running at the same time\n  local emu_port=5682\n  emu_args=\"$emu_args -port $emu_port\"\n  export ANDROID_SERIAL=\"emulator-$emu_port\"\n\n  setup_emu \"$avd_pkg\" $ver\n\n  # Restart ADB daemon just in case\n  adb kill-server\n  adb start-server\n\n  # Launch stock emulator\n  print_title \"* Launching $avd_pkg\"\n  \"$emu\" @test $emu_args >/dev/null 2>&1 &\n  emu_pid=$!\n  wait_emu\n\n  # Patch images\n  local images=()\n  for apk in \"${apks[@]}\"; do\n    images+=(\"magisk-$(basename $apk .apk).img\")\n    ./build.py -v avd_patch --apk \"$apk\" \"$ramdisk\" \"${images[-1]}\"\n  done\n\n  kill -INT $emu_pid\n  wait $emu_pid\n\n  for i in \"${!apks[@]}\"; do\n    print_title \"* Testing $avd_pkg ($(basename ${apks[i]}))\"\n    test_emu ${apks[i]} ${images[i]}\n  done\n\n  cleanup\n}\n\nrun_main() {\n  local vars\n  vars=$(parse_args \"ver avd_pkg\" \"$@\")\n  eval \"$vars\"\n\n  setup_emu \"$avd_pkg\" $ver\n  print_title \"* Launching $avd_pkg\"\n  \"$emu\" @test $emu_args 2>/dev/null\n  cleanup\n}\n\ndl_main() {\n  local vars\n  vars=$(parse_args \"avd_pkg\" \"$@\")\n  eval \"$vars\"\n\n  print_title \"* Downloading $avd_pkg\"\n  dl_emu \"$avd_pkg\"\n}\n\ncase \"$1\" in\n  test )\n    shift\n    trap test_error EXIT\n    export -f wait_for_boot\n    set -x\n    test_main \"$@\"\n    ;;\n  run )\n    shift\n    trap cleanup EXIT\n    run_main \"$@\"\n    ;;\n  dl )\n    shift\n    dl_main \"$@\"\n    ;;\n  * )\n    print_error \"Unknown argument '$1'\"\n    exit 1\n    ;;\nesac\n\n# Exit normally, don't run through cleanup again\ntrap - EXIT\n"
  },
  {
    "path": "scripts/boot_patch.sh",
    "content": "#!/system/bin/sh\n#######################################################################################\n# Magisk Boot Image Patcher\n#######################################################################################\n#\n# Usage: boot_patch.sh <bootimage>\n#\n# The following environment variables can configure the installation:\n# KEEPVERITY, KEEPFORCEENCRYPT, PATCHVBMETAFLAG, RECOVERYMODE, LEGACYSAR\n#\n# This script should be placed in a directory with the following files:\n#\n# File name          Type      Description\n#\n# boot_patch.sh      script    A script to patch boot image for Magisk.\n#                  (this file) The script will use files in its same\n#                              directory to complete the patching process.\n# util_functions.sh  script    A script which hosts all functions required\n#                              for this script to work properly.\n# magiskinit         binary    The binary to replace /init.\n# magisk             binary    The magisk binary.\n# magiskboot         binary    A tool to manipulate boot images.\n# init-ld            binary    The library that will be LD_PRELOAD of /init\n# stub.apk           binary    The stub Magisk app to embed into ramdisk.\n# chromeos           folder    This folder includes the utility and keys to sign\n#                  (optional)  chromeos boot images. Only used for Pixel C.\n#\n#######################################################################################\n\n############\n# Functions\n############\n\n# Pure bash dirname implementation\ngetdir() {\n  case \"$1\" in\n    */*)\n      dir=${1%/*}\n      if [ -z $dir ]; then\n        echo \"/\"\n      else\n        echo $dir\n      fi\n    ;;\n    *) echo \".\" ;;\n  esac\n}\n\n#################\n# Initialization\n#################\n\nif [ -z $SOURCEDMODE ]; then\n  # Switch to the location of the script file\n  cd \"$(getdir \"${BASH_SOURCE:-$0}\")\"\n  # Load utility functions\n  . ./util_functions.sh\n  # Check if 64-bit\n  api_level_arch_detect\nfi\n\nBOOTIMAGE=\"$1\"\n[ -e \"$BOOTIMAGE\" ] || abort \"$BOOTIMAGE does not exist!\"\n\n# Dump image for MTD/NAND character device boot partitions\nif [ -c \"$BOOTIMAGE\" ]; then\n  nanddump -f boot.img \"$BOOTIMAGE\"\n  BOOTNAND=\"$BOOTIMAGE\"\n  BOOTIMAGE=boot.img\nfi\n\n# Flags\n[ -z $KEEPVERITY ] && KEEPVERITY=false\n[ -z $KEEPFORCEENCRYPT ] && KEEPFORCEENCRYPT=false\n[ -z $PATCHVBMETAFLAG ] && PATCHVBMETAFLAG=false\n[ -z $RECOVERYMODE ] && RECOVERYMODE=false\n[ -z $LEGACYSAR ] && LEGACYSAR=false\nexport KEEPVERITY\nexport KEEPFORCEENCRYPT\nexport PATCHVBMETAFLAG\n\nchmod -R 755 .\n\n#########\n# Unpack\n#########\n\nCHROMEOS=false\nVENDORBOOT=false\n\nui_print \"- Unpacking boot image\"\n./magiskboot unpack \"$BOOTIMAGE\"\n\ncase $? in\n  0 ) ;;\n  2 )\n    ui_print \"- ChromeOS boot image detected\"\n    CHROMEOS=true\n    ;;\n  3 )\n    ui_print \"- Vendor boot image detected\"\n    VENDORBOOT=true\n    ;;\n  * )\n    abort \"! Unable to unpack boot image\"\n    ;;\nesac\n\n#################\n# Ramdisk Checks\n#################\n\nunset RAMDISK\nfor path in ramdisk.cpio vendor_ramdisk/init_boot.cpio vendor_ramdisk/ramdisk.cpio; do\n  if [ -e $path ]; then\n    RAMDISK=$path\n    break\n  fi\ndone\n\nui_print \"- Checking ramdisk status\"\nif [ -n \"$RAMDISK\" ]; then\n  ./magiskboot cpio $RAMDISK test\n  STATUS=$?\n  SKIP_BACKUP=\"\"\nelse\n  # No ramdisk found, create one from scratch\n  RAMDISK=ramdisk.cpio\n  # Could be stock A only legacy SAR, or some Android 13 GKIs\n  STATUS=0\n  SKIP_BACKUP=\"#\"\nfi\n\ncase $STATUS in\n  0 )\n    # Stock boot\n    ui_print \"- Stock boot image detected\"\n    SHA1=$(./magiskboot sha1 \"$BOOTIMAGE\" 2>/dev/null)\n    cat $BOOTIMAGE > stock_boot.img\n    cp -af $RAMDISK ramdisk.cpio.orig 2>/dev/null\n    ;;\n  1 )\n    # Magisk patched\n    ui_print \"- Magisk patched boot image detected\"\n    ./magiskboot cpio $RAMDISK \\\n    \"extract .backup/.magisk config.orig\" \\\n    \"restore\"\n    cp -af $RAMDISK ramdisk.cpio.orig\n    rm -f stock_boot.img\n    ;;\n  2 )\n    # Unsupported\n    ui_print \"! Boot image patched by unsupported programs\"\n    abort \"! Please restore back to stock boot image\"\n    ;;\nesac\n\nif [ -f config.orig ]; then\n  # Read existing configs\n  chmod 0644 config.orig\n  SHA1=$(grep_prop SHA1 config.orig)\n  if ! $BOOTMODE; then\n    # Do not inherit config if not in recovery\n    PREINITDEVICE=$(grep_prop PREINITDEVICE config.orig)\n  fi\n  rm config.orig\nfi\n\n##################\n# Ramdisk Patches\n##################\n\nui_print \"- Patching ramdisk\"\n\n$BOOTMODE && [ -z \"$PREINITDEVICE\" ] && PREINITDEVICE=$(./magisk --preinit-device)\n\n# Compress to save precious ramdisk space\n./magiskboot compress=xz magisk magisk.xz\n./magiskboot compress=xz stub.apk stub.xz\n./magiskboot compress=xz init-ld init-ld.xz\n\necho \"KEEPVERITY=$KEEPVERITY\" > config\necho \"KEEPFORCEENCRYPT=$KEEPFORCEENCRYPT\" >> config\necho \"RECOVERYMODE=$RECOVERYMODE\" >> config\necho \"VENDORBOOT=$VENDORBOOT\" >> config\nif [ -n \"$PREINITDEVICE\" ]; then\n  ui_print \"- Pre-init storage partition: $PREINITDEVICE\"\n  echo \"PREINITDEVICE=$PREINITDEVICE\" >> config\nfi\n[ -n \"$SHA1\" ] && echo \"SHA1=$SHA1\" >> config\n\n./magiskboot cpio $RAMDISK \\\n\"add 0750 init magiskinit\" \\\n\"mkdir 0750 overlay.d\" \\\n\"mkdir 0750 overlay.d/sbin\" \\\n\"add 0644 overlay.d/sbin/magisk.xz magisk.xz\" \\\n\"add 0644 overlay.d/sbin/stub.xz stub.xz\" \\\n\"add 0644 overlay.d/sbin/init-ld.xz init-ld.xz\" \\\n\"patch\" \\\n\"$SKIP_BACKUP backup ramdisk.cpio.orig\" \\\n\"mkdir 000 .backup\" \\\n\"add 000 .backup/.magisk config\" \\\n|| abort \"! Unable to patch ramdisk\"\n\nrm -f ramdisk.cpio.orig config *.xz\n\n#################\n# Binary Patches\n#################\n\nfor dt in dtb kernel_dtb extra; do\n  if [ -f $dt ]; then\n    if ! ./magiskboot dtb $dt test; then\n      ui_print \"! Boot image $dt was patched by old (unsupported) Magisk\"\n      abort \"! Please try again with *unpatched* boot image\"\n    fi\n    if ./magiskboot dtb $dt patch; then\n      ui_print \"- Patch fstab in boot image $dt\"\n    fi\n  fi\ndone\n\nif [ -f kernel ]; then\n  PATCHEDKERNEL=false\n  # Remove Samsung RKP\n  ./magiskboot hexpatch kernel \\\n  49010054011440B93FA00F71E9000054010840B93FA00F7189000054001840B91FA00F7188010054 \\\n  A1020054011440B93FA00F7140020054010840B93FA00F71E0010054001840B91FA00F7181010054 \\\n  && PATCHEDKERNEL=true\n\n  # Remove Samsung defex\n  # Before: [mov w2, #-221]   (-__NR_execve)\n  # After:  [mov w2, #-32768]\n  ./magiskboot hexpatch kernel 821B8012 E2FF8F12 && PATCHEDKERNEL=true\n\n  # Disable Samsung PROCA\n  # proca_config -> proca_magisk\n  ./magiskboot hexpatch kernel \\\n  70726F63615F636F6E66696700 \\\n  70726F63615F6D616769736B00 \\\n  && PATCHEDKERNEL=true\n\n  # Force kernel to load rootfs for legacy SAR devices\n  # skip_initramfs -> want_initramfs\n  $LEGACYSAR && ./magiskboot hexpatch kernel \\\n  736B69705F696E697472616D667300 \\\n  77616E745F696E697472616D667300 \\\n  && PATCHEDKERNEL=true\n\n  # If the kernel doesn't need to be patched at all,\n  # keep raw kernel to avoid bootloops on some weird devices\n  $PATCHEDKERNEL || rm -f kernel\nfi\n\n#################\n# Repack & Flash\n#################\n\nui_print \"- Repacking boot image\"\n./magiskboot repack \"$BOOTIMAGE\" || abort \"! Unable to repack boot image\"\n\n# Sign chromeos boot\n$CHROMEOS && sign_chromeos\n\n# Restore the original boot partition path\n[ -e \"$BOOTNAND\" ] && BOOTIMAGE=\"$BOOTNAND\"\n\n# Reset any error code\ntrue\n"
  },
  {
    "path": "scripts/cuttlefish.sh",
    "content": "#!/usr/bin/env bash\n\nset -xe\n. scripts/test_common.sh\n\ncvd_args=\"-daemon -enable_sandbox=false -memory_mb=8192 -report_anonymous_usage_stats=n -cpus=$core_count\"\n\ncleanup() {\n  print_error \"! An error occurred\"\n  run_cvd_bin stop_cvd || true\n  rm -f magisk-*.img\n}\n\nrun_cvd_bin() {\n  local exe=$1\n  shift\n  HOME=$CF_HOME $CF_HOME/bin/$exe \"$@\"\n}\n\nsetup_env() {\n  curl -LO https://github.com/topjohnwu/magisk-files/releases/download/files/cuttlefish-base_1.2.0_amd64.deb\n  sudo apt-get update\n  sudo dpkg -i ./cuttlefish-base_*_*64.deb || sudo apt-get install -f\n  rm cuttlefish-base_*_*64.deb\n  sudo usermod -aG kvm,cvdnetwork,render $USER\n  yes | \"$sdk\" --licenses > /dev/null\n  \"$sdk\" --channel=3 platform-tools\n  adb kill-server\n  adb start-server\n}\n\ndownload_cf() {\n  local branch=$1\n  local device=$2\n\n  if [ -z $branch ]; then\n    branch='aosp-android-latest-release'\n  fi\n  if [ -z $device ]; then\n    device='aosp_cf_x86_64_only_phone'\n  fi\n  local target=\"${device}-userdebug\"\n\n  local build_id=$(curl -sL https://ci.android.com/builds/branches/${branch}/status.json | \\\n    jq -r \".targets[] | select(.name == \\\"$target\\\") | .last_known_good_build\")\n  local sys_img_url=\"https://ci.android.com/builds/submitted/${build_id}/${target}/latest/raw/${device}-img-${build_id}.zip\"\n  local host_pkg_url=\"https://ci.android.com/builds/submitted/${build_id}/${target}/latest/raw/cvd-host_package.tar.gz\"\n\n  print_title \"* Download $target ($build_id) images\"\n  curl -L $sys_img_url -o aosp_cf_phone-img.zip\n  curl -LO $host_pkg_url\n  rm -rf $CF_HOME\n  mkdir -p $CF_HOME\n  tar xvf cvd-host_package.tar.gz -C $CF_HOME\n  unzip aosp_cf_phone-img.zip -d $CF_HOME\n  rm -f cvd-host_package.tar.gz aosp_cf_phone-img.zip\n}\n\ntest_cf() {\n  local apk=$1\n  local image=$2\n\n  run_cvd_bin stop_cvd || true\n\n  local magisk_args=\"-init_boot_image=$image\"\n\n  timeout $boot_timeout bash -c \"run_cvd_bin launch_cvd $cvd_args $magisk_args -resume=false\"\n  adb wait-for-device\n  run_setup $apk\n\n  adb reboot\n  sleep 5\n  run_cvd_bin stop_cvd || true\n\n  timeout $boot_timeout bash -c \"run_cvd_bin launch_cvd $cvd_args $magisk_args\"\n  adb wait-for-device\n  run_tests\n}\n\ntest_main() {\n  # Launch stock cuttlefish\n  run_cvd_bin launch_cvd $cvd_args -resume=false\n  adb wait-for-device\n\n  # Patch images\n  local apks=($(print_apks))\n  local images=()\n  for apk in \"${apks[@]}\"; do\n    images+=(\"magisk-$(basename $apk .apk).img\")\n    ./build.py -v avd_patch --apk \"$apk\" \"$CF_HOME/init_boot.img\" \"${images[-1]}\"\n  done\n\n  for i in \"${!apks[@]}\"; do\n    print_title \"* Testing $(basename ${apks[i]})\"\n    test_cf ${apks[i]} ${images[i]}\n  done\n\n  # Cleanup\n  run_cvd_bin stop_cvd || true\n  rm -f magisk-*.img\n}\n\nif [ -z $CF_HOME ]; then\n  print_error \"! Environment variable CF_HOME is required\"\n  exit 1\nfi\n\ncase \"$1\" in\n  setup )\n    setup_env\n    ;;\n  download )\n    download_cf $2 $3\n    ;;\n  test )\n    trap cleanup EXIT\n    export -f run_cvd_bin\n    test_main\n    trap - EXIT\n    ;;\n  * )\n    exit 1\n    ;;\nesac\n"
  },
  {
    "path": "scripts/flash_script.sh",
    "content": "#MAGISK\n############################################\n# Magisk Flash Script (updater-script)\n############################################\n\n##############\n# Preparation\n##############\n\n# Default permissions\numask 022\n\nOUTFD=$2\nCOMMONDIR=$INSTALLER/assets\nCHROMEDIR=$INSTALLER/assets/chromeos\n\nif [ ! -f $COMMONDIR/util_functions.sh ]; then\n  echo \"! Unable to extract zip file!\"\n  exit 1\nfi\n\n# Load utility functions\n. $COMMONDIR/util_functions.sh\n\nsetup_flashable\n\n############\n# Detection\n############\n\nif echo $MAGISK_VER | grep -q '\\.'; then\n  PRETTY_VER=$MAGISK_VER\nelse\n  PRETTY_VER=\"$MAGISK_VER($MAGISK_VER_CODE)\"\nfi\nprint_title \"Magisk $PRETTY_VER Installer\"\n\nis_mounted /data || mount /data || is_mounted /cache || mount /cache\nmount_partitions\ncheck_data\nget_flags\nfind_boot_image\n\n[ -z $BOOTIMAGE ] && abort \"! Unable to detect target image\"\nui_print \"- Target image: $BOOTIMAGE\"\n\n# Detect version and architecture\napi_level_arch_detect\n\n[ $API -lt 23 ] && abort \"! Magisk only support Android 6.0 and above\"\n\nui_print \"- Device platform: $ABI\"\n\nBINDIR=$INSTALLER/lib/$ABI\ncd $BINDIR\nfor file in lib*.so; do mv \"$file\" \"${file:3:${#file}-6}\"; done\ncd /\ncp -af $INSTALLER/lib/$ABI32/libmagisk.so $BINDIR/magisk32 2>/dev/null\n\n# Check if system root is installed and remove\n$BOOTMODE || remove_system_su\n\n##############\n# Environment\n##############\n\nui_print \"- Constructing environment\"\n\n# Copy required files\nrm -rf $MAGISKBIN 2>/dev/null\nmkdir -p $MAGISKBIN 2>/dev/null\ncp -af $BINDIR/. $COMMONDIR/. $BBBIN $MAGISKBIN\n\n# Remove files only used by the Magisk app\nrm -f $MAGISKBIN/bootctl $MAGISKBIN/main.jar \\\n  $MAGISKBIN/module_installer.sh $MAGISKBIN/uninstaller.sh\n\nchmod -R 755 $MAGISKBIN\n\n# addon.d\nif [ -d /system/addon.d ]; then\n  ui_print \"- Adding addon.d survival script\"\n  blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null\n  mount -o rw,remount /system || mount -o rw,remount /\n  ADDOND=/system/addon.d/99-magisk.sh\n  cp -af $COMMONDIR/addon.d.sh $ADDOND\n  chmod 755 $ADDOND\nfi\n\n##################\n# Image Patching\n##################\n\ninstall_magisk\n\n# Cleanups\n$BOOTMODE || recovery_cleanup\nrm -rf $TMPDIR\n\nui_print \"- Done\"\nexit 0\n"
  },
  {
    "path": "scripts/host_patch.sh",
    "content": "#####################################################################\n#   AVD MagiskInit Setup\n#####################################################################\n#\n# Support API level: 23 - 36\n#\n# With an emulator booted and accessible via ADB, usage:\n# ./build.py avd_patch path/to/booted/avd-image/ramdisk.img\n#\n# The purpose of this script is to patch AVD ramdisk.img and do a\n# full integration test of magiskinit under several circumstances.\n# After patching ramdisk.img, close the emulator, then select\n# \"Cold Boot Now\" in AVD Manager to force a full reboot.\n#\n#####################################################################\n# AVD Init Configurations:\n#\n# rootfs w/o early mount: API 23 - 25\n# rootfs with early mount: API 26 - 27\n# Legacy system-as-root: API 28\n# 2 stage init: API 29 - 35\n#####################################################################\n\nif [ ! -f /system/build.prop ]; then\n  # Running on PC\n  echo 'Please run `./build.py avd_patch` instead of directly executing the script!'\n  exit 1\nfi\n\ncd /data/local/tmp\nchmod 755 busybox\n\nif [ -z \"$FIRST_STAGE\" ]; then\n  export FIRST_STAGE=1\n  export ASH_STANDALONE=1\n  # Re-exec script with busybox\n  exec ./busybox sh $0 \"$@\"\nfi\n\nTARGET_FILE=\"$1\"\nOUTPUT_FILE=\"$1.magisk\"\n\nif echo \"$TARGET_FILE\" | grep -q 'ramdisk'; then\n  IS_RAMDISK=true\nelse\n  IS_RAMDISK=false\nfi\n\n# Extract files from APK\nunzip -oj magisk.apk 'assets/util_functions.sh' 'assets/stub.apk'\n. ./util_functions.sh\n\napi_level_arch_detect\n\nunzip -oj magisk.apk \"lib/$ABI/*\" -x \"lib/$ABI/libbusybox.so\"\nfor file in lib*.so; do\n  chmod 755 $file\n  mv \"$file\" \"${file:3:${#file}-6}\"\ndone\n\nif $IS_RAMDISK; then\n  ./magiskboot decompress \"$TARGET_FILE\" ramdisk.cpio\nelse\n  ./magiskboot unpack \"$TARGET_FILE\"\nfi\ncp ramdisk.cpio ramdisk.cpio.orig\n\nexport KEEPVERITY=true\nexport KEEPFORCEENCRYPT=true\n\necho \"KEEPVERITY=$KEEPVERITY\" > config\necho \"KEEPFORCEENCRYPT=$KEEPFORCEENCRYPT\" >> config\necho \"PREINITDEVICE=$(./magisk --preinit-device)\" >> config\n# For API 28, we also manually disable SystemAsRoot\n# Explicitly override skip_initramfs by setting RECOVERYMODE=true\n[ $API = \"28\" ] && echo 'RECOVERYMODE=true' >> config\ncat config\n\n./magiskboot compress=xz magisk magisk.xz\n./magiskboot compress=xz stub.apk stub.xz\n./magiskboot compress=xz init-ld init-ld.xz\n\n./magiskboot cpio ramdisk.cpio \\\n\"add 0750 init magiskinit\" \\\n\"mkdir 0750 overlay.d\" \\\n\"mkdir 0750 overlay.d/sbin\" \\\n\"add 0644 overlay.d/sbin/magisk.xz magisk.xz\" \\\n\"add 0644 overlay.d/sbin/stub.xz stub.xz\" \\\n\"add 0644 overlay.d/sbin/init-ld.xz init-ld.xz\" \\\n\"patch\" \\\n\"backup ramdisk.cpio.orig\" \\\n\"mkdir 000 .backup\" \\\n\"add 000 .backup/.magisk config\"\n\nrm -f ramdisk.cpio.orig config *.xz\nif $IS_RAMDISK; then\n  ./magiskboot compress=gzip ramdisk.cpio \"$OUTPUT_FILE\"\nelse\n  ./magiskboot repack \"$TARGET_FILE\" \"$OUTPUT_FILE\"\n  ./magiskboot cleanup\nfi\n"
  },
  {
    "path": "scripts/live_setup.sh",
    "content": "#####################################################################\n#   AVD Magisk Setup\n#####################################################################\n#\n# Support API level: 23 - 36\n#\n# For developing Magisk, just use:\n# ./build.py emulator\n#\n# This script will stop zygote, simulate the Magisk start up process\n# that would've happened before zygote was started, and finally\n# restart zygote. This is useful for setting up the emulator for\n# developing Magisk, testing modules, and developing root apps using\n# the official Android emulator (AVD) instead of a real device.\n#\n# This only covers the \"core\" features of Magisk. For testing\n# magiskinit, please checkout avd_patch.sh.\n#\n#####################################################################\n\nmount_tmpfs() {\n  # If a file name 'magisk' is in current directory, mount will fail\n  mv magisk magisk.tmp\n  mount -t tmpfs -o 'mode=0755' magisk $1\n  mv magisk.tmp magisk\n}\n\nmount_sbin() {\n  mount_tmpfs /sbin\n  chcon u:object_r:rootfs:s0 /sbin\n}\n\nif [ ! -f /system/build.prop ]; then\n  # Running on PC\n  echo 'Please run `./build.py emulator` instead of directly executing the script!'\n  exit 1\nfi\n\ncd /data/local/tmp\nchmod 755 busybox\n\nif [ -z \"$FIRST_STAGE\" ]; then\n  export FIRST_STAGE=1\n  export ASH_STANDALONE=1\n  if [ $(./busybox id -u) -ne 0 ]; then\n    # Re-exec script with root\n    exec /system/xbin/su 0 /data/local/tmp/busybox sh $0\n  else\n    # Re-exec script with busybox\n    exec ./busybox sh $0\n  fi\nfi\n\npm install -r -g $(pwd)/magisk.apk\n\n# Extract files from APK\nunzip -oj magisk.apk 'assets/util_functions.sh' 'assets/stub.apk'\n. ./util_functions.sh\n\napi_level_arch_detect\n\nunzip -oj magisk.apk \"lib/$ABI/*\" -x \"lib/$ABI/libbusybox.so\"\nfor file in lib*.so; do\n  chmod 755 $file\n  mv \"$file\" \"${file:3:${#file}-6}\"\ndone\n\nif $IS64BIT && [ -e \"/system/bin/linker\" ]; then\n  unzip -oj magisk.apk \"lib/$ABI32/libmagisk.so\"\n  mv libmagisk.so magisk32\n  chmod 755 magisk32\nfi\n\n# Stop zygote (and previous setup if exists)\nmagisk --stop 2>/dev/null\nstop\nif [ -d /debug_ramdisk ]; then\n  umount -l /debug_ramdisk 2>/dev/null\nfi\n\n# Make sure boot completed props are not set to 1\nsetprop sys.boot_completed 0\n\n# Mount /cache if not already mounted\nif ! grep -q ' /cache ' /proc/mounts; then\n  mount -t tmpfs -o 'mode=0755' tmpfs /cache\nfi\n\nMAGISKTMP=/sbin\n\n# Setup bin overlay\nif mount | grep -q rootfs; then\n  # Legacy rootfs\n  mount -o rw,remount /\n  rm -rf /root\n  mkdir /root /sbin 2>/dev/null\n  chmod 750 /root /sbin\n  ln /sbin/* /root\n  mount -o ro,remount /\n  mount_sbin\n  ln -s /root/* /sbin\nelif [ -e /sbin ]; then\n  # Legacy SAR\n  mount_sbin\n  mkdir -p /dev/sysroot\n  block=$(mount | grep ' / ' | awk '{ print $1 }')\n  [ $block = \"/dev/root\" ] && block=/dev/block/vda1\n  mount -o ro $block /dev/sysroot\n  for file in /dev/sysroot/sbin/*; do\n    [ ! -e $file ] && break\n    if [ -L $file ]; then\n      cp -af $file /sbin\n    else\n      sfile=/sbin/$(basename $file)\n      touch $sfile\n      mount -o bind $file $sfile\n    fi\n  done\n  umount -l /dev/sysroot\n  rm -rf /dev/sysroot\nelse\n  # Android Q+ without sbin\n  MAGISKTMP=/debug_ramdisk\n  mount_tmpfs /debug_ramdisk\nfi\n\n# Magisk stuff\nmkdir -p $MAGISKBIN 2>/dev/null\nunzip -oj magisk.apk 'assets/*.sh' -d $MAGISKBIN\nmkdir /data/adb/modules 2>/dev/null\nmkdir /data/adb/post-fs-data.d 2>/dev/null\nmkdir /data/adb/service.d 2>/dev/null\n\nfor file in magisk magisk32 magiskpolicy stub.apk; do\n  chmod 755 ./$file\n  cp -af ./$file $MAGISKTMP/$file\n  cp -af ./$file $MAGISKBIN/$file\ndone\ncp -af ./magiskboot $MAGISKBIN/magiskboot\ncp -af ./magiskinit $MAGISKBIN/magiskinit\ncp -af ./busybox $MAGISKBIN/busybox\n\nln -s ./magisk $MAGISKTMP/su\nln -s ./magisk $MAGISKTMP/resetprop\nln -s ./magiskpolicy $MAGISKTMP/supolicy\n\nmkdir -p $MAGISKTMP/.magisk/device\nmkdir -p $MAGISKTMP/.magisk/worker\nmount_tmpfs $MAGISKTMP/.magisk/worker\nmount --make-private $MAGISKTMP/.magisk/worker\ntouch $MAGISKTMP/.magisk/config\n\nexport MAGISKTMP\nMAKEDEV=1 $MAGISKTMP/magisk --preinit-device 2>&1\n\nRULESCMD=\"\"\nrule=\"$MAGISKTMP/.magisk/preinit/sepolicy.rule\"\n[ -f \"$rule\" ] && RULESCMD=\"--apply $rule\"\n\n# SELinux stuffs\nif [ -d /sys/fs/selinux ]; then\n  if [ -f /vendor/etc/selinux/precompiled_sepolicy ]; then\n    ./magiskpolicy --load /vendor/etc/selinux/precompiled_sepolicy --live --magisk $RULESCMD 2>&1\n  elif [ -f /sepolicy ]; then\n    ./magiskpolicy --load /sepolicy --live --magisk $RULESCMD 2>&1\n  else\n    ./magiskpolicy --live --magisk $RULESCMD 2>&1\n  fi\nfi\n\n# Boot up\n$MAGISKTMP/magisk --post-fs-data\nstart\n$MAGISKTMP/magisk --service\n# Make sure reset nb prop after zygote starts\nsleep 2\n$MAGISKTMP/magisk --boot-complete\n"
  },
  {
    "path": "scripts/module_installer.sh",
    "content": "#!/sbin/sh\n\n#################\n# Initialization\n#################\n\numask 022\n\n# echo before loading util_functions\nui_print() { echo \"$1\"; }\n\nrequire_new_magisk() {\n  ui_print \"*******************************\"\n  ui_print \" Please install Magisk v20.4+! \"\n  ui_print \"*******************************\"\n  exit 1\n}\n\n#########################\n# Load util_functions.sh\n#########################\n\nOUTFD=$2\nZIPFILE=$3\n\nmount /data 2>/dev/null\n\n[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk\n. /data/adb/magisk/util_functions.sh\n[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk\n\ninstall_module\nexit 0\n"
  },
  {
    "path": "scripts/release.sh",
    "content": "#!/usr/bin/env bash\nset -e\n\n# On macOS, gsed is required (brew install gnu-sed)\n# Required tools: gh\n# The GitHub cli (gh) has to be properly authenticated\n\n# These variables can be modified as needed\nCONFIG=config.prop\nNOTES=notes.md\n\n# These are constants, do not modify\nGCONFIG=app/gradle.properties\nBUILDCMD=\"./build.py -c $CONFIG\"\nCWD=$(pwd)\n\ngrep_prop() {\n  local REGEX=\"s/^$1=//p\"\n  shift\n  local FILES=$@\n  sed -n \"$REGEX\" $FILES | head -n 1\n}\n\nensure_config() {\n  # Make sure version is not commented out and exists\n  sed -i \"s:^# version=:version=:g\" $CONFIG\n  if ! grep -qE '^version=' $CONFIG; then\n    echo 'version=' >> $CONFIG\n  fi\n  # Make sure abiList is not set when building for release\n  sed -i \"s:^abiList=:# abiList=:g\" $CONFIG\n}\n\ndisable_version_config() {\n  # Comment out version config\n  sed -i \"s:^version=:# version=:g\" $CONFIG\n}\n\n# $1 = ver\nset_version() {\n  local ver=$1\n  local code=$(echo - | awk \"{ print $ver * 1000 }\")\n  local tag=\"v$ver\"\n\n  sed -i \"s:versionCode=.*:versionCode=${code}:g\" $GCONFIG\n  sed -i \"s:version=.*:version=${ver}:g\" $CONFIG\n  sed -i \"1s:.*:## $(date +'%Y.%-m.%-d') Magisk v$ver:\" $NOTES\n\n  # Commit version code changes\n  git add -u .\n  git status\n  git commit -m \"Release Magisk v$ver\" -m \"[skip ci]\"\n}\n\n# $1 = ver\nbuild() {\n  [ -z $1 ] && exit 1\n  local ver=$1\n  git pull\n  set_version $ver\n  $BUILDCMD clean\n  $BUILDCMD all\n  $BUILDCMD -r all\n}\n\nupload() {\n  gh auth status\n\n  local code=$(grep_prop magisk.versionCode $GCONFIG)\n  local ver=$(echo - | awk \"{ print $code / 1000 }\")\n  local tag=\"v$ver\"\n  local title=\"Magisk v$ver\"\n\n  local out=$(grep_prop outdir $CONFIG)\n  if [ -z $out ]; then\n    out=out\n  fi\n\n  git tag $tag\n  git push origin master\n  git push --tags\n\n  # Prepare release notes\n  tail -n +3 $NOTES > release.md\n\n  # Publish release\n  local release_apk=\"Magisk-v${ver}.apk\"\n  cp $out/app-release.apk $release_apk\n  gh release create --verify-tag $tag -p -t \"$title\" -F release.md $release_apk $out/app-debug.apk $NOTES\n\n  rm -f $release_apk release.md\n}\n\n# Use GNU sed on macOS\nif command -v gsed >/dev/null; then\n  function sed() { gsed \"$@\"; }\n  export -f sed\nfi\n\ntrap disable_version_config EXIT\nensure_config\ncase $1 in\n  build ) build $2 ;;\n  upload ) upload ;;\n  * ) exit 1 ;;\nesac\n"
  },
  {
    "path": "scripts/test_common.sh",
    "content": "if [ -z $ANDROID_HOME ]; then\n  export ANDROID_HOME=$ANDROID_SDK_ROOT\nfi\n\n# Make sure paths are consistent\nexport ANDROID_USER_HOME=\"$HOME/.android\"\nexport ANDROID_EMULATOR_HOME=\"$ANDROID_USER_HOME\"\nexport ANDROID_AVD_HOME=\"$ANDROID_EMULATOR_HOME/avd\"\nexport PATH=\"$PATH:$ANDROID_HOME/platform-tools\"\n\nemu=\"$ANDROID_HOME/emulator/emulator\"\nsdk=\"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager\"\navd=\"$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager\"\n\nboot_timeout=100\n\ncore_count=$(nproc)\nif [ $core_count -gt 8 ]; then\n  core_count=8\nfi\n\nprint_title() {\n  echo -e \"\\n\\033[44;39m${1}\\033[0m\\n\"\n}\n\nprint_error() {\n  echo -e \"\\n\\033[41;39m${1}\\033[0m\\n\" >&2\n}\n\n# $1 = TestClass#method\n# $2 = component\nam_instrument() {\n  set +x\n  local out=$(adb shell am instrument -w --user 0 -e class \"$1\" \"$2\")\n  echo \"$out\"\n  if grep -q 'OK (' <<< \"$out\"; then\n    set -x\n    return 0\n  else\n    set -x\n    return 1\n  fi\n}\n\n# $1 = pkg\nwait_for_pm() {\n  sleep 5\n  adb shell pm uninstall $1 || true\n}\n\nrun_setup() {\n  local apk=$1\n  adb shell 'PATH=$PATH:/debug_ramdisk magisk -v'\n\n  # Install the Magisk app\n  adb install -r -g $apk\n\n  # Install the test app\n  adb install -r -g out/test.apk\n\n  local app='com.topjohnwu.magisk.test/com.topjohnwu.magisk.test.AppTestRunner'\n\n  # Run setup through the test app\n  am_instrument '.Environment#setupEnvironment' $app\n}\n\nprint_apks() {\n  if [ \"$#\" -eq 0 ]; then\n    find out -maxdepth 1 -type f -name \"app-*.apk\" -or -name \"apk-*.apk\"\n  else\n    echo \"$@\"\n  fi\n}\n\nrun_tests() {\n  local pkg='com.topjohnwu.magisk.test'\n  local self=\"$pkg/$pkg.TestRunner\"\n  local app=\"$pkg/$pkg.AppTestRunner\"\n  local stub=\"repackaged.$pkg/$pkg.AppTestRunner\"\n\n  # Run app tests\n  am_instrument '.MagiskAppTest,.AdditionalTest' $app\n\n  # Test app hiding\n  am_instrument '.AppMigrationTest#testAppHide' $self\n\n  # Make sure it still works\n  am_instrument '.MagiskAppTest' $stub\n\n  # Test app restore\n  am_instrument '.AppMigrationTest#testAppRestore' $self\n\n  # Make sure it still works\n  am_instrument '.MagiskAppTest' $app\n}\n"
  },
  {
    "path": "scripts/uninstaller.sh",
    "content": "#MAGISK\n############################################\n# Magisk Uninstaller (updater-script)\n############################################\n\n##############\n# Preparation\n##############\n\n# Default permissions\numask 022\n\nOUTFD=$2\nCOMMONDIR=$INSTALLER/assets\nCHROMEDIR=$INSTALLER/assets/chromeos\n\nif [ ! -f $COMMONDIR/util_functions.sh ]; then\n  echo \"! Unable to extract zip file!\"\n  exit 1\nfi\n\n# Load utility functions\n. $COMMONDIR/util_functions.sh\n\nsetup_flashable\n\n############\n# Detection\n############\n\nif echo $MAGISK_VER | grep -q '\\.'; then\n  PRETTY_VER=$MAGISK_VER\nelse\n  PRETTY_VER=\"$MAGISK_VER($MAGISK_VER_CODE)\"\nfi\nprint_title \"Magisk $PRETTY_VER Uninstaller\"\n\nis_mounted /data || mount /data || abort \"! Unable to mount /data, please uninstall with the Magisk app\"\nmount_partitions\ncheck_data\n$DATA_DE || abort \"! Cannot access /data, please uninstall with the Magisk app\"\nget_flags\nfind_boot_image\n\n[ -z $BOOTIMAGE ] && abort \"! Unable to detect target image\"\nui_print \"- Target image: $BOOTIMAGE\"\n\n# Detect version and architecture\napi_level_arch_detect\n\nui_print \"- Device platform: $ABI\"\n\nBINDIR=$INSTALLER/lib/$ABI\ncd $BINDIR\nfor file in lib*.so; do mv \"$file\" \"${file:3:${#file}-6}\"; done\ncd /\ncp -af $CHROMEDIR/. $BINDIR/chromeos\nchmod -R 755 $BINDIR\n\n############\n# Uninstall\n############\n\ncd $BINDIR\n\nCHROMEOS=false\n\nui_print \"- Unpacking boot image\"\n# Dump image for MTD/NAND character device boot partitions\nif [ -c $BOOTIMAGE ]; then\n  nanddump -f boot.img $BOOTIMAGE\n  BOOTNAND=$BOOTIMAGE\n  BOOTIMAGE=boot.img\nfi\n./magiskboot unpack \"$BOOTIMAGE\"\n\ncase $? in\n  1 )\n    abort \"! Unsupported/Unknown image format\"\n    ;;\n  2 )\n    ui_print \"- ChromeOS boot image detected\"\n    CHROMEOS=true\n    ;;\nesac\n\n# Restore the original boot partition path\n[ \"$BOOTNAND\" ] && BOOTIMAGE=$BOOTNAND\n\n# Detect boot image state\nui_print \"- Checking ramdisk status\"\nif [ -e ramdisk.cpio ]; then\n  ./magiskboot cpio ramdisk.cpio test\n  STATUS=$?\nelse\n  # Stock A only system-as-root\n  STATUS=0\nfi\ncase $((STATUS & 3)) in\n  0 )  # Stock boot\n    ui_print \"- Stock boot image detected\"\n    ;;\n  1 )  # Magisk patched\n    ui_print \"- Magisk patched image detected\"\n    # Find SHA1 of stock boot image\n    ./magiskboot cpio ramdisk.cpio \"extract .backup/.magisk config.orig\"\n    if [ -f config.orig ]; then\n      chmod 0644 config.orig\n      SHA1=$(grep_prop SHA1 config.orig)\n      rm config.orig\n    fi\n    BACKUPDIR=/data/magisk_backup_$SHA1\n    if [ -d $BACKUPDIR ]; then\n      ui_print \"- Restoring stock boot image\"\n      flash_image $BACKUPDIR/boot.img.gz $BOOTIMAGE\n      for name in dtb dtbo dtbs; do\n        [ -f $BACKUPDIR/${name}.img.gz ] || continue\n        IMAGE=$(find_block $name$SLOT)\n        [ -z $IMAGE ] && continue\n        ui_print \"- Restoring stock $name image\"\n        flash_image $BACKUPDIR/${name}.img.gz $IMAGE\n      done\n    else\n      ui_print \"! Boot image backup unavailable\"\n      ui_print \"- Restoring ramdisk with internal backup\"\n      ./magiskboot cpio ramdisk.cpio restore\n      if ! ./magiskboot cpio ramdisk.cpio \"exists init\"; then\n        # A only system-as-root\n        rm -f ramdisk.cpio\n      fi\n      ./magiskboot repack $BOOTIMAGE\n      # Sign chromeos boot\n      $CHROMEOS && sign_chromeos\n      ui_print \"- Flashing restored boot image\"\n      flash_image new-boot.img $BOOTIMAGE || abort \"! Insufficient partition size\"\n    fi\n    ;;\n  2 )  # Unsupported\n    ui_print \"! Boot image patched by unsupported programs\"\n    abort \"! Cannot uninstall\"\n    ;;\nesac\n\nif $BOOTMODE; then\n  ui_print \"- Removing modules\"\n  magisk --remove-modules -n\nfi\n\nui_print \"- Removing Magisk files\"\nrm -rf \\\n/cache/*magisk* /cache/unblock /data/*magisk* /data/cache/*magisk* /data/property/*magisk* \\\n/data/Magisk.apk /data/busybox /data/custom_ramdisk_patch.sh /data/adb/*magisk* \\\n/data/adb/post-fs-data.d /data/adb/service.d /data/adb/modules* \\\n/data/unencrypted/magisk /metadata/magisk /metadata/watchdog/magisk /persist/magisk /mnt/vendor/persist/magisk\n\nADDOND=/system/addon.d/99-magisk.sh\nif [ -f $ADDOND ]; then\n  blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null\n  mount -o rw,remount /system || mount -o rw,remount /\n  rm -f $ADDOND\nfi\n\ncd /\n\nif $BOOTMODE; then\n  ui_print \"********************************************\"\n  ui_print \" The Magisk app will uninstall itself, and\"\n  ui_print \" the device will reboot after a few seconds\"\n  ui_print \"********************************************\"\n  (sleep 8; /system/bin/reboot)&\nelse\n  ui_print \"********************************************\"\n  ui_print \" The Magisk app will not be uninstalled\"\n  ui_print \" Please uninstall it manually after reboot\"\n  ui_print \"********************************************\"\n  recovery_cleanup\n  ui_print \"- Done\"\nfi\n\nrm -rf $TMPDIR\nexit 0\n"
  },
  {
    "path": "scripts/update_binary.sh",
    "content": "#!/sbin/sh\n\nTMPDIR=/dev/tmp\nrm -rf $TMPDIR\nmkdir -p $TMPDIR 2>/dev/null\n\nexport BBBIN=$TMPDIR/busybox\nfor arch in \"x86_64\" \"x86\" \"arm64-v8a\" \"armeabi-v7a\"; do\n  unzip -o \"$3\" \"lib/$arch/libbusybox.so\" -d $TMPDIR >&2\n  libpath=\"$TMPDIR/lib/$arch/libbusybox.so\"\n  chmod 755 $libpath\n  if [ -x $libpath ] && $libpath >/dev/null 2>&1; then\n    mv -f $libpath $BBBIN\n    break\n  fi\ndone\n$BBBIN rm -rf $TMPDIR/lib\n\nexport INSTALLER=$TMPDIR/install\n$BBBIN mkdir -p $INSTALLER\n$BBBIN unzip -o \"$3\" \"assets/*\" \"lib/*\" \"META-INF/com/google/*\" -x \"lib/*/libbusybox.so\" -d $INSTALLER >&2\nexport ASH_STANDALONE=1\nif echo \"$3\" | $BBBIN grep -q \"uninstall\"; then\n  exec $BBBIN sh \"$INSTALLER/assets/uninstaller.sh\" \"$@\"\nelse\n  exec $BBBIN sh \"$INSTALLER/META-INF/com/google/android/updater-script\" \"$@\"\nfi\n"
  },
  {
    "path": "scripts/util_functions.sh",
    "content": "############################################\n# Magisk General Utility Functions\n############################################\n\n#MAGISK_VERSION_STUB\n\n###################\n# Global Variables\n###################\n\n# True if the script is running on booted Android, not something like recovery\n# BOOTMODE=\n\n# The path to store temporary files that don't need to persist\n# TMPDIR=\n\n# The non-volatile path where magisk executables are stored\n# MAGISKBIN=\n\n###################\n# Helper Functions\n###################\n\nui_print() {\n  if $BOOTMODE; then\n    echo \"$1\"\n  else\n    echo -e \"ui_print $1\\nui_print\" >> /proc/self/fd/$OUTFD\n  fi\n}\n\ntoupper() {\n  echo \"$@\" | tr '[:lower:]' '[:upper:]'\n}\n\ngrep_cmdline() {\n  local REGEX=\"s/^$1=//p\"\n  { echo $(cat /proc/cmdline)$(sed -e 's/[^\"]//g' -e 's/\"\"//g' /proc/cmdline) | xargs -n 1; \\\n    sed -e 's/ = /=/g' -e 's/, /,/g' -e 's/\"//g' /proc/bootconfig; \\\n  } 2>/dev/null | sed -n \"$REGEX\"\n}\n\ngrep_prop() {\n  local REGEX=\"s/^$1=//p\"\n  shift\n  local FILES=$@\n  [ -z \"$FILES\" ] && FILES='/system/build.prop'\n  cat $FILES 2>/dev/null | dos2unix | sed -n \"$REGEX\" | head -n 1\n}\n\ngrep_get_prop() {\n  local result=$(grep_prop $@)\n  if [ -z \"$result\" ]; then\n    # Fallback to getprop\n    getprop \"$1\"\n  else\n    echo $result\n  fi\n}\n\ngetvar() {\n  local VARNAME=$1\n  local VALUE\n  local PROPPATH='/data/.magisk /cache/.magisk'\n  [ ! -z $MAGISKTMP ] && PROPPATH=\"$MAGISKTMP/.magisk/config $PROPPATH\"\n  VALUE=$(grep_prop $VARNAME $PROPPATH)\n  [ ! -z $VALUE ] && eval $VARNAME=\\$VALUE\n}\n\nis_mounted() {\n  grep -q \" $(readlink -f $1) \" /proc/mounts 2>/dev/null\n  return $?\n}\n\nabort() {\n  ui_print \"$1\"\n  $BOOTMODE || recovery_cleanup\n  [ ! -z $MODPATH ] && rm -rf $MODPATH\n  rm -rf $TMPDIR\n  exit 1\n}\n\nprint_title() {\n  local len line1len line2len bar\n  line1len=$(echo -n $1 | wc -c)\n  line2len=$(echo -n $2 | wc -c)\n  len=$line2len\n  [ $line1len -gt $line2len ] && len=$line1len\n  len=$((len + 2))\n  bar=$(printf \"%${len}s\" | tr ' ' '*')\n  ui_print \"$bar\"\n  ui_print \" $1 \"\n  [ \"$2\" ] && ui_print \" $2 \"\n  ui_print \"$bar\"\n}\n\n######################\n# Environment Related\n######################\n\nsetup_flashable() {\n  ensure_bb\n  $BOOTMODE && return\n  if [ -z $OUTFD ] || readlink /proc/$$/fd/$OUTFD | grep -q /tmp; then\n    # We will have to manually find out OUTFD\n    for FD in $(ls /proc/$$/fd); do\n      if readlink /proc/$$/fd/$FD | grep -q pipe; then\n        if ps | grep -v grep | grep -qE \" 3 $FD |status_fd=$FD\"; then\n          OUTFD=$FD\n          break\n        fi\n      fi\n    done\n  fi\n  recovery_actions\n}\n\nensure_bb() {\n  if set -o | grep -q standalone; then\n    # We are definitely in busybox ash\n    set -o standalone\n    return\n  fi\n\n  # Find our busybox binary\n  local bb\n  if [ -f $TMPDIR/busybox ]; then\n    bb=$TMPDIR/busybox\n  elif [ -f $MAGISKBIN/busybox ]; then\n    bb=$MAGISKBIN/busybox\n  else\n    abort \"! Cannot find BusyBox\"\n  fi\n  chmod 755 $bb\n\n  # Busybox could be a script, make sure /system/bin/sh exists\n  if [ ! -f /system/bin/sh ]; then\n    umount -l /system 2>/dev/null\n    mkdir -p /system/bin\n    ln -s $(command -v sh) /system/bin/sh\n  fi\n\n  export ASH_STANDALONE=1\n\n  # Find our current arguments\n  # Run in busybox environment to ensure consistent results\n  # /proc/<pid>/cmdline shall be <interpreter> <script> <arguments...>\n  local cmds=\"$($bb sh -c \"\n  for arg in \\$(tr '\\0' '\\n' < /proc/$$/cmdline); do\n    if [ -z \\\"\\$cmds\\\" ]; then\n      # Skip the first argument as we want to change the interpreter\n      cmds=\\\"sh\\\"\n    else\n      cmds=\\\"\\$cmds '\\$arg'\\\"\n    fi\n  done\n  echo \\$cmds\")\"\n\n  # Re-exec our script\n  echo $cmds | $bb xargs $bb\n  exit\n}\n\nrecovery_actions() {\n  # Make sure random won't get blocked\n  mount -o bind /dev/urandom /dev/random\n  # Unset library paths\n  OLD_LD_LIB=$LD_LIBRARY_PATH\n  OLD_LD_PRE=$LD_PRELOAD\n  OLD_LD_CFG=$LD_CONFIG_FILE\n  unset LD_LIBRARY_PATH\n  unset LD_PRELOAD\n  unset LD_CONFIG_FILE\n}\n\nrecovery_cleanup() {\n  local DIR\n  ui_print \"- Unmounting partitions\"\n  (\n  if [ ! -d /postinstall/tmp ]; then\n    umount -l /system\n    umount -l /system_root\n  fi\n  umount -l /vendor\n  umount -l /persist\n  umount -l /metadata\n  for DIR in /apex /system /system_root; do\n    if [ -L \"${DIR}_link\" ]; then\n      rmdir $DIR\n      mv -f ${DIR}_link $DIR\n    fi\n  done\n  umount -l /dev/random\n  ) 2>/dev/null\n  [ -z $OLD_LD_LIB ] || export LD_LIBRARY_PATH=$OLD_LD_LIB\n  [ -z $OLD_LD_PRE ] || export LD_PRELOAD=$OLD_LD_PRE\n  [ -z $OLD_LD_CFG ] || export LD_CONFIG_FILE=$OLD_LD_CFG\n}\n\n#######################\n# Installation Related\n#######################\n\n# find_block [partname...]\nfind_block() {\n  local BLOCK DEV DEVICE DEVNAME PARTNAME UEVENT\n  for BLOCK in \"$@\"; do\n    DEVICE=$(find /dev/block \\( -type b -o -type c -o -type l \\) -iname $BLOCK | head -n 1) 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      echo $DEVICE\n      return 0\n    fi\n  done\n  # Fallback by parsing sysfs uevents\n  for UEVENT in /sys/dev/block/*/uevent; do\n    DEVNAME=$(grep_prop DEVNAME $UEVENT)\n    PARTNAME=$(grep_prop PARTNAME $UEVENT)\n    for BLOCK in \"$@\"; do\n      if [ \"$(toupper $BLOCK)\" = \"$(toupper $PARTNAME)\" ]; then\n        echo /dev/block/$DEVNAME\n        return 0\n      fi\n    done\n  done\n  # Look just in /dev in case we're dealing with MTD/NAND without /dev/block devices/links\n  for DEV in \"$@\"; do\n    DEVICE=$(find /dev \\( -type b -o -type c -o -type l \\) -maxdepth 1 -iname $DEV | head -n 1) 2>/dev/null\n    if [ ! -z $DEVICE ]; then\n      echo $DEVICE\n      return 0\n    fi\n  done\n  return 1\n}\n\n# setup_mntpoint <mountpoint>\nsetup_mntpoint() {\n  local POINT=$1\n  [ -L $POINT ] && mv -f $POINT ${POINT}_link\n  if [ ! -d $POINT ]; then\n    rm -f $POINT\n    mkdir -p $POINT\n  fi\n}\n\n# mount_name <partname(s)> <mountpoint> <flag>\nmount_name() {\n  local PART=$1\n  local POINT=$2\n  local FLAG=$3\n  setup_mntpoint $POINT\n  is_mounted $POINT && return\n  # First try mounting with fstab\n  mount $FLAG $POINT 2>/dev/null\n  if ! is_mounted $POINT; then\n    local BLOCK=$(find_block $PART)\n    mount $FLAG $BLOCK $POINT || return\n  fi\n  ui_print \"- Mounting $POINT\"\n}\n\n# mount_ro_ensure <partname(s)> <mountpoint>\nmount_ro_ensure() {\n  # We handle ro partitions only in recovery\n  $BOOTMODE && return\n  local PART=$1\n  local POINT=$2\n  mount_name \"$PART\" $POINT '-o ro'\n  is_mounted $POINT || abort \"! Cannot mount $POINT\"\n}\n\n# After calling this method, the following variables will be set:\n# SLOT, SYSTEM_AS_ROOT, LEGACYSAR\nmount_partitions() {\n  # Check A/B slot\n  SLOT=$(grep_cmdline androidboot.slot_suffix)\n  if [ -z $SLOT ]; then\n    SLOT=$(grep_cmdline androidboot.slot)\n    [ -z $SLOT ] || SLOT=_${SLOT}\n  fi\n  [ \"$SLOT\" = \"normal\" ] && unset SLOT\n  [ -z $SLOT ] || ui_print \"- Current boot slot: $SLOT\"\n\n  # Mount ro partitions\n  if is_mounted /system_root; then\n    umount /system 2>/dev/null\n    umount /system_root 2>/dev/null\n  fi\n  mount_ro_ensure \"system$SLOT app$SLOT\" /system\n  if [ -f /system/init -o -L /system/init ]; then\n    SYSTEM_AS_ROOT=true\n    setup_mntpoint /system_root\n    if ! mount --move /system /system_root; then\n      umount /system\n      umount -l /system 2>/dev/null\n      mount_ro_ensure \"system$SLOT app$SLOT\" /system_root\n    fi\n    mount -o bind /system_root/system /system\n  else\n    if grep ' / ' /proc/mounts | grep -qv 'rootfs' || grep -q ' /system_root ' /proc/mounts; then\n      SYSTEM_AS_ROOT=true\n    else\n      SYSTEM_AS_ROOT=false\n    fi\n  fi\n  $SYSTEM_AS_ROOT && ui_print \"- Device is system-as-root\"\n\n  LEGACYSAR=false\n  if $BOOTMODE; then\n    grep ' / ' /proc/mounts | grep -q '/dev/root' && LEGACYSAR=true\n  else\n    # Recovery mode, assume devices that don't use dynamic partitions are legacy SAR\n    local IS_DYNAMIC=false\n    if grep -q 'androidboot.super_partition' /proc/cmdline; then\n      IS_DYNAMIC=true\n    elif [ -n \"$(find_block super)\" ]; then\n      IS_DYNAMIC=true\n    fi\n    if $SYSTEM_AS_ROOT && ! $IS_DYNAMIC; then\n      LEGACYSAR=true\n      ui_print \"- Legacy SAR, force kernel to load rootfs\"\n    fi\n  fi\n}\n\n# After calling this method, the following variables will be set:\n# ISENCRYPTED, PATCHVBMETAFLAG,\n# KEEPVERITY, KEEPFORCEENCRYPT, RECOVERYMODE, VENDORBOOT\nget_flags() {\n  if grep ' /data ' /proc/mounts | grep -q 'dm-'; then\n    ISENCRYPTED=true\n  elif [ \"$(getprop ro.crypto.state)\" = \"encrypted\" ]; then\n    ISENCRYPTED=true\n  elif [ \"$DATA\" = \"false\" ]; then\n    # No data access means unable to decrypt in recovery\n    ISENCRYPTED=true\n  else\n    ISENCRYPTED=false\n  fi\n  if [ -n \"$(find_block vbmeta vbmeta_a)\" ]; then\n    PATCHVBMETAFLAG=false\n  else\n    PATCHVBMETAFLAG=true\n    ui_print \"- No vbmeta partition, patch vbmeta in boot image\"\n  fi\n\n  # Overridable config flags with safe defaults\n  getvar KEEPVERITY\n  getvar KEEPFORCEENCRYPT\n  getvar RECOVERYMODE\n  getvar VENDORBOOT\n  if [ -z $KEEPVERITY ]; then\n    if $SYSTEM_AS_ROOT; then\n      KEEPVERITY=true\n      ui_print \"- System-as-root, keep dm-verity\"\n    else\n      KEEPVERITY=false\n    fi\n  fi\n  if [ -z $KEEPFORCEENCRYPT ]; then\n    if $ISENCRYPTED; then\n      KEEPFORCEENCRYPT=true\n      ui_print \"- Encrypted data, keep forceencrypt\"\n    else\n      KEEPFORCEENCRYPT=false\n    fi\n  fi\n  [ -z $RECOVERYMODE ] && RECOVERYMODE=false\n  [ -z $VENDORBOOT ] && VENDORBOOT=false\n}\n\n# Returns whether the device is GKI 13+\nis_gt_gki_13() {\n  [ \"$(uname -r | cut -d. -f1)\" -ge 5 ] && uname -r | grep -Evq \"android12-|^5\\.4\"\n}\n\n# Require RECOVERYMODE, VENDORBOOT, SLOT to be set.\n# After calling this method, BOOTIMAGE will be set.\nfind_boot_image() {\n  BOOTIMAGE=\n  if $VENDORBOOT; then\n    BOOTIMAGE=\"/dev/block/by-name/vendor_boot$SLOT\"\n  elif $RECOVERYMODE; then\n    BOOTIMAGE=$(find_block \"recovery$SLOT\" \"sos\")\n  elif [ -e \"/dev/block/by-name/init_boot$SLOT\" ] && is_gt_gki_13; then\n    # init_boot is only used with GKI 13+. It is possible that some devices with init_boot\n    # partition still uses Android 12 GKI or previous kernels, so we need to explicitly detect that scenario.\n    BOOTIMAGE=\"/dev/block/by-name/init_boot$SLOT\"\n  elif [ -e \"/dev/block/by-name/boot$SLOT\" ]; then\n    # Standard location since AOSP Android 10+\n    BOOTIMAGE=\"/dev/block/by-name/boot$SLOT\"\n  elif [ -n \"$SLOT\" ]; then\n    # Fallback for A/B devices running < Android 10\n    BOOTIMAGE=$(find_block \"ramdisk$SLOT\" \"boot$SLOT\")\n  else\n    # Fallback for all legacy and non-standard devices\n    BOOTIMAGE=$(find_block ramdisk kern-a android_boot kernel bootimg boot lnx boot_a)\n  fi\n  if [ -z $BOOTIMAGE ]; then\n    # Lets see what fstabs tells me\n    BOOTIMAGE=$(grep -v '#' /etc/*fstab* | grep -E '/boot(img)?[^a-zA-Z]' | grep -oE '/dev/[a-zA-Z0-9_./-]*' | head -n 1)\n  fi\n}\n\nflash_image() {\n  local CMD1\n  case \"$1\" in\n    *.gz) CMD1=\"gzip -d < '$1' 2>/dev/null\";;\n    *)    CMD1=\"cat '$1'\";;\n  esac\n  if [ -b \"$2\" ]; then\n    local img_sz=$(stat -c '%s' \"$1\")\n    local blk_sz=$(blockdev --getsize64 \"$2\")\n    [ \"$img_sz\" -gt \"$blk_sz\" ] && return 1\n    blockdev --setrw \"$2\"\n    local blk_ro=$(blockdev --getro \"$2\")\n    [ \"$blk_ro\" -eq 1 ] && return 2\n    eval \"$CMD1\" | cat - /dev/zero > \"$2\" 2>/dev/null\n  elif [ -c \"$2\" ]; then\n    flash_eraseall \"$2\" >&2\n    eval \"$CMD1\" | nandwrite -p \"$2\" - >&2\n  else\n    ui_print \"- Not block or char device, storing image\"\n    eval \"$CMD1\" > \"$2\" 2>/dev/null\n  fi\n  return 0\n}\n\n# Common installation script for flash_script.sh and addon.d.sh\ninstall_magisk() {\n  cd $MAGISKBIN\n\n  # Source the boot patcher\n  SOURCEDMODE=true\n  . ./boot_patch.sh \"$BOOTIMAGE\"\n\n  ui_print \"- Flashing new boot image\"\n  flash_image new-boot.img \"$BOOTIMAGE\"\n  case $? in\n    1)\n      abort \"! Insufficient partition size\"\n      ;;\n    2)\n      abort \"! $BOOTIMAGE is read only\"\n      ;;\n  esac\n\n  ./magiskboot cleanup\n  rm -f new-boot.img\n\n  run_migrations\n}\n\nsign_chromeos() {\n  ui_print \"- Signing ChromeOS boot image\"\n\n  echo > empty\n  ./chromeos/futility vbutil_kernel --pack new-boot.img.signed \\\n  --keyblock ./chromeos/kernel.keyblock --signprivate ./chromeos/kernel_data_key.vbprivk \\\n  --version 1 --vmlinuz new-boot.img --config empty --arch arm --bootloader empty --flags 0x1\n\n  rm -f empty new-boot.img\n  mv new-boot.img.signed new-boot.img\n}\n\nremove_system_su() {\n  [ -d /postinstall/tmp ] && POSTINST=/postinstall\n  cd $POSTINST/system\n  if [ -f bin/su -o -f xbin/su ] && [ ! -f /su/bin/su ]; then\n    ui_print \"- Removing system installed root\"\n    blockdev --setrw /dev/block/mapper/system$SLOT 2>/dev/null\n    mount -o rw,remount $POSTINST/system\n    # SuperSU\n    cd bin\n    if [ -e .ext/.su ]; then\n      mv -f app_process32_original app_process32 2>/dev/null\n      mv -f app_process64_original app_process64 2>/dev/null\n      mv -f install-recovery_original.sh install-recovery.sh 2>/dev/null\n      if [ -e app_process64 ]; then\n        ln -sf app_process64 app_process\n      elif [ -e app_process32 ]; then\n        ln -sf app_process32 app_process\n      fi\n    fi\n    # More SuperSU, SuperUser & ROM su\n    cd ..\n    rm -rf .pin bin/.ext etc/.installed_su_daemon etc/.has_su_daemon \\\n    xbin/daemonsu xbin/su xbin/sugote xbin/sugote-mksh xbin/supolicy \\\n    bin/app_process_init bin/su /cache/su lib/libsupol.so lib64/libsupol.so \\\n    su.d etc/init.d/99SuperSUDaemon etc/install-recovery.sh /cache/install-recovery.sh \\\n    .supersu /cache/.supersu /data/.supersu \\\n    app/Superuser.apk app/SuperSU /cache/Superuser.apk\n  elif [ -f /cache/su.img -o -f /data/su.img -o -d /data/su -o -d /data/adb/su ]; then\n    ui_print \"- Removing systemless installed root\"\n    umount -l /su 2>/dev/null\n    rm -rf /cache/su.img /data/su.img /data/su /data/adb/su /data/adb/suhide \\\n    /cache/.supersu /data/.supersu /cache/supersu_install /data/supersu_install\n  fi\n  cd $TMPDIR\n}\n\napi_level_arch_detect() {\n  API=$(grep_get_prop ro.build.version.sdk)\n  ABI=$(grep_get_prop ro.product.cpu.abi)\n  if [ \"$ABI\" = \"arm64-v8a\" ]; then\n    ARCH=arm64\n    ABI32=armeabi-v7a\n    IS64BIT=true\n  elif [ \"$ABI\" = \"x86_64\" ]; then\n    ARCH=x64\n    ABI32=x86\n    IS64BIT=true\n  elif [ \"$ABI\" = \"armeabi-v7a\" ]; then\n    ARCH=arm\n    ABI32=armeabi-v7a\n    IS64BIT=false\n  elif [ \"$ABI\" = \"x86\" ]; then\n    ARCH=x86\n    ABI32=x86\n    IS64BIT=false\n  elif [ \"$ABI\" = \"riscv64\" ]; then\n    ARCH=riscv64\n    ABI32=riscv32\n    IS64BIT=true\n  fi\n}\n\ncheck_data() {\n  DATA=false\n  DATA_DE=false\n  if grep ' /data ' /proc/mounts | grep -vq 'tmpfs'; then\n    # Test if data is writable\n    touch /data/.rw && rm /data/.rw && DATA=true\n    # Test if data is decrypted\n    $DATA && [ -d /data/adb ] && touch /data/adb/.rw && rm /data/adb/.rw && DATA_DE=true\n    $DATA_DE && [ -d /data/adb/magisk ] || mkdir /data/adb/magisk || DATA_DE=false\n  fi\n  MAGISKBIN=\"/data/magisk\"\n  $DATA || MAGISKBIN=\"/cache/data_adb/magisk\"\n  $DATA_DE && MAGISKBIN=\"/data/adb/magisk\"\n}\n\nrun_migrations() {\n  local SHA1\n  local TARGET\n  # Legacy app installation\n  local BACKUP=$MAGISKBIN/stock_boot*.gz\n  if [ -f $BACKUP ]; then\n    cp $BACKUP /data\n    rm -f $BACKUP\n  fi\n\n  # Legacy backup\n  for gz in /data/stock_boot*.gz; do\n    [ -f $gz ] || break\n    SHA1=$(basename $gz | sed -e 's/stock_boot_//' -e 's/.img.gz//')\n    [ -z $SHA1 ] && break\n    mkdir /data/magisk_backup_${SHA1} 2>/dev/null\n    mv $gz /data/magisk_backup_${SHA1}/boot.img.gz\n  done\n\n  # Stock backups\n  SHA1=\n  for name in boot dtb dtbo dtbs; do\n    BACKUP=$MAGISKBIN/stock_${name}.img\n    [ -f $BACKUP ] || continue\n    if [ $name = 'boot' ]; then\n      SHA1=$($MAGISKBIN/magiskboot sha1 $BACKUP)\n      mkdir /data/magisk_backup_${SHA1} 2>/dev/null\n    fi\n    [ -z $SHA1 ] && break\n    TARGET=/data/magisk_backup_${SHA1}/${name}.img\n    cp $BACKUP $TARGET\n    rm -f $BACKUP\n    gzip -9f $TARGET\n  done\n\n  copy_preinit_files\n}\n\ncopy_preinit_files() {\n  local PREINITDIR=$MAGISKTMP/.magisk/preinit\n  if [ ! -d $PREINITDIR ]; then\n    ui_print \"- Unable to find preinit dir\"\n    return 1\n  fi\n\n  # Copy all enabled sepolicy.rule\n  for r in /data/adb/modules*/*/sepolicy.rule; do\n    [ -f \"$r\" ] || continue\n    local MODDIR=${r%/*}\n    [ -f $MODDIR/disable ] && continue\n    [ -f $MODDIR/remove ] && continue\n    [ -f $MODDIR/update ] && continue\n    cat $r\n    echo\n  done > $PREINITDIR/sepolicy.rule\n}\n\n#################\n# Module Related\n#################\n\nset_perm() {\n  chown $2:$3 $1 || return 1\n  chmod $4 $1 || return 1\n  local CON=$5\n  [ -z $CON ] && CON=u:object_r:system_file:s0\n  chcon $CON $1 || return 1\n}\n\nset_perm_recursive() {\n  find $1 -type d 2>/dev/null | while read dir; do\n    set_perm $dir $2 $3 $4 $6\n  done\n  find $1 -type f -o -type l 2>/dev/null | while read file; do\n    set_perm $file $2 $3 $5 $6\n  done\n}\n\nmktouch() {\n  mkdir -p ${1%/*} 2>/dev/null\n  [ -z $2 ] && touch $1 || echo $2 > $1\n  chmod 644 $1\n}\n\nboot_actions() { return; }\n\n# Require ZIPFILE to be set\nis_legacy_script() {\n  unzip -l \"$ZIPFILE\" install.sh | grep -q install.sh\n  return $?\n}\n\n# $1 = MODPATH\nset_default_perm() {\n  set_perm_recursive $1 0 0 0755 0644\n  set_perm_recursive $1/system/bin 0 2000 0755 0755\n  set_perm_recursive $1/system/xbin 0 2000 0755 0755\n  set_perm_recursive $1/system/system_ext/bin 0 2000 0755 0755\n  set_perm_recursive $1/system/vendor/bin 0 2000 0755 0755 u:object_r:vendor_file:s0\n}\n\n# Require OUTFD, ZIPFILE to be set\ninstall_module() {\n  rm -rf $TMPDIR\n  mkdir -p $TMPDIR\n  chcon u:object_r:system_file:s0 $TMPDIR\n  cd $TMPDIR\n\n  setup_flashable\n  mount_partitions\n  api_level_arch_detect\n\n  # Setup busybox and binaries\n  if $BOOTMODE; then\n    boot_actions\n  else\n    recovery_actions\n  fi\n\n  # Extract prop file\n  unzip -o \"$ZIPFILE\" module.prop -d $TMPDIR >&2\n  [ ! -f $TMPDIR/module.prop ] && abort \"! This zip is not a Magisk module!\"\n\n  local MODDIRNAME=modules\n  $BOOTMODE && MODDIRNAME=modules_update\n  local MODULEROOT=/data/adb/$MODDIRNAME\n  MODID=$(grep_prop id $TMPDIR/module.prop)\n  MODNAME=$(grep_prop name $TMPDIR/module.prop)\n  MODAUTH=$(grep_prop author $TMPDIR/module.prop)\n  MODPATH=$MODULEROOT/$MODID\n\n  # Create mod paths\n  rm -rf $MODPATH\n  mkdir -p $MODPATH\n  chcon u:object_r:system_file:s0 $MODPATH\n\n  if is_legacy_script; then\n    unzip -oj \"$ZIPFILE\" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2\n\n    # Load install script\n    . $TMPDIR/install.sh\n\n    # Callbacks\n    print_modname\n    on_install\n\n    [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh\n    $SKIPMOUNT && touch $MODPATH/skip_mount\n    $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop\n    cp -af $TMPDIR/module.prop $MODPATH/module.prop\n    $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh\n    $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh\n\n    ui_print \"- Setting permissions\"\n    set_permissions\n  else\n    print_title \"$MODNAME\" \"by $MODAUTH\"\n    print_title \"Powered by Magisk\"\n\n    unzip -o \"$ZIPFILE\" customize.sh -d $MODPATH >&2\n\n    if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then\n      ui_print \"- Extracting module files\"\n      unzip -o \"$ZIPFILE\" -x 'META-INF/*' -d $MODPATH >&2\n      set_default_perm $MODPATH\n    fi\n\n    # Load customization script\n    [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh\n  fi\n\n  # Handle replace folders\n  for TARGET in $REPLACE; do\n    ui_print \"- Replace target: $TARGET\"\n    mktouch $MODPATH$TARGET/.replace\n  done\n\n  for TARGET in $REMOVE; do\n    ui_print \"- Remove target: $TARGET\"\n    mkdir -p $(dirname $MODPATH$TARGET) 2>/dev/null\n    mknod $MODPATH$TARGET c 0 0\n  done\n\n  if $BOOTMODE; then\n    # Update info for Magisk app\n    mktouch /data/adb/modules/$MODID/update\n    rm -rf /data/adb/modules/$MODID/remove 2>/dev/null\n    rm -rf /data/adb/modules/$MODID/disable 2>/dev/null\n    cp -af $MODPATH/module.prop /data/adb/modules/$MODID/module.prop\n  fi\n\n  # Copy over custom sepolicy rules\n  if [ -f $MODPATH/sepolicy.rule ]; then\n    ui_print \"- Installing custom sepolicy rules\"\n    copy_preinit_files\n  fi\n\n  # Remove stuff that doesn't belong to modules and clean up any empty directories\n  rm -rf \\\n  $MODPATH/system/placeholder $MODPATH/customize.sh \\\n  $MODPATH/README.md $MODPATH/.git*\n  rmdir -p $MODPATH 2>/dev/null\n\n  cd /\n  $BOOTMODE || recovery_cleanup\n  rm -rf $TMPDIR\n\n  ui_print \"- Done\"\n}\n\n##########\n# Presets\n##########\n\n# Detect whether in boot mode\n[ -z $BOOTMODE ] && ps | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && ps -A 2>/dev/null | grep zygote | grep -qv grep && BOOTMODE=true\n[ -z $BOOTMODE ] && BOOTMODE=false\n\nTMPDIR=/dev/tmp\nMAGISKBIN=\"/data/adb/magisk\"\n"
  },
  {
    "path": "tools/bootctl.patch",
    "content": "# How to build the bootctl bundled within the Magisk app:\n#\n# 1. Checkout and sync the AOSP tree:\n#    https://source.android.com/setup/build/downloading\n# 2. Build for arm64:\n#    lunch aosp_arm64-eng\n# 3. Apply patches:\n#    cd system/extras; patch -p1 < bootctl.patch\n# 4. Build the executable:\n#    m bootctl\n\ndiff --git a/bootctl/Android.bp b/bootctl/Android.bp\nindex f63871cf..8a551bbe 100644\n--- a/bootctl/Android.bp\n+++ b/bootctl/Android.bp\n@@ -26,11 +26,15 @@ cc_binary {\n         \"-Werror\",\n     ],\n\n-    shared_libs: [\n+    static_libs: [\n         \"android.hardware.boot@1.0\",\n         \"android.hardware.boot@1.1\",\n         \"android.hardware.boot@1.2\",\n+    ],\n+    shared_libs: [\n         \"libhidlbase\",\n         \"libutils\",\n+        \"libcutils\",\n+        \"liblog\",\n     ],\n }\n"
  },
  {
    "path": "tools/elf-cleaner/.gitignore",
    "content": "/target\n"
  },
  {
    "path": "tools/elf-cleaner/Cargo.toml",
    "content": "[package]\nname = \"elf-cleaner\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[dependencies]\nobject = { version = \"0.36\", features = [\"build\"] }\nanyhow = \"1.0\"\n\n[profile.release]\nstrip = true\nlto = true\ncodegen-units = 1\n"
  },
  {
    "path": "tools/elf-cleaner/src/main.rs",
    "content": "use object::build::elf::{Builder, Dynamic, SectionData};\nuse object::elf;\nuse std::{env, fs};\n\n// Implementation adapted from https://github.com/termux/termux-elf-cleaner\n\n// Missing ELF constants\nconst DT_AARCH64_BTI_PLT: u32 = elf::DT_LOPROC + 1;\nconst DT_AARCH64_PAC_PLT: u32 = elf::DT_LOPROC + 3;\nconst DT_AARCH64_VARIANT_PCS: u32 = elf::DT_LOPROC + 5;\n\nconst SUPPORTED_DT_FLAGS: u32 = elf::DF_1_NOW | elf::DF_1_GLOBAL;\n\nfn print_remove_dynamic(name: &str, path: &str) {\n    println!(\"Removing dynamic section entry {} in '{}'\", name, path);\n}\n\nfn process_elf(path: &str) -> anyhow::Result<()> {\n    let bytes = fs::read(path)?;\n    let mut elf = Builder::read(bytes.as_slice())?;\n    let is_aarch64 = elf.header.e_machine == elf::EM_AARCH64;\n\n    elf.sections.iter_mut().for_each(|section| {\n        if let SectionData::Dynamic(entries) = &mut section.data {\n            // Remove unsupported entries\n            entries.retain(|e| {\n                let tag = e.tag();\n                match tag {\n                    elf::DT_RPATH => {\n                        print_remove_dynamic(\"DT_RPATH\", path);\n                        return false;\n                    }\n                    elf::DT_RUNPATH => {\n                        print_remove_dynamic(\"DT_RUNPATH\", path);\n                        return false;\n                    }\n                    _ => {}\n                }\n                if is_aarch64 {\n                    match tag {\n                        DT_AARCH64_BTI_PLT => {\n                            print_remove_dynamic(\"DT_AARCH64_BTI_PLT\", path);\n                            return false;\n                        }\n                        DT_AARCH64_PAC_PLT => {\n                            print_remove_dynamic(\"DT_AARCH64_PAC_PLT\", path);\n                            return false;\n                        }\n                        DT_AARCH64_VARIANT_PCS => {\n                            print_remove_dynamic(\"DT_AARCH64_VARIANT_PCS\", path);\n                            return false;\n                        }\n                        _ => {}\n                    }\n                }\n                true\n            });\n            // Remove unsupported flags\n            for entry in entries.iter_mut() {\n                if let Dynamic::Integer { tag, val } = entry {\n                    if *tag == elf::DT_FLAGS_1 {\n                        let new_flags = *val & SUPPORTED_DT_FLAGS as u64;\n                        if new_flags != *val {\n                            println!(\n                                \"Replacing unsupported DT_FLAGS_1 {:#x} with {:#x} in '{}'\",\n                                *val, new_flags, path\n                            );\n                            *val = new_flags;\n                        }\n                        break;\n                    }\n                }\n            }\n        }\n    });\n\n    let mut out_bytes = Vec::new();\n    elf.write(&mut out_bytes)?;\n    fs::write(path, &out_bytes)?;\n    Ok(())\n}\n\nfn main() -> anyhow::Result<()> {\n    env::args().skip(1).try_for_each(|s| process_elf(&s))\n}\n"
  },
  {
    "path": "tools/keys/verity.x509.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID/TCCAuWgAwIBAgIJAJcPmDkJqolJMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4g\nVmlldzEQMA4GA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UE\nAwwHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe\nFw0xNDExMDYxOTA3NDBaFw00MjAzMjQxOTA3NDBaMIGUMQswCQYDVQQGEwJVUzET\nMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4G\nA1UECgwHQW5kcm9pZDEQMA4GA1UECwwHQW5kcm9pZDEQMA4GA1UEAwwHQW5kcm9p\nZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAOjreE0vTVSRenuzO9vnaWfk0eQzYab0gqpi\n6xAzi6dmD+ugoEKJmbPiuE5Dwf21isZ9uhUUu0dQM46dK4ocKxMRrcnmGxydFn6o\nfs3ODJMXOkv2gKXL/FdbEPdDbxzdu8z3yk+W67udM/fW7WbaQ3DO0knu+izKak/3\nT41c5uoXmQ81UNtAzRGzGchNVXMmWuTGOkg6U+0I2Td7K8yvUMWhAWPPpKLtVH9r\nAL5TzjYNR92izdKcz3AjRsI3CTjtpiVABGeX0TcjRSuZB7K9EK56HV+OFNS6I1NP\njdD7FIShyGlqqZdUOkAUZYanbpgeT5N7QL6uuqcGpoTOkalu6kkCAwEAAaNQME4w\nHQYDVR0OBBYEFH5DM/m7oArf4O3peeKO0ZIEkrQPMB8GA1UdIwQYMBaAFH5DM/m7\noArf4O3peeKO0ZIEkrQPMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB\nAHO3NSvDE5jFvMehGGtS8BnFYdFKRIglDMc4niWSzhzOVYRH4WajxdtBWc5fx0ix\nNF/+hVKVhP6AIOQa+++sk+HIi7RvioPPbhjcsVlZe7cUEGrLSSveGouQyc+j0+m6\nJF84kszIl5GGNMTnx0XRPO+g8t6h5LWfnVydgZfpGRRg+WHewk1U2HlvTjIceb0N\ndcoJ8WKJAFWdcuE7VIm4w+vF/DYX/A2Oyzr2+QRhmYSv1cusgAeC1tvH4ap+J1Lg\nUnOu5Kh/FqPLLSwNVQp4Bu7b9QFfqK8Moj84bj88NqRGZgDyqzuTrFxn6FW7dmyA\nyttuAJAEAymk1mipd9+zp38=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tools/rustup-wrapper/.gitignore",
    "content": "target/\n"
  },
  {
    "path": "tools/rustup-wrapper/Cargo.toml",
    "content": "[package]\nname = \"rustup-wrapper\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[dependencies]\nhome = \"0.5\"\n\n[profile.release]\nstrip = true\nlto = true\ncodegen-units = 1\n"
  },
  {
    "path": "tools/rustup-wrapper/src/main.rs",
    "content": "use std::env;\nuse std::path::Path;\nuse std::process::{Command, Stdio};\n\nuse home::cargo_home;\n\n/********************************\n * Why do we need this wrapper?\n ********************************\n *\n * The command `rustup component list` does not work with custom toolchains:\n * > error: toolchain 'magisk' does not support components\n *\n * However, this command is used by several IDEs to determine component\n * availability, such as clippy, rustfmt etc.\n * In this program, we use the output of the command with the nightly\n * channel if any `component` command failed.\n*/\n\nfn main() -> std::io::Result<()> {\n    let exe = env::args().next().unwrap();\n    let exe = Path::new(&exe).file_name().unwrap().to_str().unwrap();\n    let real_exe = cargo_home()?.join(\"bin\").join(exe);\n    let argv: Vec<String> = env::args().skip(1).collect();\n\n    if exe.starts_with(\"rustup\") && argv.iter().any(|s| s == \"component\") {\n        let status = Command::new(&real_exe)\n            .args(&argv)\n            .stdout(Stdio::null())\n            .stderr(Stdio::null())\n            .status()?;\n        if !status.success() {\n            let mut cmd = Command::new(&real_exe);\n            // Hardcode to use the nightly channel\n            cmd.arg(\"+nightly\");\n            // Remove any explicit channel specification\n            cmd.args(argv.iter().filter(|s| !s.starts_with('+')));\n            return cmd.status().map(|_| ());\n        }\n    }\n\n    // Simply pass through\n    Command::new(&real_exe).args(argv.iter()).status().map(|_| ())\n}\n"
  }
]