[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-en.md",
    "content": "---\nname: 'Bug Report'\nabout: 'Please troubleshoot server-side issues and upgrade to the latest client before raising a question.'\ntitle: 'BUG: '\nlabels: ''\nassignees: ''\n\n---\n\n## Describe the problem\n\nExpected behavior:\n\nActual behavior:\n\n## How to reproduce\n\nProvide helpful screenshots, videos, text descriptions, subscription links, etc.\n\n## log\n\nIf you have logs, please upload them. Please see the detailed steps for exporting logs in the documentation."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report-zh_cn.md",
    "content": "---\nname: '问题反馈'\nabout: '在提出问题前请先自行排除服务器端问题和升级到最新客户端。'\ntitle: 'BUG: '\nlabels: ''\nassignees: ''\n\n---\n\n## 描述问题\n\n预期行为：\n\n实际行为：\n\n## 如何复现\n\n提供有帮助的截图，录像，文字说明，订阅链接等。\n\n## 日志\n\n如果有日志，请上传。请在文档内查看导出日志的详细步骤。\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request-en.md",
    "content": "---\nname: 'Feature Request'\nabout: 'Make suggestions for new features of the software'\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## Description suggestions\n\n## Necessity of recommendations\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request-zh_cn.md",
    "content": "---\nname: '功能请求'\nabout: '对软件的新功能提出建议。'\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n## 描述建议\n\n## 建议的必要性\n"
  },
  {
    "path": ".github/workflows/preview.yml",
    "content": "name: Preview Build\non:\n  workflow_dispatch:\n    inputs:\njobs:\n  libcore:\n    name: Native Build (LibCore)\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Golang Status\n        run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status\n      - name: Libcore Status\n        run: git ls-files libcore | xargs cat | sha1sum > libcore_status\n      - name: LibCore Cache\n        id: cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            app/libs/libcore.aar\n          key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}\n      - name: Install Golang\n        if: steps.cache.outputs.cache-hit != 'true'\n        uses: actions/setup-go@v5\n        with:\n          go-version: ^1.25\n      - name: Native Build\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: ./run lib core\n  build:\n    name: Build OSS APK\n    runs-on: ubuntu-latest\n    needs:\n      - libcore\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Golang Status\n        run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status\n      - name: Libcore Status\n        run: git ls-files libcore | xargs cat | sha1sum > libcore_status\n      - name: LibCore Cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            app/libs/libcore.aar\n          key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}\n      - name: Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.gradle\n          key: gradle-oss-${{ hashFiles('**/*.gradle.kts') }}\n      - name: Gradle Build\n        env:\n          BUILD_PLUGIN: none\n        run: |\n          echo \"sdk.dir=${ANDROID_HOME}\" > local.properties\n          echo \"ndk.dir=${ANDROID_HOME}/ndk/25.0.8775105\" >> local.properties\n          export LOCAL_PROPERTIES=\"${{ secrets.LOCAL_PROPERTIES }}\"\n          ./run init action gradle\n          ./gradlew app:assemblePreviewRelease\n          APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')\n          APK=$(dirname $APK)\n          echo \"APK=$APK\" >> $GITHUB_ENV\n      - uses: actions/upload-artifact@v4\n        with:\n          name: APKs\n          path: ${{ env.APK }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release Build\non:\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"Release Tag\"\n        required: true\n      publish:\n        description: \"Publish: If want ignore\"\n        required: false\n      play:\n        description: \"Play: If want ignore\"\n        required: false\njobs:\n  libcore:\n    name: Native Build (LibCore)\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Golang Status\n        run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status\n      - name: Libcore Status\n        run: git ls-files libcore | xargs cat | sha1sum > libcore_status\n      - name: LibCore Cache\n        id: cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            app/libs/libcore.aar\n          key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}\n      - name: Install Golang\n        if: steps.cache.outputs.cache-hit != 'true'\n        uses: actions/setup-go@v5\n        with:\n          go-version: ^1.25\n      - name: Native Build\n        if: steps.cache.outputs.cache-hit != 'true'\n        run: ./run lib core\n  build:\n    name: Build OSS APK\n    runs-on: ubuntu-latest\n    needs:\n      - libcore\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Golang Status\n        run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status\n      - name: Libcore Status\n        run: git ls-files libcore | xargs cat | sha1sum > libcore_status\n      - name: LibCore Cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            app/libs/libcore.aar\n          key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}\n      - name: Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.gradle\n          key: gradle-oss-${{ hashFiles('**/*.gradle.kts') }}\n      - name: Gradle Build\n        env:\n          BUILD_PLUGIN: none\n        run: |\n          echo \"sdk.dir=${ANDROID_HOME}\" > local.properties\n          echo \"ndk.dir=${ANDROID_HOME}/ndk/25.0.8775105\" >> local.properties\n          export LOCAL_PROPERTIES=\"${{ secrets.LOCAL_PROPERTIES }}\"\n          ./run init action gradle\n          ./gradlew app:assembleOssRelease\n          APK=$(find app/build/outputs/apk -name '*arm64-v8a*.apk')\n          APK=$(dirname $APK)\n          echo \"APK=$APK\" >> $GITHUB_ENV\n      - uses: actions/upload-artifact@v4\n        with:\n          name: APKs\n          path: ${{ env.APK }}\n  publish:\n    name: Publish Release\n    if: github.event.inputs.publish != 'y'\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Donwload Artifacts\n        uses: actions/download-artifact@v4\n        with:\n          name: APKs\n          path: artifacts\n      - name: Release\n        run: |\n          wget -O ghr.tar.gz https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz\n          tar -xvf ghr.tar.gz\n          mv ghr*linux_amd64/ghr .\n          mkdir apks\n          find artifacts -name \"*.apk\" -exec cp {} apks \\;\n          ./ghr -delete -t \"${{ github.token }}\" -n \"${{ github.event.inputs.tag }}\" \"${{ github.event.inputs.tag }}\" apks\n  play:\n    name: Build Play Bundle\n    if: github.event.inputs.play != 'y'\n    runs-on: ubuntu-latest\n    needs:\n      - libcore\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Golang Status\n        run: find buildScript libcore/*.sh | xargs cat | sha1sum > golang_status\n      - name: Libcore Status\n        run: git ls-files libcore | xargs cat | sha1sum > libcore_status\n      - name: LibCore Cache\n        uses: actions/cache@v4\n        with:\n          path: |\n            app/libs/libcore.aar\n          key: ${{ hashFiles('.github/workflows/*', 'golang_status', 'libcore_status') }}\n      - name: Gradle cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.gradle\n          key: gradle-play-${{ hashFiles('**/*.gradle.kts') }}\n      - name: Checkout Library\n        run: |\n          git submodule update --init 'app/*'\n      - name: Gradle Build\n        run: |\n          echo \"sdk.dir=${ANDROID_HOME}\" > local.properties\n          echo \"ndk.dir=${ANDROID_HOME}/ndk/25.0.8775105\" >> local.properties\n          export LOCAL_PROPERTIES=\"${{ secrets.LOCAL_PROPERTIES }}\"\n          ./run init action gradle\n          ./gradlew bundlePlayRelease\n      - uses: actions/upload-artifact@v3\n        with:\n          name: AAB\n          path: app/build/outputs/bundle/playRelease/app-play-release.aab\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n.idea\n.vscode\n.DS_Store\nbuild/\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n/app/libs/\n/app/src/main/assets/sing-box\n/service_account_credentials.json\njniLibs/\n/library/libcore_build/\n.idea/deploymentTargetDropDown.xml\n/nkmr\n\n# submodules\n/external\n"
  },
  {
    "path": "AUTHORS",
    "content": "SagerNet was originally created in late 2021, by\nnekohasekai <contact-sagernet@sekai.icu>.\n\nHere is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS --\npeople who have submitted patches, fixed bugs, added translations, and\ngenerally made SagerNet that much better:\n\nhttps://github.com/SagerNet/SagerNet/graphs/contributors\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>."
  },
  {
    "path": "README.md",
    "content": "# NekoBox for Android\n\n[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)\n[![Releases](https://img.shields.io/github/v/release/MatsuriDayo/NekoBoxForAndroid)](https://github.com/MatsuriDayo/NekoBoxForAndroid/releases)\n[![License: GPL-3.0](https://img.shields.io/badge/license-GPL--3.0-orange.svg)](https://www.gnu.org/licenses/gpl-3.0)\n\nsing-box / universal proxy toolchain for Android.\n\n一款使用 sing-box 的 Android 通用代理软件.\n\n## 下载 / Downloads\n\n[![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/NekoBoxForAndroid/total?label=downloads-total&logo=github&style=flat-square)](https://github.com/Matsuridayo/NekoBoxForAndroid/releases)\n\n[GitHub Releases 下载](https://github.com/Matsuridayo/NekoBoxForAndroid/releases)\n\n**Google Play 版本自 2024 年 5 月起已被第三方控制，为非开源版本，请不要下载。**\n\n**The Google Play version has been controlled by a third party since May 2024 and is a non-open\nsource version. Please do not download it.**\n\n## 更新日志 & Telegram 发布频道 / Changelog & Telegram Channel\n\nhttps://t.me/Matsuridayo\n\n## 项目主页 & 文档 / Homepage & Documents\n\nhttps://matsuridayo.github.io\n\n## 支持的代理协议 / Supported Proxy Protocols\n\n* SOCKS (4/4a/5)\n* HTTP(S)\n* SSH\n* Shadowsocks\n* VMess\n* Trojan\n* VLESS\n* AnyTLS\n* ShadowTLS\n* TUIC\n* Hysteria 1/2\n* WireGuard\n* Trojan-Go (trojan-go-plugin)\n* NaïveProxy (naive-plugin)\n* Mieru (mieru-plugin)\n\n请到[这里](https://matsuridayo.github.io/nb4a-plugin/)下载插件以获得完整的代理支持.\n\nPlease visit [here](https://matsuridayo.github.io/nb4a-plugin/) to download plugins for full proxy\nsupports.\n\n## 支持的订阅格式 / Supported Subscription Format\n\n* 一些广泛使用的格式 (如 Shadowsocks, ClashMeta 和 v2rayN)\n* sing-box 出站\n\n仅支持解析出站，即节点。分流规则等信息会被忽略。\n\n* Some widely used formats (like Shadowsocks, ClashMeta and v2rayN)\n* sing-box outbound\n\nOnly resolving outbound, i.e. nodes, is supported. Information such as diversion rules are ignored.\n\n## 捐助 / Donate\n\n<details>\n\n如果这个项目对您有帮助, 可以通过捐赠的方式帮助我们维持这个项目.\n\n捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像, 如果您未被添加到这里,\n欢迎联系我们补充.\n\nDonations of 50 USD or more can display your avatar on\nthe [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please\ncontact us to add it.\n\nUSDT TRC20\n\n`TRhnA7SXE5Sap5gSG3ijxRmdYFiD4KRhPs`\n\nXMR\n\n`49bwESYQjoRL3xmvTcjZKHEKaiGywjLYVQJMUv79bXonGiyDCs8AzE3KiGW2ytTybBCpWJUvov8SjZZEGg66a4e59GXa6k5`\n\n</details>\n\n## Credits\n\nCore:\n\n- [SagerNet/sing-box](https://github.com/SagerNet/sing-box)\n\nAndroid GUI:\n\n- [shadowsocks/shadowsocks-android](https://github.com/shadowsocks/shadowsocks-android)\n- [SagerNet/SagerNet](https://github.com/SagerNet/SagerNet)\n\nWeb Dashboard:\n\n- [Yacd-meta](https://github.com/MetaCubeX/Yacd-meta)\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "@file:Suppress(\"UnstableApiUsage\")\n\nplugins {\n    id(\"com.android.application\")\n    id(\"kotlin-android\")\n    id(\"com.google.devtools.ksp\")\n    id(\"kotlin-parcelize\")\n}\n\nsetupApp()\n\nandroid {\n    compileOptions {\n        isCoreLibraryDesugaringEnabled = true\n    }\n    ksp {\n        arg(\"room.incremental\", \"true\")\n        arg(\"room.schemaLocation\", \"$projectDir/schemas\")\n    }\n    bundle {\n        language {\n            enableSplit = false\n        }\n    }\n    buildFeatures {\n        buildConfig = true\n        viewBinding = true\n        aidl = true\n    }\n    namespace = \"io.nekohasekai.sagernet\"\n    packaging {\n        jniLibs {\n            useLegacyPackaging = true\n        }\n    }\n    androidResources {\n        generateLocaleConfig = true\n    }\n}\n\ndependencies {\n\n    implementation(fileTree(\"libs\"))\n\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4\")\n    implementation(\"androidx.core:core-ktx:1.9.0\")\n    implementation(\"androidx.recyclerview:recyclerview:1.3.0\")\n    implementation(\"androidx.activity:activity-ktx:1.10.1\")\n    implementation(\"androidx.fragment:fragment-ktx:1.5.6\")\n    implementation(\"androidx.browser:browser:1.5.0\")\n    implementation(\"androidx.swiperefreshlayout:swiperefreshlayout:1.1.0\")\n    implementation(\"androidx.constraintlayout:constraintlayout:2.1.4\")\n    implementation(\"androidx.navigation:navigation-fragment-ktx:2.5.3\")\n    implementation(\"androidx.navigation:navigation-ui-ktx:2.5.3\")\n    implementation(\"androidx.preference:preference-ktx:1.2.0\")\n    implementation(\"androidx.appcompat:appcompat:1.6.1\")\n    implementation(\"androidx.work:work-runtime-ktx:2.8.1\")\n    implementation(\"androidx.work:work-multiprocess:2.8.1\")\n\n    implementation(\"com.google.android.material:material:1.8.0\")\n    implementation(\"com.google.code.gson:gson:2.9.0\")\n\n    implementation(\"com.github.jenly1314:zxing-lite:2.1.1\")\n    implementation(\"com.blacksquircle.ui:editorkit:2.6.0\")\n    implementation(\"com.blacksquircle.ui:language-base:2.6.0\")\n    implementation(\"com.blacksquircle.ui:language-json:2.6.0\")\n\n    implementation(\"com.squareup.okhttp3:okhttp:5.0.0-alpha.3\")\n    implementation(\"org.yaml:snakeyaml:1.30\")\n    implementation(\"com.github.daniel-stoneuk:material-about-library:3.2.0-rc01\")\n    implementation(\"com.jakewharton:process-phoenix:2.1.2\")\n    implementation(\"com.esotericsoftware:kryo:5.2.1\")\n    implementation(\"com.google.guava:guava:31.0.1-android\")\n    implementation(\"org.ini4j:ini4j:0.5.4\")\n\n    implementation(\"com.simplecityapps:recyclerview-fastscroll:2.0.1\") {\n        exclude(group = \"androidx.recyclerview\")\n        exclude(group = \"androidx.appcompat\")\n    }\n\n    implementation(\"androidx.room:room-runtime:2.6.1\")\n    ksp(\"androidx.room:room-compiler:2.6.1\")\n    implementation(\"androidx.room:room-ktx:2.6.1\")\n    implementation(\"com.github.MatrixDev.Roomigrant:RoomigrantLib:0.3.4\")\n    ksp(\"com.github.MatrixDev.Roomigrant:RoomigrantCompiler:0.3.4\")\n\n    coreLibraryDesugaring(\"com.android.tools:desugar_jdk_libs:2.0.3\")\n}\n"
  },
  {
    "path": "app/executableSo/.gitignore",
    "content": "*.so\n"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "-repackageclasses ''\n-allowaccessmodification\n\n-keep class io.nekohasekai.sagernet.** { *;}\n-keep class moe.matsuri.nb4a.** { *;}\n\n# Clean Kotlin\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);\n    static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);\n    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);\n    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);\n    static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);\n    static void checkFieldIsNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNull(java.lang.Object);\n    static void checkNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNullParameter(java.lang.Object, java.lang.String);\n    static void throwUninitializedPropertyAccessException(java.lang.String);\n}\n\n# ini4j\n-keep public class org.ini4j.spi.** { <init>(); }\n\n# SnakeYaml\n-keep class org.yaml.snakeyaml.** { *; }\n\n-dontobfuscate\n-keepattributes SourceFile\n\n-dontwarn java.beans.BeanInfo\n-dontwarn java.beans.FeatureDescriptor\n-dontwarn java.beans.IntrospectionException\n-dontwarn java.beans.Introspector\n-dontwarn java.beans.PropertyDescriptor\n-dontwarn java.beans.Transient\n-dontwarn java.beans.VetoableChangeListener\n-dontwarn java.beans.VetoableChangeSupport\n-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl\n-dontwarn org.bouncycastle.jce.provider.BouncyCastleProvider\n-dontwarn org.bouncycastle.jsse.BCSSLParameters\n-dontwarn org.bouncycastle.jsse.BCSSLSocket\n-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider\n-dontwarn org.openjsse.javax.net.ssl.SSLParameters\n-dontwarn org.openjsse.javax.net.ssl.SSLSocket\n-dontwarn org.openjsse.net.ssl.OpenJSSE\n-dontwarn java.beans.PropertyVetoException\n"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/1.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 1,\n    \"identityHash\": \"f66fd943df1d9e86d281a2e32c9fdd47\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f66fd943df1d9e86d281a2e32c9fdd47')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/2.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 2,\n    \"identityHash\": \"9ec160533656482a17cbd563e9e3e416\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSelector\",\n            \"columnName\": \"isSelector\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"frontProxy\",\n            \"columnName\": \"frontProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"landingProxy\",\n            \"columnName\": \"landingProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shadowTLSBean\",\n            \"columnName\": \"shadowTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9ec160533656482a17cbd563e9e3e416')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/3.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 3,\n    \"identityHash\": \"cff00d0142d9e53d2ca24a6a55cd213c\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSelector\",\n            \"columnName\": \"isSelector\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"frontProxy\",\n            \"columnName\": \"frontProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"landingProxy\",\n            \"columnName\": \"landingProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"mieruBean\",\n            \"columnName\": \"mieruBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shadowTLSBean\",\n            \"columnName\": \"shadowTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cff00d0142d9e53d2ca24a6a55cd213c')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/4.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 4,\n    \"identityHash\": \"cff00d0142d9e53d2ca24a6a55cd213c\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSelector\",\n            \"columnName\": \"isSelector\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"frontProxy\",\n            \"columnName\": \"frontProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"landingProxy\",\n            \"columnName\": \"landingProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"mieruBean\",\n            \"columnName\": \"mieruBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shadowTLSBean\",\n            \"columnName\": \"shadowTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cff00d0142d9e53d2ca24a6a55cd213c')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/5.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 5,\n    \"identityHash\": \"1dbf667053726c13d139a4d83c41f895\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSelector\",\n            \"columnName\": \"isSelector\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"frontProxy\",\n            \"columnName\": \"frontProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"landingProxy\",\n            \"columnName\": \"landingProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"mieruBean\",\n            \"columnName\": \"mieruBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shadowTLSBean\",\n            \"columnName\": \"shadowTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"anyTLSBean\",\n            \"columnName\": \"anyTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '1dbf667053726c13d139a4d83c41f895')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.SagerDatabase/6.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 6,\n    \"identityHash\": \"3d3db9106a89d6f20ef3fde6e81dbaa9\",\n    \"entities\": [\n      {\n        \"tableName\": \"proxy_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `userOrder` INTEGER NOT NULL, `ungrouped` INTEGER NOT NULL, `name` TEXT, `type` INTEGER NOT NULL, `subscription` BLOB, `order` INTEGER NOT NULL, `isSelector` INTEGER NOT NULL, `frontProxy` INTEGER NOT NULL, `landingProxy` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ungrouped\",\n            \"columnName\": \"ungrouped\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"subscription\",\n            \"columnName\": \"subscription\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isSelector\",\n            \"columnName\": \"isSelector\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"frontProxy\",\n            \"columnName\": \"frontProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"landingProxy\",\n            \"columnName\": \"landingProxy\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"proxy_entities\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `groupId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `status` INTEGER NOT NULL, `ping` INTEGER NOT NULL, `uuid` TEXT NOT NULL, `error` TEXT, `socksBean` BLOB, `httpBean` BLOB, `ssBean` BLOB, `vmessBean` BLOB, `trojanBean` BLOB, `trojanGoBean` BLOB, `mieruBean` BLOB, `naiveBean` BLOB, `hysteriaBean` BLOB, `tuicBean` BLOB, `sshBean` BLOB, `wgBean` BLOB, `shadowTLSBean` BLOB, `anyTLSBean` BLOB, `chainBean` BLOB, `nekoBean` BLOB, `configBean` BLOB)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tx\",\n            \"columnName\": \"tx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rx\",\n            \"columnName\": \"rx\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"status\",\n            \"columnName\": \"status\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ping\",\n            \"columnName\": \"ping\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"uuid\",\n            \"columnName\": \"uuid\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"error\",\n            \"columnName\": \"error\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"socksBean\",\n            \"columnName\": \"socksBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"httpBean\",\n            \"columnName\": \"httpBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ssBean\",\n            \"columnName\": \"ssBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"vmessBean\",\n            \"columnName\": \"vmessBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanBean\",\n            \"columnName\": \"trojanBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"trojanGoBean\",\n            \"columnName\": \"trojanGoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"mieruBean\",\n            \"columnName\": \"mieruBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"naiveBean\",\n            \"columnName\": \"naiveBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"hysteriaBean\",\n            \"columnName\": \"hysteriaBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tuicBean\",\n            \"columnName\": \"tuicBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sshBean\",\n            \"columnName\": \"sshBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wgBean\",\n            \"columnName\": \"wgBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shadowTLSBean\",\n            \"columnName\": \"shadowTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"anyTLSBean\",\n            \"columnName\": \"anyTLSBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chainBean\",\n            \"columnName\": \"chainBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"nekoBean\",\n            \"columnName\": \"nekoBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"configBean\",\n            \"columnName\": \"configBean\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"groupId\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"groupId\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `groupId` ON `${TABLE_NAME}` (`groupId`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `config` TEXT NOT NULL DEFAULT '', `userOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `domains` TEXT NOT NULL, `ip` TEXT NOT NULL, `port` TEXT NOT NULL, `sourcePort` TEXT NOT NULL, `network` TEXT NOT NULL, `source` TEXT NOT NULL, `protocol` TEXT NOT NULL, `outbound` INTEGER NOT NULL, `packages` TEXT NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"userOrder\",\n            \"columnName\": \"userOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"domains\",\n            \"columnName\": \"domains\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ip\",\n            \"columnName\": \"ip\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"port\",\n            \"columnName\": \"port\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourcePort\",\n            \"columnName\": \"sourcePort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"network\",\n            \"columnName\": \"network\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"source\",\n            \"columnName\": \"source\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"protocol\",\n            \"columnName\": \"protocol\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"outbound\",\n            \"columnName\": \"outbound\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"packages\",\n            \"columnName\": \"packages\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3d3db9106a89d6f20ef3fde6e81dbaa9')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.nekohasekai.sagernet.database.preference.PublicDatabase/1.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 1,\n    \"identityHash\": \"f1aab1fb633378621635c344dbc8ac7b\",\n    \"entities\": [\n      {\n        \"tableName\": \"KeyValuePair\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"valueType\",\n            \"columnName\": \"valueType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1aab1fb633378621635c344dbc8ac7b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/moe.matsuri.nb4a.TempDatabase/1.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 1,\n    \"identityHash\": \"f1aab1fb633378621635c344dbc8ac7b\",\n    \"entities\": [\n      {\n        \"tableName\": \"KeyValuePair\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"valueType\",\n            \"columnName\": \"valueType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"BLOB\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f1aab1fb633378621635c344dbc8ac7b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/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    android:installLocation=\"internalOnly\">\n\n    <uses-sdk tools:overrideLibrary=\"com.google.zxing.client.android, com.blacksquircle.ui.editorkit\" />\n\n    <permission\n        android:name=\"${applicationId}.SERVICE\"\n        android:protectionLevel=\"signature\" />\n\n    <uses-permission android:name=\"${applicationId}.SERVICE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED\" />\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.EXPAND_STATUS_BAR\" />\n\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"PackageVisibilityPolicy\" />\n    <uses-permission android:name=\"com.android.permission.GET_INSTALLED_APPS\" />\n\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n    <uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />\n\n    <uses-feature\n        android:name=\"android.software.leanback\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.touchscreen\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.camera\"\n        android:required=\"false\" />\n    <uses-feature\n        android:name=\"android.hardware.camera.autofocus\"\n        android:required=\"false\" />\n\n    <queries>\n        <intent>\n            <action android:name=\"io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN\" />\n        </intent>\n    </queries>\n\n    <application\n        android:name=\"io.nekohasekai.sagernet.SagerNet\"\n        android:allowBackup=\"true\"\n        android:autoRevokePermissions=\"allowed\"\n        android:banner=\"@mipmap/ic_launcher\"\n        android:dataExtractionRules=\"@xml/backup_rules\"\n        android:fullBackupContent=\"@xml/backup_descriptor\"\n        android:fullBackupOnly=\"true\"\n        android:hardwareAccelerated=\"true\"\n        android:hasFragileUserData=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:largeHeap=\"true\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:roundIcon=\"@mipmap/ic_launcher\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.Start\">\n        <meta-data\n            android:name=\"android.app.shortcuts\"\n            android:resource=\"@xml/shortcuts\" />\n\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.BlankActivity\"\n            android:configChanges=\"uiMode\" />\n\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.MainActivity\"\n            android:configChanges=\"uiMode\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LEANBACK_LAUNCHER\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE_PREFERENCES\" />\n            </intent-filter>\n\n            <intent-filter android:label=\"@string/subscription_import\">\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data\n                    android:host=\"subscription\"\n                    android:scheme=\"sn\" />\n\n            </intent-filter>\n\n            <intent-filter android:label=\"@string/subscription_import\">\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data\n                    android:host=\"install-config\"\n                    android:scheme=\"clash\" />\n            </intent-filter>\n\n            <intent-filter android:label=\"@string/profile_import\">\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"sn\" />\n                <data android:scheme=\"ss\" />\n                <data android:scheme=\"ssr\" />\n                <data android:scheme=\"socks\" />\n                <data android:scheme=\"socks4\" />\n                <data android:scheme=\"socksa\" />\n                <data android:scheme=\"sock5\" />\n                <data android:scheme=\"vmess\" />\n                <data android:scheme=\"trojan\" />\n                <data android:scheme=\"trojan-go\" />\n                <data android:scheme=\"naive+https\" />\n                <data android:scheme=\"naive+quic\" />\n                <data android:scheme=\"hysteria\" />\n\n            </intent-filter>\n\n            <meta-data\n                android:name=\"android.app.shortcuts\"\n                android:resource=\"@xml/shortcuts\" />\n        </activity>\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.VpnRequestActivity\"\n            android:excludeFromRecents=\"true\"\n            android:taskAffinity=\"\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.ConfigEditActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.SocksSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.HttpSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.ShadowsocksSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.VMessSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.TrojanSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.TrojanGoSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.MieruSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.NaiveSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.HysteriaSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.SSHSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.WireGuardSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.TuicSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.profile.ChainSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"moe.matsuri.nb4a.proxy.config.ConfigSettingActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.GroupSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.RouteSettingsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.AssetsActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.AppListActivity\"\n            android:configChanges=\"uiMode\" />\n        <activity\n            android:name=\".QuickToggleShortcut\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/quick_toggle\"\n            android:launchMode=\"singleTask\"\n            android:process=\":bg\"\n            android:taskAffinity=\"\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.CREATE_SHORTCUT\" />\n            </intent-filter>\n        </activity>\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.QuickEnableShortcut\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/quick_enable\"\n            android:launchMode=\"singleTask\"\n            android:process=\":bg\"\n            android:taskAffinity=\"\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.QuickDisableShortcut\"\n            android:excludeFromRecents=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/quick_disable\"\n            android:launchMode=\"singleTask\"\n            android:process=\":bg\"\n            android:taskAffinity=\"\"\n            android:theme=\"@android:style/Theme.Translucent.NoTitleBar\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.AppManagerActivity\"\n            android:configChanges=\"uiMode\"\n            android:excludeFromRecents=\"true\"\n            android:label=\"@string/proxied_apps\"\n            android:launchMode=\"singleTask\"\n            android:parentActivityName=\".ui.MainActivity\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.ScannerActivity\"\n            android:configChanges=\"uiMode\"\n            android:excludeFromRecents=\"true\"\n            android:label=\"@string/add_profile_methods_scan_qr_code\"\n            android:launchMode=\"singleTask\"\n            android:parentActivityName=\"io.nekohasekai.sagernet.ui.MainActivity\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.ProfileSelectActivity\"\n            android:configChanges=\"uiMode\"\n            android:label=\"@string/select_profile\"\n            android:launchMode=\"singleTask\"\n            android:parentActivityName=\"io.nekohasekai.sagernet.ui.MainActivity\" />\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.StunActivity\"\n            android:configChanges=\"uiMode\"\n            android:launchMode=\"singleTask\"\n            android:parentActivityName=\"io.nekohasekai.sagernet.ui.MainActivity\" />\n\n        <activity\n            android:name=\"io.nekohasekai.sagernet.ui.SwitchActivity\"\n            android:configChanges=\"uiMode\"\n            android:excludeFromRecents=\"true\"\n            android:launchMode=\"singleInstance\"\n            android:theme=\"@style/Theme.SagerNet.Dialog\" />\n\n        <service\n            android:name=\"io.nekohasekai.sagernet.bg.ProxyService\"\n            android:exported=\"false\"\n            android:foregroundServiceType=\"systemExempted\"\n            android:process=\":bg\"\n            tools:ignore=\"ForegroundServicePermission\" />\n\n        <service\n            android:name=\"io.nekohasekai.sagernet.bg.VpnService\"\n            android:exported=\"false\"\n            android:foregroundServiceType=\"systemExempted\"\n            android:label=\"@string/app_name\"\n            android:permission=\"android.permission.BIND_VPN_SERVICE\"\n            android:process=\":bg\"\n            tools:ignore=\"ForegroundServicePermission\">\n\n            <intent-filter>\n                <action android:name=\"android.net.VpnService\" />\n            </intent-filter>\n        </service>\n\n        <service\n            android:name=\"io.nekohasekai.sagernet.bg.TileService\"\n            android:exported=\"true\"\n            android:foregroundServiceType=\"systemExempted\"\n            android:icon=\"@drawable/ic_service_active\"\n            android:label=\"@string/tile_title\"\n            android:permission=\"android.permission.BIND_QUICK_SETTINGS_TILE\"\n            android:process=\":bg\"\n            tools:ignore=\"ForegroundServicePermission\"\n            tools:targetApi=\"n\">\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE\" />\n            </intent-filter>\n            <meta-data\n                android:name=\"android.service.quicksettings.TOGGLEABLE_TILE\"\n                android:value=\"true\" />\n        </service>\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.cache\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/cache_paths\" />\n        </provider>\n\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            tools:node=\"remove\" />\n\n        <receiver\n            android:name=\"io.nekohasekai.sagernet.BootReceiver\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:process=\":bg\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n                <action android:name=\"android.intent.action.LOCKED_BOOT_COMPLETED\" />\n                <action android:name=\"android.intent.action.MY_PACKAGE_REPLACED\" />\n            </intent-filter>\n        </receiver>\n\n        <service\n            android:name=\"androidx.room.MultiInstanceInvalidationService\"\n            android:process=\":bg\" />\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetService.aidl",
    "content": "package io.nekohasekai.sagernet.aidl;\n\nimport io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback;\n\ninterface ISagerNetService {\n  int getState();\n  String getProfileName();\n\n  void registerCallback(in ISagerNetServiceCallback cb, int id);\n  oneway void unregisterCallback(in ISagerNetServiceCallback cb);\n\n  int urlTest();\n}\n"
  },
  {
    "path": "app/src/main/aidl/io/nekohasekai/sagernet/aidl/ISagerNetServiceCallback.aidl",
    "content": "package io.nekohasekai.sagernet.aidl;\n\nimport io.nekohasekai.sagernet.aidl.SpeedDisplayData;\nimport io.nekohasekai.sagernet.aidl.TrafficData;\n\noneway interface ISagerNetServiceCallback {\n  void stateChanged(int state, String profileName, String msg);\n  void missingPlugin(String profileName, String pluginName);\n  void cbSpeedUpdate(in SpeedDisplayData stats);\n  void cbTrafficUpdate(in TrafficData stats);\n  void cbSelectorUpdate(long id);\n}\n"
  },
  {
    "path": "app/src/main/aidl/io/nekohasekai/sagernet/aidl/SpeedDisplayData.aidl",
    "content": "package io.nekohasekai.sagernet.aidl;\n\nparcelable SpeedDisplayData;\n"
  },
  {
    "path": "app/src/main/aidl/io/nekohasekai/sagernet/aidl/TrafficData.aidl",
    "content": "package io.nekohasekai.sagernet.aidl;\n\nparcelable TrafficData;\n"
  },
  {
    "path": "app/src/main/assets/LICENSE",
    "content": "Copyright (C) 2021 by nekohasekai\n<contact-sagernet@sekai.icu>\n\nThis program is free software: you can\nredistribute it and/or modify it under\nthe terms of the GNU General Public License\nas published by the Free Software Foundation,\neither version 3 of the License,\nor (at your option) any later version.\n\nThis program is distributed in the hope\nthat it will be useful, but WITHOUT ANY WARRANTY;\nwithout even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\nSee the GNU General Public License for more details.\n\nYou should have received a copy of the\nGNU General Public License along with this program.\nIf not, see <http://www.gnu.org/licenses/>."
  },
  {
    "path": "app/src/main/assets/proxy_packagename.txt",
    "content": "amanita_design.samorost3.gp\nandroid\nau.com.shiftyjelly.pocketcasts\nbbc.mobile.news.ww\nbe.mygod.vpnhotspot\nch.protonmail.android\ncm.aptoide.pt\nco.wanqu.android\ncom.alphainventor.filemanager\ncom.amazon.kindle\ncom.amazon.mshop.android.shopping\ncom.android.chrome\ncom.android.providers.downloads\ncom.android.providers.downloads.ui\ncom.android.providers.telephony\ncom.android.settings\ncom.android.vending\ncom.android6park.m6park\ncom.apkpure.aegon\ncom.apkupdater\ncom.app.pornhub\ncom.arthurivanets.owly\ncom.asahi.tida.tablet\ncom.authy.authy\ncom.avmovie\ncom.ballistiq.artstation\ncom.binance.dev\ncom.bitly.app\ncom.brave.browser\ncom.brave.browser_beta\ncom.breel.wallpapers18\ncom.bvanced.android.youtube\ncom.chrome.beta\ncom.chrome.canary\ncom.chrome.dev\ncom.cl.newt66y\ncom.cradle.iitc_mobile\norg.exarhteam.iitc_mobile\ncom.cygames.shadowverse\ncom.dcard.freedom\ncom.devhd.feedly\ncom.devolver.reigns2\ncom.discord\ncom.downloader.video.tumblr\ncom.driverbrowser\ncom.dropbox.android\ncom.duolingo\ncom.duckduckgo.mobile.android\ncom.dv.adm\ncom.estrongs.android.pop\ncom.estrongs.android.pop.pro\ncom.evernote\ncom.facebook.katana\ncom.facebook.lite\ncom.facebook.mlite\ncom.facebook.orca\ncom.facebook.services\ncom.facebook.system\ncom.fastaccess.github\ncom.felixfilip.scpae\ncom.fireproofstudios.theroom4\ncom.firstrowria.pushnotificationtester\ncom.flyersoft.moonreaderp\ncom.fooview.android.fooview\ncom.fvd.eversync\ncom.gameloft.android.anmp.glofta8hm\ncom.gameloft.android.anmp.glofta9hm\ncom.gianlu.aria2app\ncom.github.yeriomin.yalpstore\ncom.google.android.apps.adm\ncom.google.android.apps.books\ncom.google.android.apps.docs\ncom.google.android.apps.docs.editors.sheets\ncom.google.android.apps.docs.editors.docs\ncom.google.android.apps.docs.editors.slides\ncom.google.android.apps.fitness\ncom.google.android.apps.googleassistant\ncom.google.android.apps.googlevoice\ncom.google.android.apps.hangoutsdialer\ncom.google.android.apps.inbox\ncom.google.android.apps.magazines\ncom.google.android.apps.maps\ncom.google.android.apps.nbu.files\ncom.google.android.apps.paidtasks\ncom.google.android.apps.pdfviewer\ncom.google.android.apps.photos\ncom.google.android.apps.plus\ncom.google.android.apps.translate\ncom.google.android.gm\ncom.google.android.gms\ncom.google.android.gms.setup\ncom.google.android.googlequicksearchbox\ncom.google.android.gsf\ncom.google.android.gsf.login\ncom.google.android.ims\ncom.google.android.inputmethod.latin\ncom.google.android.instantapps.supervisor\ncom.google.android.keep\ncom.google.android.music\ncom.google.android.ogyoutube\ncom.google.android.partnersetup\ncom.google.android.play.games\ncom.google.android.street\ncom.google.android.syncadapters.calendar\ncom.google.android.syncadapters.contacts\ncom.google.android.talk\ncom.google.android.tts\ncom.google.android.videos\ncom.google.android.youtube\ncom.google.ar.lens\ncom.google.android.apps.authenticator2\ncom.hochan.coldsoup\ncom.ifttt.ifttt\ncom.imgur.mobile\ncom.innologica.inoreader\ncom.instagram.android\ncom.instagram.lite\ncom.instapaper.android\ncom.jarvanh.vpntether\ncom.kapp.youtube.final\ncom.klinker.android.twitter_l\ncom.lastpass.lpandroid\ncom.linecorp.linelite\ncom.lingodeer\ncom.ltnnews.news\ncom.mediapods.tumbpods\ncom.mgoogle.android.gms\ncom.microsoft.emmx\ncom.microsoft.office.powerpoint\ncom.microsoft.skydrive\ncom.mixplorer\ncom.msd.consumerchinese\ncom.msd.professionalchinese\ncom.mss2011c.sharehelper\ncom.netflix.mediaclient\ncom.newin.nplayer.pro\ncom.nianticlabs.ingress.prime.qa\ncom.nianticproject.ingress\ncom.ninefolders.hd3\ncom.ninegag.android.app\ncom.nintendo.zara\ncom.nytimes.cn\ncom.oasisfeng.island\ncom.ocnt.liveapp.hw\ncom.orekie.search\ncom.patreon.android\ncom.paypal.android.p2pmobile\ncom.perol.asdpl.pixivez\ncom.pinterest\ncom.popularapp.periodcalendar\ncom.popularapp.videodownloaderforinstagram\ncom.pushbullet.android\ncom.quoord.tapatalkpro.activity\ncom.quora.android\ncom.rayark.cytus2\ncom.rayark.implosion\ncom.rayark.pluto\ncom.reddit.frontpage\ncom.resilio.sync\ncom.rhmsoft.edit\ncom.rubenmayayo.reddit\ncom.sec.android.app.sbrowser\ncom.sec.android.app.sbrowser.beta\ncom.shanga.walli\ncom.simplehabit.simplehabitapp\ncom.slack\ncom.snaptube.premium\ncom.sololearn\ncom.sonelli.juicessh\ncom.sparkslab.dcardreader\ncom.spotify.music\ncom.spotify.lite\ncom.tencent.huatuo\ncom.termux\ncom.teslacoilsw.launcher\ncom.theinitium.news\ncom.thomsonreuters.reuters\ncom.thunkable.android.hritvik00.freenom\ncom.topjohnwu.magisk\ncom.tripadvisor.tripadvisor\ncom.tumblr\ncom.twitter.android\ncom.u91porn\ncom.u9porn\ncom.ubisoft.dance.justdance2015companion\ncom.udn.news\ncom.utopia.pxview\ncom.valvesoftware.android.steam.community\ncom.vanced.manager\ncom.vanced.android.youtube\ncom.vanced.android.apps.youtube.music\ncom.mgoogle.android.gms\ncom.vimeo.android.videoapp\ncom.vivaldi.browser\ncom.vivaldi.browser.snapshot\ncom.vkontakte.android\ncom.whatsapp\ncom.wire\ncom.wuxiangai.refactor\ncom.xda.labs\ncom.xvideos.app\ncom.yahoo.mobile.client.android.superapp\ncom.yandex.browser\ncom.yandex.browser.beta\ncom.yandex.browser.alpha\ncom.z28j.feel\ncom.zhiliaoapp.musically\ncon.medium.reader\nde.apkgrabber\nde.robv.android.xposed.installer\ndk.tacit.android.foldersync.full\nes.rafalense.telegram.themes\nes.rafalense.themes\nflipboard.app\nfm.moon.app\nfr.gouv.etalab.mastodon\ngithub.tornaco.xposedmoduletest\nidm.internet.download.manager\nidm.internet.download.manager.plus\nio.github.javiewer\nio.github.skyhacker2.magnetsearch\nio.va.exposed\nit.mvilla.android.fenix2\njp.bokete.app.android\njp.naver.line.android\njp.pxv.android\nluo.speedometergpspro\nm.cna.com.tw.App\nmark.via.gp\nme.tshine.easymark\nnet.teeha.android.url_shortener\nnet.tsapps.appsales\nonion.fire\norg.fdroid.fdroid\norg.freedownloadmanager.fdm\norg.kustom.widget\norg.mozilla.fennec_aurora\norg.mozilla.fenix\norg.mozilla.fenix.nightly\norg.mozilla.firefox\norg.mozilla.firefox_beta\norg.mozilla.focus\norg.schabi.newpipe\norg.telegram.messenger\norg.telegram.multi\norg.telegram.plus\norg.thunderdog.challegram\norg.torproject.android\norg.torproject.torbrowser_alpha\norg.wikipedia\norg.xbmc.kodi\npl.zdunex25.updater\ntv.twitch.android.app\ntw.com.gamer.android.activecenter\nvideodownloader.downloadvideo.downloader\nuk.co.bbc.learningenglish\ncom.ted.android\nde.danoeh.antennapod\ncom.kiwibrowser.browser\nnekox.messenger\ncom.nextcloud.client\ncom.aurora.store\ncom.aurora.adroid\nchat.simplex.app\nim.vector.app\nnetwork.loki.messenger\neu.siacs.conversations\nxyz.nextalone.nagram\nde.danoeh.antennapod\nnet.programmierecke.radiodroid2\nim.fdx.v2ex\nml.docilealligator.infinityforreddit\ncom.bytemyth.ama\napp.vanadium.browser\ncom.cakewallet.cake_wallet\norg.purplei2p.i2pd\ndk.tacit.android.foldersync.lite\ncom.nononsenseapps.feeder\ncom.m2049r.xmrwallet\ncom.paypal.android.p2pmobile\ncom.google.android.apps.googlevoice\ncom.readdle.spark\norg.torproject.torbrowser\ncom.deepl.mobiletranslator\ncom.microsoft.bing\ncom.keylesspalace.tusky\ncom.ottplay.ottplay\nru.iptvremote.android.iptv.pro\njp.naver.line.android\ncom.xmflsct.app.tooot\ncom.forem.android\napp.revanced.android.youtube\napp.rvx.android.youtube\napp.rvx.android.apps.youtube.music\ncom.mgoogle.android.gms\ncom.pionex.client\nvip.mytokenpocket\nim.token.app\ncom.linekong.mars24\ncom.feixiaohao\ncom.aicoin.appandroid\ncom.binance.dev\ncom.kraken.trade\ncom.okinc.okex.gp\ncom.authy.authy\nair.com.rosettastone.mobile.CoursePlayer\ncom.blizzard.bma\ncom.amazon.kindle\ncom.google.android.apps.fitness\nnet.tsapps.appsales\ncom.wemesh.android\ncom.google.android.apps.googleassistant\nallen.town.focus.reader\nme.hyliu.fluent_reader_lite\ncom.aljazeera.mobile\ncom.ft.news\nde.marmaro.krt.ffupdater\nmyradio.radio.fmradio.liveradio.radiostation\ncom.google.earth\neu.kanade.tachiyomi.j2k\ncom.audials\ncom.microsoft.skydrive\ncom.mb.android.tg\ncom.melodis.midomiMusicIdentifier.freemium\ncom.foxnews.android\nch.threema.app\ncom.briarproject.briar.android\nfoundation.e.apps\ncom.valvesoftware.android.steam.friendsui\ncom.imback.yeetalk\nso.onekey.app.wallet\ncom.xc3fff0e.xmanager\nmeditofoundation.medito\ncom.picol.client\ncom.streetwriters.notesnook\nshanghai.panewsApp.com\norg.coursera.android\ncom.positron_it.zlib\ncom.blizzard.messenger\ncom.javdb.javrocket\ncom.picacomic.fregata\ncom.fxl.chacha\nme.proton.android.drive\ncom.lastpass.lpandroid\ncom.tradingview.tradingviewapp\ncom.deviantart.android.damobile\ncom.fusionmedia.investing\ncom.ewa.ewaapp\ncom.duolingo\ncom.hellotalk\nio.github.huskydg.magisk\ncom.jsy.xpgbox\ncom.hostloc.app.hostloc\ncom.dena.pokota\ncom.vitorpamplona.amethyst\ncom.zhiliaoapp.musically\nus.spotco.fennec_dos\ncom.fongmi.android.tv\ncom.pocketprep.android.itcybersecurity\ncom.cloudtv\ncom.glassdoor.app\ncom.indeed.android.jobsearch\ncom.linkedin.android\ncom.github.tvbox.osc.bh\ncom.example.douban\ncom.sipnetic.app\ncom.microsoft.rdc.androidx\norg.zwanoo.android.speedtest\ncom.sonelli.juicessh\ncom.scmp.newspulse\norg.lsposed.manager\nmnn.Android\ncom.thomsonretuers.reuters\ncom.guardian\ncom.ttxapps.onesyncv2\norg.fcitx.fcitx5.android.updater\ncom.instagram.barcelona\ncom.deniscerri.ytdl\njp.pokemon.pokemonsleep\ncom.github.android\ncom.openai.chatgpt\nmega.privacy.android.app\ncom.taptap.global\ntw.com.gamer.android.animad\ncom.microsoft.copilot\ncom.google.android.apps.aiwallpapers\nai.x.grok\ncom.google.android.apps.weather\ncom.metrolist.music\ncom.google.android.apps.youtube.creator"
  },
  {
    "path": "app/src/main/assets/yacd.version.txt",
    "content": "3"
  },
  {
    "path": "app/src/main/java/com/github/shadowsocks/plugin/Utils.kt",
    "content": "@file:JvmName(\"Utils\")\n\npackage com.github.shadowsocks.plugin\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\nclass Empty : Parcelable"
  },
  {
    "path": "app/src/main/java/com/github/shadowsocks/plugin/fragment/AlertDialogFragment.kt",
    "content": "package com.github.shadowsocks.plugin.fragment\n\nimport android.app.Activity\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.os.Parcelable\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatDialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.setFragmentResult\nimport androidx.fragment.app.setFragmentResultListener\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\n\n/**\n * Based on: https://android.googlesource.com/platform/\npackages/apps/ExactCalculator/+/8c43f06/src/com/android/calculator2/AlertDialogFragment.java\n */\nabstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable?> :\n    AppCompatDialogFragment(), DialogInterface.OnClickListener {\n    companion object {\n        private const val KEY_RESULT = \"result\"\n        private const val KEY_ARG = \"arg\"\n        private const val KEY_RET = \"ret\"\n        private const val KEY_WHICH = \"which\"\n\n        fun <Ret : Parcelable> setResultListener(fragment: Fragment, requestKey: String,\n                                                 listener: (Int, Ret?) -> Unit) {\n            fragment.setFragmentResultListener(requestKey) { _, bundle ->\n                listener(bundle.getInt(KEY_WHICH, Activity.RESULT_CANCELED), bundle.getParcelable(KEY_RET))\n            }\n        }\n        inline fun <reified T : AlertDialogFragment<*, Ret>, Ret : Parcelable?> setResultListener(\n            fragment: Fragment, noinline listener: (Int, Ret?) -> Unit) =\n            setResultListener(fragment, T::class.java.name, listener)\n    }\n    protected abstract fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener)\n\n    private val resultKey get() = requireArguments().getString(KEY_RESULT)\n    protected val arg by lazy { requireArguments().getParcelable<Arg>(KEY_ARG)!! }\n    protected open fun ret(which: Int): Ret? = null\n\n    private fun args() = arguments ?: Bundle().also { arguments = it }\n    fun arg(arg: Arg) = args().putParcelable(KEY_ARG, arg)\n    fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey)\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =\n        MaterialAlertDialogBuilder(requireContext()).also { it.prepare(this) }.create()\n\n    override fun onClick(dialog: DialogInterface?, which: Int) {\n        setFragmentResult(resultKey ?: return, Bundle().apply {\n            putInt(KEY_WHICH, which)\n            putParcelable(KEY_RET, ret(which) ?: return@apply)\n        })\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        onClick(null, Activity.RESULT_CANCELED)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/BootReceiver.kt",
    "content": "package io.nekohasekai.sagernet\n\nimport android.content.BroadcastReceiver\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport io.nekohasekai.sagernet.bg.SubscriptionUpdater\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\n\nclass BootReceiver : BroadcastReceiver() {\n    companion object {\n        private val componentName by lazy { ComponentName(app, BootReceiver::class.java) }\n        var enabled: Boolean\n            get() = app.packageManager.getComponentEnabledSetting(componentName) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED\n            set(value) = app.packageManager.setComponentEnabledSetting(\n                componentName, if (value) PackageManager.COMPONENT_ENABLED_STATE_ENABLED\n                else PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP\n            )\n    }\n\n    override fun onReceive(context: Context, intent: Intent) {\n        runOnDefaultDispatcher {\n            SubscriptionUpdater.reconfigureUpdater()\n        }\n\n        if (!DataStore.persistAcrossReboot) {   // sanity check\n            enabled = false\n            return\n        }\n\n        val doStart = when (intent.action) {\n            Intent.ACTION_LOCKED_BOOT_COMPLETED -> false // DataStore.directBootAware\n            else -> Build.VERSION.SDK_INT < 24 || SagerNet.user.isUserUnlocked\n        } && DataStore.selectedProxy > 0\n\n        if (doStart) SagerNet.startService()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/Constants.kt",
    "content": "package io.nekohasekai.sagernet\n\nconst val CONNECTION_TEST_URL = \"http://cp.cloudflare.com/\"\n\nobject Key {\n\n    const val DB_PUBLIC = \"configuration.db\"\n    const val DB_PROFILE = \"sager_net.db\"\n\n    const val PERSIST_ACROSS_REBOOT = \"isAutoConnect\"\n\n    const val APP_EXPERT = \"isExpert\"\n    const val APP_THEME = \"appTheme\"\n    const val NIGHT_THEME = \"nightTheme\"\n    const val SERVICE_MODE = \"serviceMode\"\n    const val MODE_VPN = \"vpn\"\n    const val MODE_PROXY = \"proxy\"\n\n    const val GLOBAL_CUSTOM_CONFIG = \"globalCustomConfig\"\n\n    const val REMOTE_DNS = \"remoteDns\"\n    const val DIRECT_DNS = \"directDns\"\n    const val ENABLE_DNS_ROUTING = \"enableDnsRouting\"\n    const val ENABLE_FAKEDNS = \"enableFakeDns\"\n\n    const val IPV6_MODE = \"ipv6Mode\"\n\n    const val PROXY_APPS = \"proxyApps\"\n    const val BYPASS_MODE = \"bypassMode\"\n    const val INDIVIDUAL = \"individual\"\n    const val METERED_NETWORK = \"meteredNetwork\"\n\n    const val TRAFFIC_SNIFFING = \"trafficSniffing\"\n    const val RESOLVE_DESTINATION = \"resolveDestination\"\n\n    const val BYPASS_LAN = \"bypassLan\"\n    const val BYPASS_LAN_IN_CORE = \"bypassLanInCore\"\n\n    const val MIXED_PORT = \"mixedPort\"\n    const val ALLOW_ACCESS = \"allowAccess\"\n    const val SPEED_INTERVAL = \"speedInterval\"\n    const val SHOW_DIRECT_SPEED = \"showDirectSpeed\"\n\n    const val APPEND_HTTP_PROXY = \"appendHttpProxy\"\n\n    const val CONNECTION_TEST_URL = \"connectionTestURL\"\n\n    const val NETWORK_CHANGE_RESET_CONNECTIONS = \"networkChangeResetConnections\"\n    const val WAKE_RESET_CONNECTIONS = \"wakeResetConnections\"\n    const val RULES_PROVIDER = \"rulesProvider\"\n    const val LOG_LEVEL = \"logLevel\"\n    const val LOG_BUF_SIZE = \"logBufSize\"\n    const val MTU = \"mtu\"\n    const val ALWAYS_SHOW_ADDRESS = \"alwaysShowAddress\"\n\n    // Protocol Settings\n    const val GLOBAL_ALLOW_INSECURE = \"globalAllowInsecure\"\n\n    const val ACQUIRE_WAKE_LOCK = \"acquireWakeLock\"\n    const val SHOW_BOTTOM_BAR = \"showBottomBar\"\n\n    const val ALLOW_INSECURE_ON_REQUEST = \"allowInsecureOnRequest\"\n\n    const val TUN_IMPLEMENTATION = \"tunImplementation\"\n    const val PROFILE_TRAFFIC_STATISTICS = \"profileTrafficStatistics\"\n\n    const val PROFILE_DIRTY = \"profileDirty\"\n    const val PROFILE_ID = \"profileId\"\n    const val PROFILE_NAME = \"profileName\"\n    const val PROFILE_GROUP = \"profileGroup\"\n    const val PROFILE_CURRENT = \"profileCurrent\"\n\n    const val SERVER_ADDRESS = \"serverAddress\"\n    const val SERVER_PORT = \"serverPort\"\n    const val SERVER_USERNAME = \"serverUsername\"\n    const val SERVER_PASSWORD = \"serverPassword\"\n    const val SERVER_METHOD = \"serverMethod\"\n    const val SERVER_PASSWORD1 = \"serverPassword1\"\n\n    const val PROTOCOL_VERSION = \"protocolVersion\"\n\n    const val SERVER_PROTOCOL = \"serverProtocol\"\n    const val SERVER_OBFS = \"serverObfs\"\n\n    const val SERVER_NETWORK = \"serverNetwork\"\n    const val SERVER_HOST = \"serverHost\"\n    const val SERVER_PATH = \"serverPath\"\n    const val SERVER_SNI = \"serverSNI\"\n    const val SERVER_ENCRYPTION = \"serverEncryption\"\n    const val SERVER_ALPN = \"serverALPN\"\n    const val SERVER_CERTIFICATES = \"serverCertificates\"\n    const val SERVER_MTU = \"serverMTU\"\n\n    const val SERVER_CONFIG = \"serverConfig\"\n    const val SERVER_CUSTOM = \"serverCustom\"\n    const val SERVER_CUSTOM_OUTBOUND = \"serverCustomOutbound\"\n\n    const val SERVER_SECURITY_CATEGORY = \"serverSecurityCategory\"\n    const val SERVER_TLS_CAMOUFLAGE_CATEGORY = \"serverTlsCamouflageCategory\"\n    const val SERVER_ECH_CATEORY = \"serverECHCategory\"\n    const val SERVER_WS_CATEGORY = \"serverWsCategory\"\n    const val SERVER_SS_CATEGORY = \"serverSsCategory\"\n    const val SERVER_HEADERS = \"serverHeaders\"\n    const val SERVER_ALLOW_INSECURE = \"serverAllowInsecure\"\n\n    const val SERVER_AUTH_TYPE = \"serverAuthType\"\n    const val SERVER_UPLOAD_SPEED = \"serverUploadSpeed\"\n    const val SERVER_DOWNLOAD_SPEED = \"serverDownloadSpeed\"\n    const val SERVER_STREAM_RECEIVE_WINDOW = \"serverStreamReceiveWindow\"\n    const val SERVER_CONNECTION_RECEIVE_WINDOW = \"serverConnectionReceiveWindow\"\n    const val SERVER_DISABLE_MTU_DISCOVERY = \"serverDisableMtuDiscovery\"\n    const val SERVER_HOP_INTERVAL = \"hopInterval\"\n\n    const val SERVER_PRIVATE_KEY = \"serverPrivateKey\"\n    const val SERVER_INSECURE_CONCURRENCY = \"serverInsecureConcurrency\"\n\n    const val SERVER_UDP_RELAY_MODE = \"serverUDPRelayMode\"\n    const val SERVER_CONGESTION_CONTROLLER = \"serverCongestionController\"\n    const val SERVER_DISABLE_SNI = \"serverDisableSNI\"\n    const val SERVER_REDUCE_RTT = \"serverReduceRTT\"\n\n    const val ROUTE_NAME = \"routeName\"\n    const val ROUTE_DOMAIN = \"routeDomain\"\n    const val ROUTE_IP = \"routeIP\"\n    const val ROUTE_PORT = \"routePort\"\n    const val ROUTE_SOURCE_PORT = \"routeSourcePort\"\n    const val ROUTE_NETWORK = \"routeNetwork\"\n    const val ROUTE_SOURCE = \"routeSource\"\n    const val ROUTE_PROTOCOL = \"routeProtocol\"\n    const val ROUTE_OUTBOUND = \"routeOutbound\"\n    const val ROUTE_PACKAGES = \"routePackages\"\n\n    const val GROUP_NAME = \"groupName\"\n    const val GROUP_TYPE = \"groupType\"\n    const val GROUP_ORDER = \"groupOrder\"\n    const val GROUP_IS_SELECTOR = \"groupIsSelector\"\n    const val GROUP_FRONT_PROXY = \"groupFrontProxy\"\n    const val GROUP_LANDING_PROXY = \"groupLandingProxy\"\n\n    const val GROUP_SUBSCRIPTION = \"groupSubscription\"\n    const val SUBSCRIPTION_LINK = \"subscriptionLink\"\n    const val SUBSCRIPTION_FORCE_RESOLVE = \"subscriptionForceResolve\"\n    const val SUBSCRIPTION_DEDUPLICATION = \"subscriptionDeduplication\"\n    const val SUBSCRIPTION_UPDATE = \"subscriptionUpdate\"\n    const val SUBSCRIPTION_UPDATE_WHEN_CONNECTED_ONLY = \"subscriptionUpdateWhenConnectedOnly\"\n    const val SUBSCRIPTION_USER_AGENT = \"subscriptionUserAgent\"\n    const val SUBSCRIPTION_AUTO_UPDATE = \"subscriptionAutoUpdate\"\n    const val SUBSCRIPTION_AUTO_UPDATE_DELAY = \"subscriptionAutoUpdateDelay\"\n\n    //\n\n    const val APP_TLS_VERSION = \"appTLSVersion\"\n    const val ENABLE_CLASH_API = \"enableClashAPI\"\n}\n\nobject TunImplementation {\n    const val GVISOR = 0\n    const val SYSTEM = 1\n    const val MIXED = 2\n}\n\nobject IPv6Mode {\n    const val DISABLE = 0\n    const val ENABLE = 1\n    const val PREFER = 2\n    const val ONLY = 3\n}\n\nobject GroupType {\n    const val BASIC = 0\n    const val SUBSCRIPTION = 1\n}\n\nobject GroupOrder {\n    const val ORIGIN = 0\n    const val BY_NAME = 1\n    const val BY_DELAY = 2\n}\n\nobject Action {\n    const val SERVICE = \"io.nekohasekai.sagernet.SERVICE\"\n    const val CLOSE = \"io.nekohasekai.sagernet.CLOSE\"\n    const val RELOAD = \"io.nekohasekai.sagernet.RELOAD\"\n\n    // const val SWITCH_WAKE_LOCK = \"io.nekohasekai.sagernet.SWITCH_WAKELOCK\"\n    const val RESET_UPSTREAM_CONNECTIONS = \"moe.nb4a.RESET_UPSTREAM_CONNECTIONS\"\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/QuickToggleShortcut.kt",
    "content": "/*******************************************************************************\n *                                                                             *\n *  Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com>                          *\n *  Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be>  *\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 *                                                                             *\n *******************************************************************************/\n\npackage io.nekohasekai.sagernet\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.content.pm.ShortcutManager\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.core.content.getSystemService\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\nimport io.nekohasekai.sagernet.database.DataStore\n\n@Suppress(\"DEPRECATION\")\nclass QuickToggleShortcut : Activity(), SagerConnection.Callback {\n    private val connection = SagerConnection(SagerConnection.CONNECTION_ID_SHORTCUT)\n    private var profileId = -1L\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {\n            setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this,\n                ShortcutInfoCompat.Builder(this, \"toggle\")\n                    .setIntent(Intent(this,\n                        QuickToggleShortcut::class.java).setAction(Intent.ACTION_MAIN))\n                    .setIcon(IconCompat.createWithResource(this,\n                        R.drawable.ic_qu_shadowsocks_launcher))\n                    .setShortLabel(getString(R.string.quick_toggle))\n                    .build()))\n            finish()\n        } else {\n            profileId = intent.getLongExtra(\"profile\", -1L)\n            connection.connect(this, this)\n            if (Build.VERSION.SDK_INT >= 25) {\n                getSystemService<ShortcutManager>()!!.reportShortcutUsed(if (profileId >= 0) \"shortcut-profile-$profileId\" else \"toggle\")\n            }\n        }\n    }\n\n    override fun onServiceConnected(service: ISagerNetService) {\n        val state = BaseService.State.values()[service.state]\n        when {\n            state.canStop -> {\n                if (profileId == DataStore.selectedProxy || profileId == -1L) {\n                    SagerNet.stopService()\n                } else {\n                    DataStore.selectedProxy = profileId\n                    SagerNet.reloadService()\n                }\n            }\n            state == BaseService.State.Stopped -> {\n                if (profileId >= 0L) DataStore.selectedProxy = profileId\n                SagerNet.startService()\n            }\n        }\n        finish()\n    }\n\n    override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {}\n\n    override fun onDestroy() {\n        connection.disconnect(this)\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/SagerNet.kt",
    "content": "package io.nekohasekai.sagernet\n\nimport android.annotation.SuppressLint\nimport android.app.*\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.os.Build\nimport android.os.PowerManager\nimport android.os.StrictMode\nimport android.os.UserManager\nimport androidx.annotation.RequiresApi\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.getSystemService\nimport go.Seq\nimport io.nekohasekai.sagernet.bg.SagerConnection\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.isOss\nimport io.nekohasekai.sagernet.ktx.isPreview\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.ui.MainActivity\nimport io.nekohasekai.sagernet.utils.*\nimport kotlinx.coroutines.DEBUG_PROPERTY_NAME\nimport kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON\nimport libcore.Libcore\nimport moe.matsuri.nb4a.NativeInterface\nimport moe.matsuri.nb4a.net.LocalResolverImpl\nimport moe.matsuri.nb4a.utils.JavaUtil\nimport moe.matsuri.nb4a.utils.cleanWebview\nimport java.io.File\nimport androidx.work.Configuration as WorkConfiguration\n\nclass SagerNet : Application(),\n    WorkConfiguration.Provider {\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(base)\n\n        application = this\n    }\n\n    private val nativeInterface = NativeInterface()\n\n    val externalAssets: File by lazy { getExternalFilesDir(null) ?: filesDir }\n    val process: String = JavaUtil.getProcessName()\n    private val isMainProcess = process == BuildConfig.APPLICATION_ID\n    val isBgProcess = process.endsWith(\":bg\")\n\n    override fun onCreate() {\n        super.onCreate()\n\n        Thread.setDefaultUncaughtExceptionHandler(CrashHandler)\n\n        if (isMainProcess || isBgProcess) {\n            externalAssets.mkdirs()\n            Seq.setContext(this)\n            Libcore.initCore(\n                process,\n                cacheDir.absolutePath + \"/\",\n                filesDir.absolutePath + \"/\",\n                externalAssets.absolutePath + \"/\",\n                DataStore.logBufSize,\n                DataStore.logLevel > 0,\n                nativeInterface, nativeInterface, LocalResolverImpl\n            )\n\n            // fix multi process issue in Android 9+\n            JavaUtil.handleWebviewDir(this)\n\n            runOnDefaultDispatcher {\n                PackageCache.register()\n                cleanWebview()\n            }\n        }\n\n        if (isMainProcess) {\n            Theme.apply(this)\n            Theme.applyNightTheme()\n            runOnDefaultDispatcher {\n                DefaultNetworkListener.start(this) {\n                    underlyingNetwork = it\n                }\n\n                updateNotificationChannels()\n            }\n        }\n\n        if (BuildConfig.DEBUG) {\n            System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)\n            StrictMode.setVmPolicy(\n                StrictMode.VmPolicy.Builder()\n                    .detectLeakedSqlLiteObjects()\n                    .detectLeakedClosableObjects()\n                    .detectLeakedRegistrationObjects()\n                    .penaltyLog()\n                    .build()\n            )\n        }\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        updateNotificationChannels()\n    }\n\n    override fun getWorkManagerConfiguration(): WorkConfiguration {\n        return WorkConfiguration.Builder()\n            .setDefaultProcessName(\"${BuildConfig.APPLICATION_ID}:bg\")\n            .build()\n    }\n\n    override fun onTrimMemory(level: Int) {\n        super.onTrimMemory(level)\n\n        Libcore.forceGc()\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    companion object {\n\n        lateinit var application: SagerNet\n\n        val isTv by lazy {\n            uiMode.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION\n        }\n\n        val configureIntent: (Context) -> PendingIntent by lazy {\n            {\n                PendingIntent.getActivity(\n                    it,\n                    0,\n                    Intent(\n                        application, MainActivity::class.java\n                    ).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT),\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0\n                )\n            }\n        }\n        val activity by lazy { application.getSystemService<ActivityManager>()!! }\n        val clipboard by lazy { application.getSystemService<ClipboardManager>()!! }\n        val connectivity by lazy { application.getSystemService<ConnectivityManager>()!! }\n        val notification by lazy { application.getSystemService<NotificationManager>()!! }\n        val user by lazy { application.getSystemService<UserManager>()!! }\n        val uiMode by lazy { application.getSystemService<UiModeManager>()!! }\n        val power by lazy { application.getSystemService<PowerManager>()!! }\n\n        fun getClipboardText(): String {\n            return clipboard.primaryClip?.takeIf { it.itemCount > 0 }\n                ?.getItemAt(0)?.text?.toString() ?: \"\"\n        }\n\n        fun trySetPrimaryClip(clip: String) = try {\n            clipboard.setPrimaryClip(ClipData.newPlainText(null, clip))\n            true\n        } catch (e: RuntimeException) {\n            Logs.w(e)\n            false\n        }\n\n        fun updateNotificationChannels() {\n            if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {\n                notification.createNotificationChannels(\n                    listOf(\n                        NotificationChannel(\n                            \"service-vpn\",\n                            application.getText(R.string.service_vpn),\n                            if (Build.VERSION.SDK_INT >= 28) NotificationManager.IMPORTANCE_MIN\n                            else NotificationManager.IMPORTANCE_LOW\n                        ),   // #1355\n                        NotificationChannel(\n                            \"service-proxy\",\n                            application.getText(R.string.service_proxy),\n                            NotificationManager.IMPORTANCE_LOW\n                        ), NotificationChannel(\n                            \"service-subscription\",\n                            application.getText(R.string.service_subscription),\n                            NotificationManager.IMPORTANCE_DEFAULT\n                        ), NotificationChannel(\n                            \"connection-test\",\n                            application.getText(R.string.connection_test),\n                            NotificationManager.IMPORTANCE_DEFAULT\n                        )\n                    )\n                )\n            }\n        }\n\n        fun startService() = ContextCompat.startForegroundService(\n            application, Intent(application, SagerConnection.serviceClass)\n        )\n\n        fun reloadService() =\n            application.sendBroadcast(Intent(Action.RELOAD).setPackage(application.packageName))\n\n        fun stopService() =\n            application.sendBroadcast(Intent(Action.CLOSE).setPackage(application.packageName))\n\n        var underlyingNetwork: Network? = null\n\n        var appVersionNameForDisplay = {\n            var n = BuildConfig.VERSION_NAME\n            if (isPreview) {\n                n += \" \" + BuildConfig.PRE_VERSION_NAME\n            } else if (!isOss) {\n                n += \" ${BuildConfig.FLAVOR}\"\n            }\n            if (BuildConfig.DEBUG) {\n                n += \" DEBUG\"\n            }\n            n\n        }()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/aidl/SpeedDisplayData.kt",
    "content": "package io.nekohasekai.sagernet.aidl\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class SpeedDisplayData(\n    // Bytes per second\n    var txRateProxy: Long = 0L,\n    var rxRateProxy: Long = 0L,\n    var txRateDirect: Long = 0L,\n    var rxRateDirect: Long = 0L,\n\n    // Bytes for the current session\n    // Outbound \"bypass\" usage is not counted\n    var txTotal: Long = 0L,\n    var rxTotal: Long = 0L,\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/aidl/TrafficData.kt",
    "content": "package io.nekohasekai.sagernet.aidl\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class TrafficData(\n    var id: Long = 0L,\n    var tx: Long = 0L,\n    var rx: Long = 0L,\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/AbstractInstance.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport java.io.Closeable\n\ninterface AbstractInstance : Closeable {\n\n    fun launch()\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.app.Service\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.*\nimport android.widget.Toast\nimport io.nekohasekai.sagernet.Action\nimport io.nekohasekai.sagernet.BootReceiver\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback\nimport io.nekohasekai.sagernet.bg.proto.ProxyInstance\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.plugin.PluginManager\nimport io.nekohasekai.sagernet.utils.DefaultNetworkListener\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport libcore.Libcore\nimport moe.matsuri.nb4a.Protocols\nimport moe.matsuri.nb4a.utils.Util\nimport java.net.UnknownHostException\n\nclass BaseService {\n\n    enum class State(\n        val canStop: Boolean = false,\n        val started: Boolean = false,\n        val connected: Boolean = false,\n    ) {\n        /**\n         * Idle state is only used by UI and will never be returned by BaseService.\n         */\n        Idle, Connecting(true, true, false), Connected(true, true, true), Stopping, Stopped,\n    }\n\n    interface ExpectedException\n\n    class Data internal constructor(private val service: Interface) {\n        var state = State.Stopped\n        var proxy: ProxyInstance? = null\n        var notification: ServiceNotification? = null\n\n        val receiver = broadcastReceiver { ctx, intent ->\n            when (intent.action) {\n                Intent.ACTION_SHUTDOWN -> service.persistStats()\n                Action.RELOAD -> service.reload()\n                // Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() }\n                PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED -> {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                        if (SagerNet.power.isDeviceIdleMode) {\n                            proxy?.box?.sleep()\n                        } else {\n                            proxy?.box?.wake()\n                            if (DataStore.wakeResetConnections) {\n                                Libcore.resetAllConnections(true)\n                            }\n                        }\n                    }\n                }\n\n                Action.RESET_UPSTREAM_CONNECTIONS -> runOnDefaultDispatcher {\n                    Libcore.resetAllConnections(true)\n                    runOnMainDispatcher {\n                        Util.collapseStatusBar(ctx)\n                        Toast.makeText(ctx, \"Reset upstream connections done\", Toast.LENGTH_SHORT)\n                            .show()\n                    }\n                }\n\n                else -> service.stopRunner()\n            }\n        }\n        var closeReceiverRegistered = false\n\n        val binder = Binder(this)\n        var connectingJob: Job? = null\n\n        fun changeState(s: State, msg: String? = null) {\n            if (state == s && msg == null) return\n            state = s\n            DataStore.serviceState = s\n            binder.stateChanged(s, msg)\n        }\n    }\n\n    class Binder(private var data: Data? = null) : ISagerNetService.Stub(), CoroutineScope,\n        AutoCloseable {\n        private val callbacks = object : RemoteCallbackList<ISagerNetServiceCallback>() {\n            override fun onCallbackDied(callback: ISagerNetServiceCallback?, cookie: Any?) {\n                super.onCallbackDied(callback, cookie)\n            }\n        }\n\n        val callbackIdMap = mutableMapOf<ISagerNetServiceCallback, Int>()\n\n        override val coroutineContext = Dispatchers.Main.immediate + Job()\n\n        override fun getState(): Int = (data?.state ?: State.Idle).ordinal\n        override fun getProfileName(): String = data?.proxy?.displayProfileName ?: \"Idle\"\n\n        override fun registerCallback(cb: ISagerNetServiceCallback, id: Int) {\n            if (id == SagerConnection.CONNECTION_ID_RESTART_BG) {\n                Runtime.getRuntime().exit(0)\n                return\n            }\n            if (!callbackIdMap.contains(cb)) {\n                callbacks.register(cb)\n            }\n            callbackIdMap[cb] = id\n        }\n\n        private val broadcastMutex = Mutex()\n\n        suspend fun broadcast(work: (ISagerNetServiceCallback) -> Unit) {\n            broadcastMutex.withLock {\n                val count = callbacks.beginBroadcast()\n                try {\n                    repeat(count) {\n                        try {\n                            work(callbacks.getBroadcastItem(it))\n                        } catch (_: RemoteException) {\n                        } catch (_: Exception) {\n                        }\n                    }\n                } finally {\n                    callbacks.finishBroadcast()\n                }\n            }\n        }\n\n        override fun unregisterCallback(cb: ISagerNetServiceCallback) {\n            callbackIdMap.remove(cb)\n            callbacks.unregister(cb)\n        }\n\n        override fun urlTest(): Int {\n            if (data?.proxy?.box == null) {\n                error(\"core not started\")\n            }\n            try {\n                return Libcore.urlTest(\n                    data!!.proxy!!.box, DataStore.connectionTestURL, 3000\n                )\n            } catch (e: Exception) {\n                error(Protocols.genFriendlyMsg(e.readableMessage))\n            }\n        }\n\n        fun stateChanged(s: State, msg: String?) = launch {\n            val profileName = profileName\n            broadcast { it.stateChanged(s.ordinal, profileName, msg) }\n        }\n\n        fun missingPlugin(pluginName: String) = launch {\n            val profileName = profileName\n            broadcast { it.missingPlugin(profileName, pluginName) }\n        }\n\n        override fun close() {\n            callbacks.kill()\n            cancel()\n            data = null\n        }\n    }\n\n    interface Interface {\n        val data: Data\n        val tag: String\n        fun createNotification(profileName: String): ServiceNotification\n\n        fun onBind(intent: Intent): IBinder? =\n            if (intent.action == Action.SERVICE) data.binder else null\n\n        fun reload() {\n            if (DataStore.selectedProxy == 0L) {\n                stopRunner(false, (this as Context).getString(R.string.profile_empty))\n            }\n            if (canReloadSelector()) {\n                val ent = SagerDatabase.proxyDao.getById(DataStore.selectedProxy)\n                val tag = data.proxy!!.config.profileTagMap[ent?.id] ?: \"\"\n                if (tag.isNotBlank() && ent != null) {\n                    // select from GUI\n                    data.proxy!!.box.selectOutbound(tag)\n                    // or select from webui\n                    // => selector_OnProxySelected\n                }\n                return\n            }\n            val s = data.state\n            when {\n                s == State.Stopped -> startRunner()\n                s.canStop -> stopRunner(true)\n                else -> Logs.w(\"Illegal state $s when invoking use\")\n            }\n        }\n\n        fun canReloadSelector(): Boolean {\n            if ((data.proxy?.config?.selectorGroupId ?: -1L) < 0) return false\n            val ent = SagerDatabase.proxyDao.getById(DataStore.selectedProxy) ?: return false\n            val tmpBox = ProxyInstance(ent)\n            tmpBox.buildConfigTmp()\n            if (tmpBox.lastSelectorGroupId == data.proxy?.lastSelectorGroupId) {\n                return true\n            }\n            return false\n        }\n\n        suspend fun startProcesses() {\n            data.proxy!!.launch()\n        }\n\n        fun startRunner() {\n            this as Context\n            if (Build.VERSION.SDK_INT >= 26) startForegroundService(Intent(this, javaClass))\n            else startService(Intent(this, javaClass))\n        }\n\n        fun killProcesses() {\n            data.proxy?.close()\n            wakeLock?.apply {\n                release()\n                wakeLock = null\n            }\n            runOnDefaultDispatcher {\n                DefaultNetworkListener.stop(this)\n            }\n        }\n\n        fun stopRunner(restart: Boolean = false, msg: String? = null) {\n            DataStore.baseService = null\n            DataStore.vpnService = null\n\n            if (data.state == State.Stopping) return\n            data.notification?.destroy()\n            data.notification = null\n            this as Service\n\n            data.changeState(State.Stopping)\n\n            runOnMainDispatcher {\n                data.connectingJob?.cancelAndJoin() // ensure stop connecting first\n                // we use a coroutineScope here to allow clean-up in parallel\n                coroutineScope {\n                    killProcesses()\n                    val data = data\n                    if (data.closeReceiverRegistered) {\n                        unregisterReceiver(data.receiver)\n                        data.closeReceiverRegistered = false\n                    }\n                    data.proxy = null\n                }\n\n                // change the state\n                data.changeState(State.Stopped, msg)\n                // stop the service if nothing has bound to it\n                if (restart) startRunner() else {\n                    stopSelf()\n                }\n            }\n        }\n\n        fun persistStats() {\n            // TODO NEW save app stats?\n        }\n\n        // networks\n        var upstreamInterfaceName: String?\n\n        suspend fun preInit() {\n            DefaultNetworkListener.start(this) {\n                SagerNet.connectivity.getLinkProperties(it)?.also { link ->\n                    SagerNet.underlyingNetwork = it\n                    DataStore.vpnService?.updateUnderlyingNetwork()\n                    //\n                    val oldName = upstreamInterfaceName\n                    if (oldName != link.interfaceName) {\n                        upstreamInterfaceName = link.interfaceName\n                    }\n                    if (oldName != null && upstreamInterfaceName != null && oldName != upstreamInterfaceName) {\n                        Logs.d(\"Network changed: $oldName -> $upstreamInterfaceName\")\n                        if (DataStore.networkChangeResetConnections) {\n                            Libcore.resetAllConnections(true)\n                        }\n                    }\n                }\n            }\n        }\n\n        var wakeLock: PowerManager.WakeLock?\n        fun acquireWakeLock()\n\n        suspend fun lateInit() {\n            wakeLock?.apply {\n                release()\n                wakeLock = null\n            }\n\n            if (DataStore.acquireWakeLock) {\n                acquireWakeLock()\n                data.notification?.postNotificationWakeLockStatus(true)\n            } else {\n                data.notification?.postNotificationWakeLockStatus(false)\n            }\n        }\n\n        fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n            DataStore.baseService = this\n\n            val data = data\n            if (data.state != State.Stopped) return Service.START_NOT_STICKY\n            val profile = SagerDatabase.proxyDao.getById(DataStore.selectedProxy)\n            this as Context\n            if (profile == null) { // gracefully shutdown: https://stackoverflow.com/q/47337857/2245107\n                data.notification = createNotification(\"\")\n                stopRunner(false, getString(R.string.profile_empty))\n                return Service.START_NOT_STICKY\n            }\n\n            val proxy = ProxyInstance(profile, this)\n            data.proxy = proxy\n            BootReceiver.enabled = DataStore.persistAcrossReboot\n            if (!data.closeReceiverRegistered) {\n                val filter = IntentFilter().apply {\n                    addAction(Action.RELOAD)\n                    addAction(Intent.ACTION_SHUTDOWN)\n                    addAction(Action.CLOSE)\n                    // addAction(Action.SWITCH_WAKE_LOCK)\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                        addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)\n                    }\n                    addAction(Action.RESET_UPSTREAM_CONNECTIONS)\n                }\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n                    registerReceiver(\n                        data.receiver,\n                        filter,\n                        \"$packageName.SERVICE\",\n                        null,\n                        Context.RECEIVER_EXPORTED\n                    )\n                } else {\n                    registerReceiver(\n                        data.receiver,\n                        filter,\n                        \"$packageName.SERVICE\",\n                        null\n                    )\n                }\n                data.closeReceiverRegistered = true\n            }\n\n            data.changeState(State.Connecting)\n            runOnMainDispatcher {\n                try {\n                    data.notification = createNotification(ServiceNotification.genTitle(profile))\n\n                    Executable.killAll()    // clean up old processes\n                    preInit()\n                    proxy.init()\n                    DataStore.currentProfile = profile.id\n\n                    proxy.processes = GuardedProcessPool {\n                        Logs.w(it)\n                        stopRunner(false, it.readableMessage)\n                    }\n\n                    startProcesses()\n                    data.changeState(State.Connected)\n\n                    lateInit()\n                } catch (_: CancellationException) { // if the job was cancelled, it is canceller's responsibility to call stopRunner\n                } catch (_: UnknownHostException) {\n                    stopRunner(false, getString(R.string.invalid_server))\n                } catch (e: PluginManager.PluginNotFoundException) {\n                    Toast.makeText(this@Interface, e.readableMessage, Toast.LENGTH_SHORT).show()\n                    Logs.w(e)\n                    data.binder.missingPlugin(e.plugin)\n                    stopRunner(false, null)\n                } catch (exc: Throwable) {\n                    if (exc.javaClass.name.endsWith(\"proxyerror\")) {\n                        // error from golang\n                        Logs.w(exc.readableMessage)\n                    } else {\n                        Logs.w(exc)\n                    }\n                    stopRunner(\n                        false, \"${getString(R.string.service_failed)}: ${exc.readableMessage}\"\n                    )\n                } finally {\n                    data.connectingJob = null\n                }\n            }\n            return Service.START_NOT_STICKY\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/Executable.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.system.ErrnoException\nimport android.system.Os\nimport android.system.OsConstants\nimport io.nekohasekai.sagernet.ktx.Logs\nimport java.io.File\nimport java.io.IOException\nimport androidx.core.text.isDigitsOnly\n\nobject Executable {\n    private val EXECUTABLES = setOf(\n        \"libtrojan.so\", \"libtrojan-go.so\", \"libnaive.so\", \"libtuic.so\", \"libhysteria.so\"\n    )\n\n    fun killAll(alsoKillBg: Boolean = false) {\n        // kill bg may fail\n        for (process in File(\"/proc\").listFiles { _, name -> name.isDigitsOnly() } ?: return) {\n            val exe = File(\n                try {\n                File(process, \"cmdline\").inputStream().bufferedReader().use {\n                    it.readText()\n                }\n            } catch (_: IOException) {\n                continue\n            }.split(Character.MIN_VALUE, limit = 2).first())\n            if (EXECUTABLES.contains(exe.name) || (alsoKillBg && exe.name.endsWith(\":bg\"))) try {\n                Os.kill(process.name.toInt(), OsConstants.SIGKILL)\n                Logs.w(\"SIGKILL ${exe.name} (${process.name}) succeed\")\n            } catch (e: ErrnoException) {\n                if (e.errno != OsConstants.ESRCH) {\n                    Logs.w(\"SIGKILL ${exe.absolutePath} (${process.name}) failed\")\n                    Logs.w(e)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/GuardedProcessPool.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.os.Build\nimport android.os.SystemClock\nimport android.system.ErrnoException\nimport android.system.Os\nimport android.system.OsConstants\nimport androidx.annotation.MainThread\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.utils.Commandline\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.channels.Channel\nimport libcore.Libcore\nimport java.io.File\nimport java.io.IOException\nimport java.io.InputStream\nimport kotlin.concurrent.thread\n\nclass GuardedProcessPool(private val onFatal: suspend (IOException) -> Unit) : CoroutineScope {\n    companion object {\n        private val pid by lazy {\n            Class.forName(\"java.lang.ProcessManager\\$ProcessImpl\").getDeclaredField(\"pid\")\n                .apply { isAccessible = true }\n        }\n    }\n\n    private inner class Guard(\n        private val cmd: List<String>,\n        private val env: Map<String, String> = mapOf()\n    ) {\n        private lateinit var process: Process\n\n        private fun streamLogger(input: InputStream, logger: (String) -> Unit) = try {\n            input.bufferedReader().forEachLine(logger)\n        } catch (_: IOException) {\n        }    // ignore\n\n        fun start() {\n            process = ProcessBuilder(cmd).directory(SagerNet.application.noBackupFilesDir).apply {\n                environment().putAll(env)\n            }.start()\n        }\n\n        @DelicateCoroutinesApi\n        suspend fun looper(onRestartCallback: (suspend () -> Unit)?) {\n            var running = true\n            val cmdName = File(cmd.first()).nameWithoutExtension\n            val exitChannel = Channel<Int>()\n            try {\n                while (true) {\n                    thread(name = \"stderr-$cmdName\") {\n                        streamLogger(process.errorStream) { Libcore.nekoLogPrintln(\"[$cmdName] $it\") }\n                    }\n                    thread(name = \"stdout-$cmdName\") {\n                        streamLogger(process.inputStream) { Libcore.nekoLogPrintln(\"[$cmdName] $it\") }\n                        // this thread also acts as a daemon thread for waitFor\n                        runBlocking { exitChannel.send(process.waitFor()) }\n                    }\n                    val startTime = SystemClock.elapsedRealtime()\n                    val exitCode = exitChannel.receive()\n                    running = false\n                    when {\n                        SystemClock.elapsedRealtime() - startTime < 1000 -> throw IOException(\n                            \"$cmdName exits too fast (exit code: $exitCode)\"\n                        )\n\n                        exitCode == 128 + OsConstants.SIGKILL -> Logs.w(\"$cmdName was killed\")\n                        else -> Logs.w(IOException(\"$cmdName unexpectedly exits with code $exitCode\"))\n                    }\n                    Logs.i(\"restart process: ${Commandline.toString(cmd)} (last exit code: $exitCode)\")\n                    start()\n                    running = true\n                    onRestartCallback?.invoke()\n                }\n            } catch (e: IOException) {\n                Logs.w(\"error occurred. stop guard: ${Commandline.toString(cmd)}\")\n                GlobalScope.launch(Dispatchers.Main) { onFatal(e) }\n            } finally {\n                if (running) withContext(NonCancellable) {  // clean-up cannot be cancelled\n                    if (Build.VERSION.SDK_INT < 24) {\n                        try {\n                            Os.kill(pid.get(process) as Int, OsConstants.SIGTERM)\n                        } catch (e: ErrnoException) {\n                            if (e.errno != OsConstants.ESRCH) Logs.w(e)\n                        } catch (e: ReflectiveOperationException) {\n                            Logs.w(e)\n                        }\n                        if (withTimeoutOrNull(500) { exitChannel.receive() } != null) return@withContext\n                    }\n                    process.destroy()                       // kill the process\n                    if (Build.VERSION.SDK_INT >= 26) {\n                        if (withTimeoutOrNull(1000) { exitChannel.receive() } != null) return@withContext\n                        process.destroyForcibly()           // Force to kill the process if it's still alive\n                    }\n                    exitChannel.receive()\n                }                                           // otherwise process already exited, nothing to be done\n            }\n        }\n    }\n\n    override val coroutineContext = Dispatchers.Main.immediate + Job()\n    var processCount = 0\n\n    @MainThread\n    fun start(\n        cmd: List<String>,\n        env: MutableMap<String, String> = mutableMapOf(),\n        onRestartCallback: (suspend () -> Unit)? = null\n    ) {\n        Logs.i(\"start process: ${Commandline.toString(cmd)}\")\n        Guard(cmd, env).apply {\n            start() // if start fails, IOException will be thrown directly\n            launch { looper(onRestartCallback) }\n        }\n        processCount += 1\n    }\n\n    @MainThread\n    fun close(scope: CoroutineScope) {\n        cancel()\n        coroutineContext[Job]!!.also { job -> scope.launch { job.cancelAndJoin() } }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/ProxyService.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.annotation.SuppressLint\nimport android.app.Service\nimport android.content.Intent\nimport android.os.PowerManager\nimport io.nekohasekai.sagernet.SagerNet\n\nclass ProxyService : Service(), BaseService.Interface {\n    override val data = BaseService.Data(this)\n    override val tag: String get() = \"SagerNetProxyService\"\n    override fun createNotification(profileName: String): ServiceNotification =\n        ServiceNotification(this, profileName, \"service-proxy\", true)\n\n    override var wakeLock: PowerManager.WakeLock? = null\n    override var upstreamInterfaceName: String? = null\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun acquireWakeLock() {\n        wakeLock = SagerNet.power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"sagernet:proxy\")\n            .apply { acquire() }\n    }\n\n    override fun onBind(intent: Intent) = super.onBind(intent)\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =\n        super<BaseService.Interface>.onStartCommand(intent, flags, startId)\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/SagerConnection.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.Intent\nimport android.content.ServiceConnection\nimport android.os.IBinder\nimport android.os.RemoteException\nimport io.nekohasekai.sagernet.Action\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.aidl.ISagerNetServiceCallback\nimport io.nekohasekai.sagernet.aidl.SpeedDisplayData\nimport io.nekohasekai.sagernet.aidl.TrafficData\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\n\nclass SagerConnection(\n    private var connectionId: Int,\n    private var listenForDeath: Boolean = false\n) : ServiceConnection, IBinder.DeathRecipient {\n\n    companion object {\n        val serviceClass\n            get() = when (DataStore.serviceMode) {\n                Key.MODE_PROXY -> ProxyService::class\n                Key.MODE_VPN -> VpnService::class\n                else -> throw UnknownError()\n            }.java\n\n        const val CONNECTION_ID_SHORTCUT = 0\n        const val CONNECTION_ID_TILE = 1\n        const val CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND = 2\n        const val CONNECTION_ID_MAIN_ACTIVITY_BACKGROUND = 3\n        const val CONNECTION_ID_RESTART_BG = 4\n\n        var restartingApp = false\n    }\n\n    interface Callback {\n        // smaller ISagerNetServiceCallback\n\n        fun cbSpeedUpdate(stats: SpeedDisplayData) {}\n        fun cbTrafficUpdate(data: TrafficData) {}\n        fun cbSelectorUpdate(id: Long) {}\n\n        fun stateChanged(state: BaseService.State, profileName: String?, msg: String?)\n\n        fun missingPlugin(profileName: String, pluginName: String) {}\n\n        fun onServiceConnected(service: ISagerNetService)\n\n        /**\n         * Different from Android framework, this method will be called even when you call `detachService`.\n         */\n        fun onServiceDisconnected() {}\n        fun onBinderDied() {}\n    }\n\n    private var connectionActive = false\n    private var callbackRegistered = false\n    private var callback: Callback? = null\n    private val serviceCallback = object : ISagerNetServiceCallback.Stub() {\n\n        override fun stateChanged(state: Int, profileName: String?, msg: String?) {\n            if (state < 0) return // skip private\n            val s = BaseService.State.values()[state]\n            DataStore.serviceState = s\n            val callback = callback ?: return\n            runOnMainDispatcher {\n                callback.stateChanged(s, profileName, msg)\n            }\n        }\n\n        override fun cbSpeedUpdate(stats: SpeedDisplayData) {\n            val callback = callback ?: return\n            runOnMainDispatcher {\n                callback.cbSpeedUpdate(stats)\n            }\n        }\n\n        override fun cbTrafficUpdate(stats: TrafficData) {\n            val callback = callback ?: return\n            runOnMainDispatcher {\n                callback.cbTrafficUpdate(stats)\n            }\n        }\n\n        override fun cbSelectorUpdate(id: Long) {\n            val callback = callback ?: return\n            runOnMainDispatcher {\n                callback.cbSelectorUpdate(id)\n            }\n        }\n\n        override fun missingPlugin(profileName: String, pluginName: String) {\n            val callback = callback ?: return\n            runOnMainDispatcher {\n                callback.missingPlugin(profileName, pluginName)\n            }\n        }\n\n    }\n\n    private var binder: IBinder? = null\n\n    var service: ISagerNetService? = null\n\n    fun updateConnectionId(id: Int) {\n        connectionId = id\n        try {\n            service?.registerCallback(serviceCallback, id)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    override fun onServiceConnected(name: ComponentName?, binder: IBinder) {\n        this.binder = binder\n        val service = ISagerNetService.Stub.asInterface(binder)!!\n        this.service = service\n        try {\n            if (listenForDeath) binder.linkToDeath(this, 0)\n            check(!callbackRegistered)\n            service.registerCallback(serviceCallback, connectionId)\n            callbackRegistered = true\n        } catch (e: RemoteException) {\n            e.printStackTrace()\n        }\n        callback?.onServiceConnected(service)\n    }\n\n    override fun onServiceDisconnected(name: ComponentName?) {\n        unregisterCallback()\n        callback?.onServiceDisconnected()\n        service = null\n        binder = null\n    }\n\n    override fun binderDied() {\n        service = null\n        callbackRegistered = false\n        if (!restartingApp) {\n            callback?.also { runOnMainDispatcher { it.onBinderDied() } }\n        }\n    }\n\n    private fun unregisterCallback() {\n        val service = service\n        if (service != null && callbackRegistered) try {\n            service.unregisterCallback(serviceCallback)\n        } catch (_: RemoteException) {\n        }\n        callbackRegistered = false\n    }\n\n    fun connect(context: Context, callback: Callback?) {\n        if (connectionActive) return\n        connectionActive = true\n        check(this.callback == null)\n        this.callback = callback\n        val intent = Intent(context, serviceClass).setAction(Action.SERVICE)\n        context.bindService(intent, this, Context.BIND_AUTO_CREATE)\n    }\n\n    fun disconnect(context: Context) {\n        unregisterCallback()\n        if (connectionActive) try {\n            context.unbindService(this)\n        } catch (_: IllegalArgumentException) {\n        }   // ignore\n        connectionActive = false\n        if (listenForDeath) try {\n            binder?.unlinkToDeath(this, 0)\n        } catch (_: NoSuchElementException) {\n        }\n        binder = null\n        service = null\n        callback = null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/ServiceNotification.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.app.PendingIntent\nimport android.app.Service\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED\nimport android.os.Build\nimport android.text.format.Formatter\nimport android.widget.Toast\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport io.nekohasekai.sagernet.Action\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.SpeedDisplayData\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.getColorAttr\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\nimport io.nekohasekai.sagernet.ui.SwitchActivity\nimport io.nekohasekai.sagernet.utils.Theme\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\n\n/**\n * User can customize visibility of notification since Android 8.\n * The default visibility:\n *\n * Android 8.x: always visible due to system limitations\n * VPN:         always invisible because of VPN notification/icon\n * Other:       always visible\n *\n * See also: https://github.com/aosp-mirror/platform_frameworks_base/commit/070d142993403cc2c42eca808ff3fafcee220ac4\n */\nclass ServiceNotification(\n    private val service: BaseService.Interface, title: String,\n    channel: String, visible: Boolean = false,\n) : BroadcastReceiver() {\n    companion object {\n        const val notificationId = 1\n        val flags =\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0\n\n        fun genTitle(ent: ProxyEntity): String {\n            val gn = if (DataStore.showGroupInNotification)\n                SagerDatabase.groupDao.getById(ent.groupId)?.displayName() else null\n            return if (gn == null) ent.displayName() else \"[$gn] ${ent.displayName()}\"\n        }\n    }\n\n    var listenPostSpeed = true\n\n    suspend fun postNotificationSpeedUpdate(stats: SpeedDisplayData) {\n        useBuilder {\n            if (showDirectSpeed) {\n                val speedDetail = (service as Context).getString(\n                    R.string.speed_detail, service.getString(\n                        R.string.speed, Formatter.formatFileSize(service, stats.txRateProxy)\n                    ), service.getString(\n                        R.string.speed, Formatter.formatFileSize(service, stats.rxRateProxy)\n                    ), service.getString(\n                        R.string.speed,\n                        Formatter.formatFileSize(service, stats.txRateDirect)\n                    ), service.getString(\n                        R.string.speed,\n                        Formatter.formatFileSize(service, stats.rxRateDirect)\n                    )\n                )\n                it.setStyle(NotificationCompat.BigTextStyle().bigText(speedDetail))\n                it.setContentText(speedDetail)\n            } else {\n                val speedSimple = (service as Context).getString(\n                    R.string.traffic, service.getString(\n                        R.string.speed, Formatter.formatFileSize(service, stats.txRateProxy)\n                    ), service.getString(\n                        R.string.speed, Formatter.formatFileSize(service, stats.rxRateProxy)\n                    )\n                )\n                it.setContentText(speedSimple)\n            }\n            it.setSubText(\n                service.getString(\n                    R.string.traffic,\n                    Formatter.formatFileSize(service, stats.txTotal),\n                    Formatter.formatFileSize(service, stats.rxTotal)\n                )\n            )\n        }\n        update()\n    }\n\n    suspend fun postNotificationTitle(newTitle: String) {\n        useBuilder {\n            it.setContentTitle(newTitle)\n        }\n        update()\n    }\n\n    suspend fun postNotificationWakeLockStatus(acquired: Boolean) {\n        updateActions()\n        useBuilder {\n            it.priority =\n                if (acquired) NotificationCompat.PRIORITY_HIGH else NotificationCompat.PRIORITY_LOW\n        }\n        update()\n    }\n\n    private val showDirectSpeed = DataStore.showDirectSpeed\n\n    private val builder = NotificationCompat.Builder(service as Context, channel)\n        .setWhen(0)\n        .setTicker(service.getString(R.string.forward_success))\n        .setContentTitle(title)\n        .setOnlyAlertOnce(true)\n        .setContentIntent(SagerNet.configureIntent(service))\n        .setSmallIcon(R.drawable.ic_service_active)\n        .setCategory(NotificationCompat.CATEGORY_SERVICE)\n        .setPriority(if (visible) NotificationCompat.PRIORITY_LOW else NotificationCompat.PRIORITY_MIN)\n\n    private val buildLock = Mutex()\n\n    private suspend fun useBuilder(f: (NotificationCompat.Builder) -> Unit) {\n        buildLock.withLock {\n            f(builder)\n        }\n    }\n\n    init {\n        service as Context\n\n        Theme.apply(app)\n        Theme.apply(service)\n        builder.color = service.getColorAttr(R.attr.colorPrimary)\n\n        service.registerReceiver(this, IntentFilter().apply {\n            addAction(Intent.ACTION_SCREEN_ON)\n            addAction(Intent.ACTION_SCREEN_OFF)\n        })\n\n        runOnMainDispatcher {\n            updateActions()\n            show()\n        }\n    }\n\n    private suspend fun updateActions() {\n        service as Context\n        useBuilder {\n            it.clearActions()\n\n            val closeAction = NotificationCompat.Action.Builder(\n                0, service.getText(R.string.stop), PendingIntent.getBroadcast(\n                    service, 0, Intent(Action.CLOSE).setPackage(service.packageName), flags\n                )\n            ).setShowsUserInterface(false).build()\n            it.addAction(closeAction)\n\n            val switchAction = NotificationCompat.Action.Builder(\n                0, service.getString(R.string.action_switch), PendingIntent.getActivity(\n                    service, 0, Intent(service, SwitchActivity::class.java), flags\n                )\n            ).setShowsUserInterface(false).build()\n            it.addAction(switchAction)\n\n            val resetUpstreamAction = NotificationCompat.Action.Builder(\n                0, service.getString(R.string.reset_connections),\n                PendingIntent.getBroadcast(\n                    service, 0, Intent(Action.RESET_UPSTREAM_CONNECTIONS), flags\n                )\n            ).setShowsUserInterface(false).build()\n            it.addAction(resetUpstreamAction)\n        }\n    }\n\n    override fun onReceive(context: Context, intent: Intent) {\n        if (service.data.state == BaseService.State.Connected) {\n            listenPostSpeed = intent.action == Intent.ACTION_SCREEN_ON\n        }\n    }\n\n\n    private suspend fun show() =\n        useBuilder {\n            try {\n                if (Build.VERSION.SDK_INT >= 34) {\n                    (service as Service).startForeground(\n                        notificationId,\n                        it.build(),\n                        FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED\n                    )\n                } else {\n                    (service as Service).startForeground(notificationId, it.build())\n                }\n            } catch (e: Exception) {\n                Toast.makeText(\n                    SagerNet.application,\n                    \"startForeground: $e\",\n                    Toast.LENGTH_LONG\n                ).show()\n            }\n        }\n\n    private suspend fun update() = useBuilder {\n        NotificationManagerCompat.from(service as Service).notify(notificationId, it.build())\n    }\n\n    fun destroy() {\n        listenPostSpeed = false\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            (service as Service).stopForeground(Service.STOP_FOREGROUND_REMOVE)\n        } else {\n            (service as Service).stopForeground(true)\n        }\n        service.unregisterReceiver(this)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/SubscriptionUpdater.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.content.Context\nimport androidx.core.app.NotificationCompat\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.work.CoroutineWorker\nimport androidx.work.ExistingPeriodicWorkPolicy.UPDATE\nimport androidx.work.PeriodicWorkRequest\nimport androidx.work.WorkerParameters\nimport androidx.work.multiprocess.RemoteWorkManager\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.group.GroupUpdater\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport java.util.concurrent.TimeUnit\n\nobject SubscriptionUpdater {\n\n    private const val WORK_NAME = \"SubscriptionUpdater\"\n\n    suspend fun reconfigureUpdater() {\n        RemoteWorkManager.getInstance(app).cancelUniqueWork(WORK_NAME)\n\n        val subscriptions = SagerDatabase.groupDao.subscriptions()\n            .filter { it.subscription!!.autoUpdate }\n        if (subscriptions.isEmpty()) return\n\n        // PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS\n        var minDelay =\n            subscriptions.minByOrNull { it.subscription!!.autoUpdateDelay }!!.subscription!!.autoUpdateDelay.toLong()\n        val now = System.currentTimeMillis() / 1000L\n        var minInitDelay =\n            subscriptions.minOf { now - it.subscription!!.lastUpdated - (minDelay * 60) }\n        if (minDelay < 15) minDelay = 15\n        if (minInitDelay > 60) minInitDelay = 60\n\n        // main process\n        RemoteWorkManager.getInstance(app).enqueueUniquePeriodicWork(\n            WORK_NAME,\n            UPDATE,\n            PeriodicWorkRequest.Builder(UpdateTask::class.java, minDelay, TimeUnit.MINUTES)\n                .apply {\n                    if (minInitDelay > 0) setInitialDelay(minInitDelay, TimeUnit.SECONDS)\n                }\n                .build()\n        )\n    }\n\n    class UpdateTask(\n        appContext: Context, params: WorkerParameters\n    ) : CoroutineWorker(appContext, params) {\n\n        val nm = NotificationManagerCompat.from(applicationContext)\n\n        val notification = NotificationCompat.Builder(applicationContext, \"service-subscription\")\n            .setWhen(0)\n            .setTicker(applicationContext.getString(R.string.forward_success))\n            .setContentTitle(applicationContext.getString(R.string.subscription_update))\n            .setSmallIcon(R.drawable.ic_service_active)\n            .setCategory(NotificationCompat.CATEGORY_SERVICE)\n\n        override suspend fun doWork(): Result {\n            var subscriptions =\n                SagerDatabase.groupDao.subscriptions().filter { it.subscription!!.autoUpdate }\n            if (!DataStore.serviceState.connected) {\n                Logs.d(\"work: not connected\")\n                subscriptions = subscriptions.filter { !it.subscription!!.updateWhenConnectedOnly }\n            }\n\n            if (subscriptions.isNotEmpty()) for (profile in subscriptions) {\n                val subscription = profile.subscription!!\n\n                if (((System.currentTimeMillis() / 1000).toInt() - subscription.lastUpdated) < subscription.autoUpdateDelay * 60) {\n                    Logs.d(\"work: not updating \" + profile.displayName())\n                    continue\n                }\n                Logs.d(\"work: updating \" + profile.displayName())\n\n                notification.setContentText(\n                    applicationContext.getString(\n                        R.string.subscription_update_message, profile.displayName()\n                    )\n                )\n                nm.notify(2, notification.build())\n\n                GroupUpdater.executeUpdate(profile, false)\n            }\n\n            nm.cancel(2)\n\n            return Result.success()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/TileService.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.graphics.drawable.Icon\nimport android.service.quicksettings.Tile\nimport androidx.annotation.RequiresApi\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport android.service.quicksettings.TileService as BaseTileService\n\n@RequiresApi(24)\nclass TileService : BaseTileService(), SagerConnection.Callback {\n    private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle) }\n    private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_service_busy) }\n    private val iconConnected by lazy {\n        Icon.createWithResource(this, R.drawable.ic_service_active)\n    }\n    private var tapPending = false\n\n    private val connection = SagerConnection(SagerConnection.CONNECTION_ID_TILE)\n    override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) =\n        updateTile(state, profileName)\n\n    override fun onServiceConnected(service: ISagerNetService) {\n        updateTile(BaseService.State.values()[service.state], service.profileName)\n        if (tapPending) {\n            tapPending = false\n            onClick()\n        }\n    }\n\n    override fun cbSelectorUpdate(id: Long) {\n        val profile = SagerDatabase.proxyDao.getById(id) ?: return\n        updateTile(BaseService.State.Connected, profile.displayName())\n    }\n\n    override fun onStartListening() {\n        super.onStartListening()\n        connection.connect(this, this)\n    }\n\n    override fun onStopListening() {\n        connection.disconnect(this)\n        super.onStopListening()\n    }\n\n    override fun onClick() {\n        if (isLocked) unlockAndRun(this::toggle) else toggle()\n    }\n\n    private fun updateTile(serviceState: BaseService.State, profileName: String?) {\n        qsTile?.apply {\n            label = null\n            when (serviceState) {\n                BaseService.State.Idle -> error(\"serviceState\")\n                BaseService.State.Connecting -> {\n                    icon = iconBusy\n                    state = Tile.STATE_ACTIVE\n                }\n\n                BaseService.State.Connected -> {\n                    icon = iconConnected\n                    label = profileName\n                    state = Tile.STATE_ACTIVE\n                }\n\n                BaseService.State.Stopping -> {\n                    icon = iconBusy\n                    state = Tile.STATE_UNAVAILABLE\n                }\n\n                BaseService.State.Stopped -> {\n                    icon = iconIdle\n                    state = Tile.STATE_INACTIVE\n                }\n            }\n            label = label ?: getString(R.string.app_name)\n            updateTile()\n        }\n    }\n\n    private fun toggle() {\n        val service = connection.service\n        if (service == null) tapPending =\n            true else BaseService.State.values()[service.state].let { state ->\n            when {\n                state.canStop -> SagerNet.stopService()\n                state == BaseService.State.Stopped -> SagerNet.startService()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/VpnService.kt",
    "content": "package io.nekohasekai.sagernet.bg\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.app.Service\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.ProxyInfo\nimport android.os.Build\nimport android.os.ParcelFileDescriptor\nimport android.os.PowerManager\nimport io.nekohasekai.sagernet.*\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.LOCALHOST\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.ui.VpnRequestActivity\nimport io.nekohasekai.sagernet.utils.Subnet\nimport android.net.VpnService as BaseVpnService\n\nclass VpnService : BaseVpnService(),\n    BaseService.Interface {\n\n    companion object {\n\n        const val PRIVATE_VLAN4_CLIENT = \"172.19.0.1\"\n        const val PRIVATE_VLAN4_ROUTER = \"172.19.0.2\"\n        const val FAKEDNS_VLAN4_CLIENT = \"198.18.0.0\"\n        const val PRIVATE_VLAN6_CLIENT = \"fdfe:dcba:9876::1\"\n        const val PRIVATE_VLAN6_ROUTER = \"fdfe:dcba:9876::2\"\n\n    }\n\n    var conn: ParcelFileDescriptor? = null\n\n    private var metered = false\n\n    override var upstreamInterfaceName: String? = null\n\n    override suspend fun startProcesses() {\n        DataStore.vpnService = this\n        super.startProcesses() // launch proxy instance\n    }\n\n    override var wakeLock: PowerManager.WakeLock? = null\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun acquireWakeLock() {\n        wakeLock = SagerNet.power.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"sagernet:vpn\")\n            .apply { acquire() }\n    }\n\n    @Suppress(\"EXPERIMENTAL_API_USAGE\")\n    override fun killProcesses() {\n        conn?.close()\n        conn = null\n        super.killProcesses()\n    }\n\n    override fun onBind(intent: Intent) = when (intent.action) {\n        SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)\n        else -> super<BaseService.Interface>.onBind(intent)\n    }\n\n    override val data = BaseService.Data(this)\n    override val tag = \"SagerNetVpnService\"\n    override fun createNotification(profileName: String) =\n        ServiceNotification(this, profileName, \"service-vpn\")\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        if (DataStore.serviceMode == Key.MODE_VPN) {\n            if (prepare(this) != null) {\n                startActivity(\n                    Intent(\n                        this, VpnRequestActivity::class.java\n                    ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n                )\n            } else return super<BaseService.Interface>.onStartCommand(intent, flags, startId)\n        }\n        stopRunner()\n        return Service.START_NOT_STICKY\n    }\n\n    inner class NullConnectionException : NullPointerException(),\n        BaseService.ExpectedException {\n        override fun getLocalizedMessage() = getString(R.string.reboot_required)\n    }\n\n    fun startVpn(tunOptionsJson: String, tunPlatformOptionsJson: String): Int {\n//        Logs.d(tunOptionsJson)\n//        Logs.d(tunPlatformOptionsJson)\n//        val tunOptions = JSONObject(tunOptionsJson)\n\n        // address & route & MTU ...... use NB4A GUI config\n        val builder = Builder().setConfigureIntent(SagerNet.configureIntent(this))\n            .setSession(getString(R.string.app_name))\n            .setMtu(DataStore.mtu)\n        val ipv6Mode = DataStore.ipv6Mode\n\n        // address\n        builder.addAddress(PRIVATE_VLAN4_CLIENT, 30)\n        if (ipv6Mode != IPv6Mode.DISABLE) {\n            builder.addAddress(PRIVATE_VLAN6_CLIENT, 126)\n        }\n        builder.addDnsServer(PRIVATE_VLAN4_ROUTER)\n\n        // route\n        if (DataStore.bypassLan) {\n            resources.getStringArray(R.array.bypass_private_route).forEach {\n                val subnet = Subnet.fromString(it)!!\n                builder.addRoute(subnet.address.hostAddress!!, subnet.prefixSize)\n            }\n            builder.addRoute(PRIVATE_VLAN4_ROUTER, 32)\n            builder.addRoute(FAKEDNS_VLAN4_CLIENT, 15)\n            // https://issuetracker.google.com/issues/149636790\n            if (ipv6Mode != IPv6Mode.DISABLE) {\n                builder.addRoute(\"2000::\", 3)\n            }\n        } else {\n            builder.addRoute(\"0.0.0.0\", 0)\n            if (ipv6Mode != IPv6Mode.DISABLE) {\n                builder.addRoute(\"::\", 0)\n            }\n        }\n\n        updateUnderlyingNetwork(builder)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(metered)\n\n        // app route\n        val packageName = packageName\n        val proxyApps = DataStore.proxyApps\n        var bypass = DataStore.bypass\n        val workaroundSYSTEM = false /* DataStore.tunImplementation == TunImplementation.SYSTEM */\n        val needBypassRootUid = workaroundSYSTEM || data.proxy!!.config.trafficMap.values.any {\n            it[0].hysteriaBean?.protocol == HysteriaBean.PROTOCOL_FAKETCP\n        }\n\n        if (proxyApps || needBypassRootUid) {\n            val individual = mutableSetOf<String>()\n            val allApps by lazy {\n                packageManager.getInstalledPackages(PackageManager.GET_PERMISSIONS).filter {\n                    when (it.packageName) {\n                        packageName -> false\n                        \"android\" -> true\n                        else -> it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true\n                    }\n                }.map {\n                    it.packageName\n                }\n            }\n            if (proxyApps) {\n                individual.addAll(DataStore.individual.split('\\n').filter { it.isNotBlank() })\n                if (bypass && needBypassRootUid) {\n                    val individualNew = allApps.toMutableList()\n                    individualNew.removeAll(individual)\n                    individual.clear()\n                    individual.addAll(individualNew)\n                    bypass = false\n                }\n            } else {\n                individual.addAll(allApps)\n                bypass = false\n            }\n\n            val added = mutableListOf<String>()\n\n            individual.apply {\n                // Allow Matsuri itself using VPN.\n                remove(packageName)\n                if (!bypass) add(packageName)\n            }.forEach {\n                try {\n                    if (bypass) {\n                        builder.addDisallowedApplication(it)\n                    } else {\n                        builder.addAllowedApplication(it)\n                    }\n                    added.add(it)\n                } catch (ex: PackageManager.NameNotFoundException) {\n                    Logs.w(ex)\n                }\n            }\n\n            if (bypass) {\n                Logs.d(\"Add bypass: ${added.joinToString(\", \")}\")\n            } else {\n                Logs.d(\"Add allow: ${added.joinToString(\", \")}\")\n            }\n        }\n\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && DataStore.appendHttpProxy) {\n            builder.setHttpProxy(ProxyInfo.buildDirectProxy(LOCALHOST, DataStore.mixedPort))\n        }\n\n        metered = DataStore.meteredNetwork\n        if (Build.VERSION.SDK_INT >= 29) builder.setMetered(metered)\n        conn = builder.establish() ?: throw NullConnectionException()\n\n        return conn!!.fd\n    }\n\n    fun updateUnderlyingNetwork(builder: Builder? = null) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {\n            SagerNet.underlyingNetwork?.let {\n                builder?.setUnderlyingNetworks(arrayOf(SagerNet.underlyingNetwork))\n                    ?: setUnderlyingNetworks(arrayOf(SagerNet.underlyingNetwork))\n            }\n        }\n    }\n\n    override fun onRevoke() = stopRunner()\n\n    override fun onDestroy() {\n        DataStore.vpnService = null\n        super.onDestroy()\n        data.binder.close()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/BoxInstance.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nimport android.os.SystemClock\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.bg.AbstractInstance\nimport io.nekohasekai.sagernet.bg.GuardedProcessPool\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.fmt.ConfigBuildResult\nimport io.nekohasekai.sagernet.fmt.buildConfig\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.fmt.hysteria.buildHysteria1Config\nimport io.nekohasekai.sagernet.fmt.mieru.MieruBean\nimport io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig\nimport io.nekohasekai.sagernet.fmt.naive.NaiveBean\nimport io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig\nimport io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean\nimport io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.plugin.PluginManager\nimport kotlinx.coroutines.*\nimport libcore.BoxInstance\nimport libcore.Libcore\nimport moe.matsuri.nb4a.net.LocalResolverImpl\nimport java.io.File\n\nabstract class BoxInstance(\n    val profile: ProxyEntity\n) : AbstractInstance {\n\n    lateinit var config: ConfigBuildResult\n    lateinit var box: BoxInstance\n\n    val pluginPath = hashMapOf<String, PluginManager.InitResult>()\n    val pluginConfigs = hashMapOf<Int, Pair<Int, String>>()\n    val externalInstances = hashMapOf<Int, AbstractInstance>()\n    open lateinit var processes: GuardedProcessPool\n    private var cacheFiles = ArrayList<File>()\n    fun isInitialized(): Boolean {\n        return ::config.isInitialized && ::box.isInitialized\n    }\n\n    protected fun initPlugin(name: String): PluginManager.InitResult {\n        return pluginPath.getOrPut(name) { PluginManager.init(name)!! }\n    }\n\n    protected open fun buildConfig() {\n        config = buildConfig(profile)\n    }\n\n    protected open suspend fun loadConfig() {\n        box = Libcore.newSingBoxInstance(config.config, LocalResolverImpl)\n    }\n\n    open suspend fun init() {\n        buildConfig()\n        for ((chain) in config.externalIndex) {\n            chain.entries.forEachIndexed { index, (port, profile) ->\n                when (val bean = profile.requireBean()) {\n                    is TrojanGoBean -> {\n                        initPlugin(\"trojan-go-plugin\")\n                        pluginConfigs[port] = profile.type to bean.buildTrojanGoConfig(port)\n                    }\n\n                    is MieruBean -> {\n                        initPlugin(\"mieru-plugin\")\n                        pluginConfigs[port] = profile.type to bean.buildMieruConfig(port)\n                    }\n\n                    is NaiveBean -> {\n                        initPlugin(\"naive-plugin\")\n                        pluginConfigs[port] = profile.type to bean.buildNaiveConfig(port)\n                    }\n\n                    is HysteriaBean -> {\n                        initPlugin(\"hysteria-plugin\")\n                        pluginConfigs[port] = profile.type to bean.buildHysteria1Config(port) {\n                            File(\n                                app.cacheDir, \"hysteria_\" + SystemClock.elapsedRealtime() + \".ca\"\n                            ).apply {\n                                parentFile?.mkdirs()\n                                cacheFiles.add(this)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        loadConfig()\n    }\n\n    override fun launch() {\n        // TODO move, this is not box\n        val cacheDir = File(SagerNet.application.cacheDir, \"tmpcfg\")\n        cacheDir.mkdirs()\n\n        for ((chain) in config.externalIndex) {\n            chain.entries.forEachIndexed { index, (port, profile) ->\n                val bean = profile.requireBean()\n                val needChain = index != chain.size - 1\n                val (profileType, config) = pluginConfigs[port] ?: (0 to \"\")\n\n                when {\n                    externalInstances.containsKey(port) -> {\n                        externalInstances[port]!!.launch()\n                    }\n\n                    bean is TrojanGoBean -> {\n                        val configFile = File(\n                            cacheDir, \"trojan_go_\" + SystemClock.elapsedRealtime() + \".json\"\n                        )\n                        configFile.parentFile?.mkdirs()\n                        configFile.writeText(config)\n                        cacheFiles.add(configFile)\n\n                        val commands = mutableListOf(\n                            initPlugin(\"trojan-go-plugin\").path, \"-config\", configFile.absolutePath\n                        )\n\n                        processes.start(commands)\n                    }\n\n                    bean is MieruBean -> {\n                        val configFile = File(\n                            cacheDir, \"mieru_\" + SystemClock.elapsedRealtime() + \".json\"\n                        )\n\n                        configFile.parentFile?.mkdirs()\n                        configFile.writeText(config)\n                        cacheFiles.add(configFile)\n\n                        val envMap = mutableMapOf<String, String>()\n                        envMap[\"MIERU_CONFIG_JSON_FILE\"] = configFile.absolutePath\n                        envMap[\"MIERU_PROTECT_PATH\"] = \"protect_path\"\n\n                        val commands = mutableListOf(\n                            initPlugin(\"mieru-plugin\").path, \"run\",\n                        )\n\n                        processes.start(commands, envMap)\n                    }\n\n                    bean is NaiveBean -> {\n                        val configFile = File(\n                            cacheDir, \"naive_\" + SystemClock.elapsedRealtime() + \".json\"\n                        )\n\n                        configFile.parentFile?.mkdirs()\n                        configFile.writeText(config)\n                        cacheFiles.add(configFile)\n\n                        val envMap = mutableMapOf<String, String>()\n\n                        if (bean.certificates.isNotBlank()) {\n                            val certFile = File(\n                                cacheDir, \"naive_\" + SystemClock.elapsedRealtime() + \".crt\"\n                            )\n\n                            certFile.parentFile?.mkdirs()\n                            certFile.writeText(bean.certificates)\n                            cacheFiles.add(certFile)\n\n                            envMap[\"SSL_CERT_FILE\"] = certFile.absolutePath\n                        }\n\n                        val commands = mutableListOf(\n                            initPlugin(\"naive-plugin\").path, configFile.absolutePath\n                        )\n\n                        processes.start(commands, envMap)\n                    }\n\n                    bean is HysteriaBean -> {\n                        val configFile = File(\n                            cacheDir, \"hysteria_\" + SystemClock.elapsedRealtime() + \".json\"\n                        )\n\n                        configFile.parentFile?.mkdirs()\n                        configFile.writeText(config)\n                        cacheFiles.add(configFile)\n\n                        val commands = mutableListOf(\n                            initPlugin(\"hysteria-plugin\").path,\n                            \"--no-check\",\n                            \"--config\",\n                            configFile.absolutePath,\n                            \"--log-level\",\n                            if (DataStore.logLevel > 0) \"trace\" else \"warn\",\n                            \"client\"\n                        )\n\n                        if (bean.protocol == HysteriaBean.PROTOCOL_FAKETCP) {\n                            commands.addAll(0, listOf(\"su\", \"-c\"))\n                        }\n\n                        processes.start(commands)\n                    }\n                }\n            }\n        }\n\n        box.start()\n    }\n\n    @Suppress(\"EXPERIMENTAL_API_USAGE\")\n    override fun close() {\n        for (instance in externalInstances.values) {\n            runCatching {\n                instance.close()\n            }\n        }\n\n        cacheFiles.removeAll { it.delete(); true }\n\n        if (::processes.isInitialized) processes.close(GlobalScope + Dispatchers.IO)\n\n        if (::box.isInitialized) {\n            box.close()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.ServiceNotification\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport kotlinx.coroutines.runBlocking\nimport moe.matsuri.nb4a.utils.JavaUtil\n\nclass ProxyInstance(profile: ProxyEntity, var service: BaseService.Interface? = null) :\n    BoxInstance(profile) {\n\n    var notTmp = true\n\n    var lastSelectorGroupId = -1L\n    var displayProfileName = ServiceNotification.genTitle(profile)\n\n    // for TrafficLooper\n    var looper: TrafficLooper? = null\n\n    override fun buildConfig() {\n        super.buildConfig()\n        lastSelectorGroupId = super.config.selectorGroupId\n        //\n        if (notTmp) Logs.d(config.config)\n        if (notTmp && BuildConfig.DEBUG) Logs.d(JavaUtil.gson.toJson(config.trafficMap))\n    }\n\n    // only use this in temporary instance\n    fun buildConfigTmp() {\n        notTmp = false\n        buildConfig()\n    }\n\n    override suspend fun init() {\n        super.init()\n        pluginConfigs.forEach { (_, plugin) ->\n            val (_, content) = plugin\n            Logs.d(content)\n        }\n    }\n\n    override suspend fun loadConfig() {\n        super.loadConfig()\n    }\n\n    override fun launch() {\n        box.setAsMain()\n        super.launch() // start box\n        runOnDefaultDispatcher {\n            looper = service?.let { TrafficLooper(it.data, this) }\n            looper?.start()\n        }\n    }\n\n    override fun close() {\n        super.close()\n        runBlocking {\n            looper?.stop()\n            looper = null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/TestInstance.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.bg.GuardedProcessPool\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.fmt.buildConfig\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.ktx.tryResume\nimport io.nekohasekai.sagernet.ktx.tryResumeWithException\nimport kotlinx.coroutines.delay\nimport libcore.Libcore\nimport moe.matsuri.nb4a.net.LocalResolverImpl\nimport kotlin.coroutines.suspendCoroutine\n\nclass TestInstance(profile: ProxyEntity, val link: String, private val timeout: Int) :\n    BoxInstance(profile) {\n\n    suspend fun doTest(): Int {\n        return suspendCoroutine { c ->\n            processes = GuardedProcessPool {\n                Logs.w(it)\n                c.tryResumeWithException(it)\n            }\n            runOnDefaultDispatcher {\n                use {\n                    try {\n                        init()\n                        launch()\n                        if (processes.processCount > 0) {\n                            // wait for plugin start\n                            delay(500)\n                        }\n                        c.tryResume(Libcore.urlTest(box, link, timeout))\n                    } catch (e: Exception) {\n                        c.tryResumeWithException(e)\n                    }\n                }\n            }\n        }\n    }\n\n    override fun buildConfig() {\n        config = buildConfig(profile, true)\n    }\n\n    override suspend fun loadConfig() {\n        // don't call destroyAllJsi here\n        if (BuildConfig.DEBUG) Logs.d(config.config)\n        box = Libcore.newSingBoxInstance(config.config, LocalResolverImpl)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficLooper.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nimport io.nekohasekai.sagernet.aidl.SpeedDisplayData\nimport io.nekohasekai.sagernet.aidl.TrafficData\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.fmt.TAG_BYPASS\nimport io.nekohasekai.sagernet.fmt.TAG_PROXY\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport kotlinx.coroutines.*\n\nclass TrafficLooper\n    (\n    val data: BaseService.Data, private val sc: CoroutineScope\n) {\n\n    private var job: Job? = null\n    private val idMap = mutableMapOf<Long, TrafficUpdater.TrafficLooperData>() // id to 1 data\n    private val tagMap = mutableMapOf<String, TrafficUpdater.TrafficLooperData>() // tag to 1 data\n\n    suspend fun stop() {\n        job?.cancel()\n        // finally traffic post\n        if (!DataStore.profileTrafficStatistics) return\n        val traffic = mutableMapOf<Long, TrafficData>()\n        data.proxy?.config?.trafficMap?.forEach { (_, ents) ->\n            for (ent in ents) {\n                val item = idMap[ent.id] ?: return@forEach\n                ent.rx = item.rx\n                ent.tx = item.tx\n                ProfileManager.updateProfile(ent) // update DB\n                traffic[ent.id] = TrafficData(\n                    id = ent.id,\n                    rx = ent.rx,\n                    tx = ent.tx,\n                )\n            }\n        }\n        data.binder.broadcast { b ->\n            for (t in traffic) {\n                b.cbTrafficUpdate(t.value)\n            }\n        }\n        Logs.d(\"finally traffic post done\")\n    }\n\n    fun start() {\n        job = sc.launch { loop() }\n    }\n\n    var selectorNowId = -114514L\n    var selectorNowFakeTag = \"\"\n\n    fun selectMain(id: Long) {\n        Logs.d(\"select traffic count $TAG_PROXY to $id, old id is $selectorNowId\")\n        val oldData = idMap[selectorNowId]\n        val newData = idMap[id] ?: return\n        oldData?.apply {\n            tag = selectorNowFakeTag\n            ignore = true\n            // post traffic when switch\n            if (DataStore.profileTrafficStatistics) {\n                data.proxy?.config?.trafficMap?.get(tag)?.firstOrNull()?.let {\n                    it.rx = rx\n                    it.tx = tx\n                    runOnDefaultDispatcher {\n                        ProfileManager.updateProfile(it) // update DB\n                    }\n                }\n            }\n        }\n        selectorNowFakeTag = newData.tag\n        selectorNowId = id\n        newData.apply {\n            tag = TAG_PROXY\n            ignore = false\n        }\n    }\n\n    private suspend fun loop() {\n        val delayMs = DataStore.speedInterval.toLong()\n        val showDirectSpeed = DataStore.showDirectSpeed\n        val profileTrafficStatistics = DataStore.profileTrafficStatistics\n        if (delayMs == 0L) return\n\n        var trafficUpdater: TrafficUpdater? = null\n        var proxy: ProxyInstance?\n\n        // for display\n        val itemBypass = TrafficUpdater.TrafficLooperData(tag = TAG_BYPASS)\n\n        while (sc.isActive) {\n            proxy = data.proxy\n            if (proxy == null) {\n                delay(delayMs)\n                continue\n            }\n\n            if (trafficUpdater == null) {\n                if (!proxy.isInitialized()) continue\n                idMap.clear()\n                idMap[-1] = itemBypass\n                //\n                val tags = hashSetOf(TAG_PROXY, TAG_BYPASS)\n                proxy.config.trafficMap.forEach { (tag, ents) ->\n                    tags.add(tag)\n                    for (ent in ents) {\n                        val item = TrafficUpdater.TrafficLooperData(\n                            tag = tag,\n                            rx = ent.rx,\n                            tx = ent.tx,\n                            rxBase = ent.rx,\n                            txBase = ent.tx,\n                            ignore = proxy.config.selectorGroupId >= 0L,\n                        )\n                        idMap[ent.id] = item\n                        tagMap[tag] = item\n                        Logs.d(\"traffic count $tag to ${ent.id}\")\n                    }\n                }\n                if (proxy.config.selectorGroupId >= 0L) {\n                    selectMain(proxy.config.mainEntId)\n                }\n                //\n                trafficUpdater = TrafficUpdater(\n                    box = proxy.box, items = idMap.values.toList()\n                )\n                proxy.box.setV2rayStats(tags.joinToString(\"\\n\"))\n            }\n\n            trafficUpdater.updateAll()\n            if (!sc.isActive) return\n\n            // add all non-bypass to \"main\"\n            var mainTxRate = 0L\n            var mainRxRate = 0L\n            var mainTx = 0L\n            var mainRx = 0L\n            tagMap.forEach { (_, it) ->\n                if (!it.ignore) {\n                    mainTxRate += it.txRate\n                    mainRxRate += it.rxRate\n                }\n                mainTx += it.tx - it.txBase\n                mainRx += it.rx - it.rxBase\n            }\n\n            // speed\n            val speed = SpeedDisplayData(\n                mainTxRate,\n                mainRxRate,\n                if (showDirectSpeed) itemBypass.txRate else 0L,\n                if (showDirectSpeed) itemBypass.rxRate else 0L,\n                mainTx,\n                mainRx\n            )\n\n            // broadcast (MainActivity)\n            if (data.state == BaseService.State.Connected\n                && data.binder.callbackIdMap.containsValue(SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND)\n            ) {\n                data.binder.broadcast { b ->\n                    if (data.binder.callbackIdMap[b] == SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND) {\n                        b.cbSpeedUpdate(speed)\n                        if (profileTrafficStatistics) {\n                            idMap.forEach { (id, item) ->\n                                b.cbTrafficUpdate(\n                                    TrafficData(id = id, rx = item.rx, tx = item.tx) // display\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n\n            // ServiceNotification\n            data.notification?.apply {\n                if (listenPostSpeed) postNotificationSpeedUpdate(speed)\n            }\n\n            delay(delayMs)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/TrafficUpdater.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nclass TrafficUpdater(\n    private val box: libcore.BoxInstance,\n    val items: List<TrafficLooperData>, // contain \"bypass\"\n) {\n\n    class TrafficLooperData(\n        // Don't associate proxyEntity\n        var tag: String,\n        var tx: Long = 0,\n        var rx: Long = 0,\n        var txBase: Long = 0,\n        var rxBase: Long = 0,\n        var txRate: Long = 0,\n        var rxRate: Long = 0,\n        var lastUpdate: Long = 0,\n        var ignore: Boolean = false,\n    )\n\n    private fun updateOne(item: TrafficLooperData): TrafficLooperData {\n        // last update\n        val now = System.currentTimeMillis()\n        val interval = now - item.lastUpdate\n        item.lastUpdate = now\n        if (interval <= 0) return item.apply {\n            rxRate = 0\n            txRate = 0\n        }\n\n        // query\n        val tx = box.queryStats(item.tag, \"uplink\")\n        val rx = box.queryStats(item.tag, \"downlink\")\n\n        // add diff\n        item.rx += rx\n        item.tx += tx\n        item.rxRate = rx * 1000 / interval\n        item.txRate = tx * 1000 / interval\n\n        // return diff\n        return TrafficLooperData(\n            tag = item.tag,\n            rx = rx,\n            tx = tx,\n            rxRate = item.rxRate,\n            txRate = item.txRate,\n        )\n    }\n\n    suspend fun updateAll() {\n        val updated = mutableMapOf<String, TrafficLooperData>() // diffs\n        items.forEach { item ->\n            if (item.ignore) return@forEach\n            var diff = updated[item.tag]\n            // query a tag only once\n            if (diff == null) {\n                diff = updateOne(item)\n                updated[item.tag] = diff\n            } else {\n                item.rx += diff.rx\n                item.tx += diff.tx\n                item.rxRate = diff.rxRate\n                item.txRate = diff.txRate\n            }\n        }\n//        Logs.d(JavaUtil.gson.toJson(items))\n//        Logs.d(JavaUtil.gson.toJson(updated))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/bg/proto/UrlTest.kt",
    "content": "package io.nekohasekai.sagernet.bg.proto\n\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProxyEntity\n\nclass UrlTest {\n\n    val link = DataStore.connectionTestURL\n    private val timeout = 5000\n\n    suspend fun doTest(profile: ProxyEntity): Int {\n        return TestInstance(profile, link, timeout).doTest()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport android.os.Binder\nimport androidx.preference.PreferenceDataStore\nimport io.nekohasekai.sagernet.CONNECTION_TEST_URL\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.IPv6Mode\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.TunImplementation\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.VpnService\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.database.preference.PublicDatabase\nimport io.nekohasekai.sagernet.database.preference.RoomPreferenceDataStore\nimport io.nekohasekai.sagernet.ktx.boolean\nimport io.nekohasekai.sagernet.ktx.int\nimport io.nekohasekai.sagernet.ktx.long\nimport io.nekohasekai.sagernet.ktx.parsePort\nimport io.nekohasekai.sagernet.ktx.string\nimport io.nekohasekai.sagernet.ktx.stringToInt\nimport io.nekohasekai.sagernet.ktx.stringToIntIfExists\nimport moe.matsuri.nb4a.TempDatabase\n\nobject DataStore : OnPreferenceDataStoreChangeListener {\n\n    // share service state in main & bg process\n    @Volatile\n    var serviceState = BaseService.State.Idle\n\n    val configurationStore = RoomPreferenceDataStore(PublicDatabase.kvPairDao)\n    val profileCacheStore = RoomPreferenceDataStore(TempDatabase.profileCacheDao)\n\n    // last used, but may not be running\n    var currentProfile by configurationStore.long(Key.PROFILE_CURRENT)\n\n    var selectedProxy by configurationStore.long(Key.PROFILE_ID)\n    var selectedGroup by configurationStore.long(Key.PROFILE_GROUP) { currentGroupId() } // \"ungrouped\" group id = 1\n\n    // only in bg process\n    var vpnService: VpnService? = null\n    var baseService: BaseService.Interface? = null\n\n    // main\n\n    var runningTest = false\n\n    fun currentGroupId(): Long {\n        val currentSelected = configurationStore.getLong(Key.PROFILE_GROUP, -1)\n        if (currentSelected > 0L) return currentSelected\n        val groups = SagerDatabase.groupDao.allGroups()\n        if (groups.isNotEmpty()) {\n            val groupId = groups[0].id\n            selectedGroup = groupId\n            return groupId\n        }\n        val groupId = SagerDatabase.groupDao.createGroup(ProxyGroup(ungrouped = true))\n        selectedGroup = groupId\n        return groupId\n    }\n\n    fun currentGroup(): ProxyGroup {\n        var group: ProxyGroup? = null\n        val currentSelected = configurationStore.getLong(Key.PROFILE_GROUP, -1)\n        if (currentSelected > 0L) {\n            group = SagerDatabase.groupDao.getById(currentSelected)\n        }\n        if (group != null) return group\n        val groups = SagerDatabase.groupDao.allGroups()\n        if (groups.isEmpty()) {\n            group = ProxyGroup(ungrouped = true).apply {\n                id = SagerDatabase.groupDao.createGroup(this)\n            }\n        } else {\n            group = groups[0]\n        }\n        selectedGroup = group.id\n        return group\n    }\n\n    fun selectedGroupForImport(): Long {\n        val current = currentGroup()\n        if (current.type == GroupType.BASIC) return current.id\n        val groups = SagerDatabase.groupDao.allGroups()\n        return groups.find { it.type == GroupType.BASIC }!!.id\n    }\n\n    var appTLSVersion by configurationStore.string(Key.APP_TLS_VERSION)\n    var enableClashAPI by configurationStore.boolean(Key.ENABLE_CLASH_API)\n    var showBottomBar by configurationStore.boolean(Key.SHOW_BOTTOM_BAR)\n\n    var allowInsecureOnRequest by configurationStore.boolean(Key.ALLOW_INSECURE_ON_REQUEST)\n    var networkChangeResetConnections by configurationStore.boolean(Key.NETWORK_CHANGE_RESET_CONNECTIONS) { true }\n    var wakeResetConnections by configurationStore.boolean(Key.WAKE_RESET_CONNECTIONS)\n\n    //\n\n    var isExpert by configurationStore.boolean(Key.APP_EXPERT)\n    var appTheme by configurationStore.int(Key.APP_THEME)\n    var nightTheme by configurationStore.stringToInt(Key.NIGHT_THEME)\n    var serviceMode by configurationStore.string(Key.SERVICE_MODE) { Key.MODE_VPN }\n\n    var trafficSniffing by configurationStore.stringToInt(Key.TRAFFIC_SNIFFING) { 1 }\n    var resolveDestination by configurationStore.boolean(Key.RESOLVE_DESTINATION)\n\n    var mtu by configurationStore.stringToInt(Key.MTU) { 9000 }\n\n    var bypassLan by configurationStore.boolean(Key.BYPASS_LAN)\n    var bypassLanInCore by configurationStore.boolean(Key.BYPASS_LAN_IN_CORE)\n\n    var allowAccess by configurationStore.boolean(Key.ALLOW_ACCESS)\n    var speedInterval by configurationStore.stringToInt(Key.SPEED_INTERVAL)\n    var showGroupInNotification by configurationStore.boolean(\"showGroupInNotification\")\n\n    var globalCustomConfig by configurationStore.string(Key.GLOBAL_CUSTOM_CONFIG) { \"\" }\n\n    var remoteDns by configurationStore.string(Key.REMOTE_DNS) { \"https://dns.google/dns-query\" }\n    var directDns by configurationStore.string(Key.DIRECT_DNS) { \"https://223.5.5.5/dns-query\" }\n    var enableDnsRouting by configurationStore.boolean(Key.ENABLE_DNS_ROUTING) { true }\n    var enableFakeDns by configurationStore.boolean(Key.ENABLE_FAKEDNS) { true }\n\n    var rulesProvider by configurationStore.stringToInt(Key.RULES_PROVIDER)\n    var logLevel by configurationStore.stringToInt(Key.LOG_LEVEL)\n    var logBufSize by configurationStore.int(Key.LOG_BUF_SIZE) { 0 }\n    var acquireWakeLock by configurationStore.boolean(Key.ACQUIRE_WAKE_LOCK)\n\n    // hopefully hashCode = mHandle doesn't change, currently this is true from KitKat to Nougat\n    private val userIndex by lazy { Binder.getCallingUserHandle().hashCode() }\n    var mixedPort: Int\n        get() = getLocalPort(Key.MIXED_PORT, 2080)\n        set(value) = saveLocalPort(Key.MIXED_PORT, value)\n\n    fun initGlobal() {\n        if (configurationStore.getString(Key.MIXED_PORT) == null) {\n            mixedPort = mixedPort\n        }\n    }\n\n\n    private fun getLocalPort(key: String, default: Int): Int {\n        return parsePort(configurationStore.getString(key), default + userIndex)\n    }\n\n    private fun saveLocalPort(key: String, value: Int) {\n        configurationStore.putString(key, \"$value\")\n    }\n\n    var ipv6Mode by configurationStore.stringToInt(Key.IPV6_MODE) { IPv6Mode.DISABLE }\n\n    var meteredNetwork by configurationStore.boolean(Key.METERED_NETWORK)\n    var proxyApps by configurationStore.boolean(Key.PROXY_APPS)\n    var bypass by configurationStore.boolean(Key.BYPASS_MODE) { true }\n    var individual by configurationStore.string(Key.INDIVIDUAL)\n    var showDirectSpeed by configurationStore.boolean(Key.SHOW_DIRECT_SPEED) { true }\n\n    val persistAcrossReboot by configurationStore.boolean(Key.PERSIST_ACROSS_REBOOT) { false }\n\n    var appendHttpProxy by configurationStore.boolean(Key.APPEND_HTTP_PROXY)\n    var connectionTestURL by configurationStore.string(Key.CONNECTION_TEST_URL) { CONNECTION_TEST_URL }\n    var connectionTestConcurrent by configurationStore.int(\"connectionTestConcurrent\") { 5 }\n    var alwaysShowAddress by configurationStore.boolean(Key.ALWAYS_SHOW_ADDRESS)\n\n    var tunImplementation by configurationStore.stringToInt(Key.TUN_IMPLEMENTATION) { TunImplementation.GVISOR }\n    var profileTrafficStatistics by configurationStore.boolean(Key.PROFILE_TRAFFIC_STATISTICS) { true }\n\n    var yacdURL by configurationStore.string(\"yacdURL\") { \"http://127.0.0.1:9090/ui\" }\n\n    // protocol\n\n    var globalAllowInsecure by configurationStore.boolean(Key.GLOBAL_ALLOW_INSECURE) { false }\n\n    // old cache, DO NOT ADD\n\n    var dirty by profileCacheStore.boolean(Key.PROFILE_DIRTY)\n    var editingId by profileCacheStore.long(Key.PROFILE_ID)\n    var editingGroup by profileCacheStore.long(Key.PROFILE_GROUP)\n    var profileName by profileCacheStore.string(Key.PROFILE_NAME)\n    var serverAddress by profileCacheStore.string(Key.SERVER_ADDRESS)\n    var serverPort by profileCacheStore.stringToInt(Key.SERVER_PORT)\n    var serverPorts by profileCacheStore.string(\"serverPorts\")\n    var serverUsername by profileCacheStore.string(Key.SERVER_USERNAME)\n    var serverPassword by profileCacheStore.string(Key.SERVER_PASSWORD)\n    var serverPassword1 by profileCacheStore.string(Key.SERVER_PASSWORD1)\n    var serverMethod by profileCacheStore.string(Key.SERVER_METHOD)\n\n    var sharedStorage by profileCacheStore.string(\"sharedStorage\")\n\n    var serverProtocol by profileCacheStore.string(Key.SERVER_PROTOCOL)\n    var serverObfs by profileCacheStore.string(Key.SERVER_OBFS)\n\n    var serverNetwork by profileCacheStore.string(Key.SERVER_NETWORK)\n    var serverHost by profileCacheStore.string(Key.SERVER_HOST)\n    var serverPath by profileCacheStore.string(Key.SERVER_PATH)\n    var serverSNI by profileCacheStore.string(Key.SERVER_SNI)\n    var serverEncryption by profileCacheStore.string(Key.SERVER_ENCRYPTION)\n    var serverALPN by profileCacheStore.string(Key.SERVER_ALPN)\n    var serverCertificates by profileCacheStore.string(Key.SERVER_CERTIFICATES)\n    var serverMTU by profileCacheStore.stringToInt(Key.SERVER_MTU)\n    var serverHeaders by profileCacheStore.string(Key.SERVER_HEADERS)\n    var serverAllowInsecure by profileCacheStore.boolean(Key.SERVER_ALLOW_INSECURE)\n\n    var serverAuthType by profileCacheStore.stringToInt(Key.SERVER_AUTH_TYPE)\n    var serverUploadSpeed by profileCacheStore.stringToInt(Key.SERVER_UPLOAD_SPEED)\n    var serverDownloadSpeed by profileCacheStore.stringToInt(Key.SERVER_DOWNLOAD_SPEED)\n    var serverStreamReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_STREAM_RECEIVE_WINDOW)\n    var serverConnectionReceiveWindow by profileCacheStore.stringToIntIfExists(Key.SERVER_CONNECTION_RECEIVE_WINDOW)\n    var serverDisableMtuDiscovery by profileCacheStore.boolean(Key.SERVER_DISABLE_MTU_DISCOVERY)\n    var serverHopInterval by profileCacheStore.stringToInt(Key.SERVER_HOP_INTERVAL) { 10 }\n\n    var protocolVersion by profileCacheStore.stringToInt(Key.PROTOCOL_VERSION) { 2 } // default is SOCKS5\n\n    var serverProtocolInt by profileCacheStore.stringToInt(Key.SERVER_PROTOCOL)\n    var serverPrivateKey by profileCacheStore.string(Key.SERVER_PRIVATE_KEY)\n    var serverInsecureConcurrency by profileCacheStore.stringToInt(Key.SERVER_INSECURE_CONCURRENCY)\n\n    var serverUDPRelayMode by profileCacheStore.string(Key.SERVER_UDP_RELAY_MODE)\n    var serverCongestionController by profileCacheStore.string(Key.SERVER_CONGESTION_CONTROLLER)\n    var serverDisableSNI by profileCacheStore.boolean(Key.SERVER_DISABLE_SNI)\n    var serverReduceRTT by profileCacheStore.boolean(Key.SERVER_REDUCE_RTT)\n\n    var routeName by profileCacheStore.string(Key.ROUTE_NAME)\n    var routeDomain by profileCacheStore.string(Key.ROUTE_DOMAIN)\n    var routeIP by profileCacheStore.string(Key.ROUTE_IP)\n    var routePort by profileCacheStore.string(Key.ROUTE_PORT)\n    var routeSourcePort by profileCacheStore.string(Key.ROUTE_SOURCE_PORT)\n    var routeNetwork by profileCacheStore.string(Key.ROUTE_NETWORK)\n    var routeSource by profileCacheStore.string(Key.ROUTE_SOURCE)\n    var routeProtocol by profileCacheStore.string(Key.ROUTE_PROTOCOL)\n    var routeOutbound by profileCacheStore.stringToInt(Key.ROUTE_OUTBOUND)\n    var routeOutboundRule by profileCacheStore.long(Key.ROUTE_OUTBOUND + \"Long\")\n    var routePackages by profileCacheStore.string(Key.ROUTE_PACKAGES)\n\n    var frontProxy by profileCacheStore.long(Key.GROUP_FRONT_PROXY + \"Long\")\n    var landingProxy by profileCacheStore.long(Key.GROUP_LANDING_PROXY + \"Long\")\n    var frontProxyTmp by profileCacheStore.stringToInt(Key.GROUP_FRONT_PROXY)\n    var landingProxyTmp by profileCacheStore.stringToInt(Key.GROUP_LANDING_PROXY)\n\n    var serverConfig by profileCacheStore.string(Key.SERVER_CONFIG)\n    var serverCustom by profileCacheStore.string(Key.SERVER_CUSTOM)\n    var serverCustomOutbound by profileCacheStore.string(Key.SERVER_CUSTOM_OUTBOUND)\n\n    var groupName by profileCacheStore.string(Key.GROUP_NAME)\n    var groupType by profileCacheStore.stringToInt(Key.GROUP_TYPE)\n    var groupOrder by profileCacheStore.stringToInt(Key.GROUP_ORDER)\n    var groupIsSelector by profileCacheStore.boolean(Key.GROUP_IS_SELECTOR)\n\n    var subscriptionLink by profileCacheStore.string(Key.SUBSCRIPTION_LINK)\n    var subscriptionForceResolve by profileCacheStore.boolean(Key.SUBSCRIPTION_FORCE_RESOLVE)\n    var subscriptionDeduplication by profileCacheStore.boolean(Key.SUBSCRIPTION_DEDUPLICATION)\n    var subscriptionUpdateWhenConnectedOnly by profileCacheStore.boolean(Key.SUBSCRIPTION_UPDATE_WHEN_CONNECTED_ONLY)\n    var subscriptionUserAgent by profileCacheStore.string(Key.SUBSCRIPTION_USER_AGENT)\n    var subscriptionAutoUpdate by profileCacheStore.boolean(Key.SUBSCRIPTION_AUTO_UPDATE)\n    var subscriptionAutoUpdateDelay by profileCacheStore.stringToInt(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY) { 360 }\n\n    var rulesFirstCreate by profileCacheStore.boolean(\"rulesFirstCreate\")\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/GroupManager.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.bg.SubscriptionUpdater\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\n\nobject GroupManager {\n\n    interface Listener {\n        suspend fun groupAdd(group: ProxyGroup)\n        suspend fun groupUpdated(group: ProxyGroup)\n\n        suspend fun groupRemoved(groupId: Long)\n        suspend fun groupUpdated(groupId: Long)\n    }\n\n    interface Interface {\n        suspend fun confirm(message: String): Boolean\n        suspend fun alert(message: String)\n        suspend fun onUpdateSuccess(\n            group: ProxyGroup,\n            changed: Int,\n            added: List<String>,\n            updated: Map<String, String>,\n            deleted: List<String>,\n            duplicate: List<String>,\n            byUser: Boolean\n        )\n\n        suspend fun onUpdateFailure(group: ProxyGroup, message: String)\n    }\n\n    private val listeners = ArrayList<Listener>()\n    var userInterface: Interface? = null\n\n    suspend fun iterator(what: suspend Listener.() -> Unit) {\n        synchronized(listeners) {\n            listeners.toList()\n        }.forEach { listener ->\n            what(listener)\n        }\n    }\n\n    fun addListener(listener: Listener) {\n        synchronized(listeners) {\n            listeners.add(listener)\n        }\n    }\n\n    fun removeListener(listener: Listener) {\n        synchronized(listeners) {\n            listeners.remove(listener)\n        }\n    }\n\n    suspend fun clearGroup(groupId: Long) {\n        DataStore.selectedProxy = 0L\n        SagerDatabase.proxyDao.deleteAll(groupId)\n        iterator { groupUpdated(groupId) }\n    }\n\n    fun rearrange(groupId: Long) {\n        val entities = SagerDatabase.proxyDao.getByGroup(groupId)\n        for (index in entities.indices) {\n            entities[index].userOrder = (index + 1).toLong()\n        }\n        SagerDatabase.proxyDao.updateProxy(entities)\n    }\n\n    suspend fun postUpdate(group: ProxyGroup) {\n        iterator { groupUpdated(group) }\n    }\n\n    suspend fun postUpdate(groupId: Long) {\n        postUpdate(SagerDatabase.groupDao.getById(groupId) ?: return)\n    }\n\n    suspend fun postReload(groupId: Long) {\n        iterator { groupUpdated(groupId) }\n    }\n\n    suspend fun createGroup(group: ProxyGroup): ProxyGroup {\n        group.userOrder = SagerDatabase.groupDao.nextOrder() ?: 1\n        group.id = SagerDatabase.groupDao.createGroup(group.applyDefaultValues())\n        iterator { groupAdd(group) }\n        if (group.type == GroupType.SUBSCRIPTION) {\n            SubscriptionUpdater.reconfigureUpdater()\n        }\n        return group\n    }\n\n    suspend fun updateGroup(group: ProxyGroup) {\n        SagerDatabase.groupDao.updateGroup(group)\n        iterator { groupUpdated(group) }\n        if (group.type == GroupType.SUBSCRIPTION) {\n            SubscriptionUpdater.reconfigureUpdater()\n        }\n    }\n\n    suspend fun deleteGroup(groupId: Long) {\n        SagerDatabase.groupDao.deleteById(groupId)\n        SagerDatabase.proxyDao.deleteByGroup(groupId)\n        iterator { groupRemoved(groupId) }\n        SubscriptionUpdater.reconfigureUpdater()\n    }\n\n    suspend fun deleteGroup(group: List<ProxyGroup>) {\n        SagerDatabase.groupDao.deleteGroup(group)\n        SagerDatabase.proxyDao.deleteByGroup(group.map { it.id }.toLongArray())\n        for (proxyGroup in group) iterator { groupRemoved(proxyGroup.id) }\n        SubscriptionUpdater.reconfigureUpdater()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/ParcelizeBridge.java",
    "content": "package io.nekohasekai.sagernet.database;\n\nimport android.os.Parcel;\n\n/**\n * see: https://youtrack.jetbrains.com/issue/KT-19853\n */\npublic class ParcelizeBridge {\n\n    public static RuleEntity createRule(Parcel parcel) {\n        return (RuleEntity) RuleEntity.CREATOR.createFromParcel(parcel);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/ProfileManager.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport android.database.sqlite.SQLiteCantOpenDatabaseException\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.aidl.TrafficData\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\nimport java.io.IOException\nimport java.sql.SQLException\nimport java.util.*\n\n\nobject ProfileManager {\n\n    interface Listener {\n        suspend fun onAdd(profile: ProxyEntity)\n        suspend fun onUpdated(data: TrafficData)\n        suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean)\n        suspend fun onRemoved(groupId: Long, profileId: Long)\n    }\n\n    interface RuleListener {\n        suspend fun onAdd(rule: RuleEntity)\n        suspend fun onUpdated(rule: RuleEntity)\n        suspend fun onRemoved(ruleId: Long)\n        suspend fun onCleared()\n    }\n\n    private val listeners = ArrayList<Listener>()\n    private val ruleListeners = ArrayList<RuleListener>()\n\n    suspend fun iterator(what: suspend Listener.() -> Unit) {\n        synchronized(listeners) {\n            listeners.toList()\n        }.forEach { listener ->\n            what(listener)\n        }\n    }\n\n    suspend fun ruleIterator(what: suspend RuleListener.() -> Unit) {\n        val ruleListeners = synchronized(ruleListeners) {\n            ruleListeners.toList()\n        }\n        for (listener in ruleListeners) {\n            what(listener)\n        }\n    }\n\n    fun addListener(listener: Listener) {\n        synchronized(listeners) {\n            listeners.add(listener)\n        }\n    }\n\n    fun removeListener(listener: Listener) {\n        synchronized(listeners) {\n            listeners.remove(listener)\n        }\n    }\n\n    fun addListener(listener: RuleListener) {\n        synchronized(ruleListeners) {\n            ruleListeners.add(listener)\n        }\n    }\n\n    fun removeListener(listener: RuleListener) {\n        synchronized(ruleListeners) {\n            ruleListeners.remove(listener)\n        }\n    }\n\n    suspend fun createProfile(groupId: Long, bean: AbstractBean): ProxyEntity {\n        bean.applyDefaultValues()\n\n        val profile = ProxyEntity(groupId = groupId).apply {\n            id = 0\n            putBean(bean)\n            userOrder = SagerDatabase.proxyDao.nextOrder(groupId) ?: 1\n        }\n        profile.id = SagerDatabase.proxyDao.addProxy(profile)\n        iterator { onAdd(profile) }\n        return profile\n    }\n\n    suspend fun updateProfile(profile: ProxyEntity) {\n        SagerDatabase.proxyDao.updateProxy(profile)\n        iterator { onUpdated(profile, false) }\n    }\n\n    suspend fun updateProfile(profiles: List<ProxyEntity>) {\n        SagerDatabase.proxyDao.updateProxy(profiles)\n        profiles.forEach {\n            iterator { onUpdated(it, false) }\n        }\n    }\n\n    suspend fun deleteProfile2(groupId: Long, profileId: Long) {\n        if (SagerDatabase.proxyDao.deleteById(profileId) == 0) return\n        if (DataStore.selectedProxy == profileId) {\n            DataStore.selectedProxy = 0L\n        }\n    }\n\n    suspend fun deleteProfile(groupId: Long, profileId: Long) {\n        if (SagerDatabase.proxyDao.deleteById(profileId) == 0) return\n        if (DataStore.selectedProxy == profileId) {\n            DataStore.selectedProxy = 0L\n        }\n        iterator { onRemoved(groupId, profileId) }\n        if (SagerDatabase.proxyDao.countByGroup(groupId) > 1) {\n            GroupManager.rearrange(groupId)\n        }\n    }\n\n    fun getProfile(profileId: Long): ProxyEntity? {\n        if (profileId == 0L) return null\n        return try {\n            SagerDatabase.proxyDao.getById(profileId)\n        } catch (ex: SQLiteCantOpenDatabaseException) {\n            throw IOException(ex)\n        } catch (ex: SQLException) {\n            Logs.w(ex)\n            null\n        }\n    }\n\n    fun getProfiles(profileIds: List<Long>): List<ProxyEntity> {\n        if (profileIds.isEmpty()) return listOf()\n        return try {\n            SagerDatabase.proxyDao.getEntities(profileIds)\n        } catch (ex: SQLiteCantOpenDatabaseException) {\n            throw IOException(ex)\n        } catch (ex: SQLException) {\n            Logs.w(ex)\n            listOf()\n        }\n    }\n\n    // postUpdate: post to listeners, don't change the DB\n\n    suspend fun postUpdate(profileId: Long, noTraffic: Boolean = false) {\n        postUpdate(getProfile(profileId) ?: return, noTraffic)\n    }\n\n    suspend fun postUpdate(profile: ProxyEntity, noTraffic: Boolean = false) {\n        iterator { onUpdated(profile, noTraffic) }\n    }\n\n    suspend fun postUpdate(data: TrafficData) {\n        iterator { onUpdated(data) }\n    }\n\n    suspend fun createRule(rule: RuleEntity, post: Boolean = true): RuleEntity {\n        rule.userOrder = SagerDatabase.rulesDao.nextOrder() ?: 1\n        rule.id = SagerDatabase.rulesDao.createRule(rule)\n        if (post) {\n            ruleIterator { onAdd(rule) }\n        }\n        return rule\n    }\n\n    suspend fun updateRule(rule: RuleEntity) {\n        SagerDatabase.rulesDao.updateRule(rule)\n        ruleIterator { onUpdated(rule) }\n    }\n\n    suspend fun deleteRule(ruleId: Long) {\n        SagerDatabase.rulesDao.deleteById(ruleId)\n        ruleIterator { onRemoved(ruleId) }\n    }\n\n    suspend fun deleteRules(rules: List<RuleEntity>) {\n        SagerDatabase.rulesDao.deleteRules(rules)\n        ruleIterator {\n            rules.forEach {\n                onRemoved(it.id)\n            }\n        }\n    }\n\n    suspend fun getRules(): List<RuleEntity> {\n        var rules = SagerDatabase.rulesDao.allRules()\n        if (rules.isEmpty() && !DataStore.rulesFirstCreate) {\n            DataStore.rulesFirstCreate = true\n            createRule(\n                RuleEntity(\n                    name = app.getString(R.string.route_opt_block_quic),\n                    port = \"443\",\n                    network = \"udp\",\n                    outbound = -2\n                )\n            )\n            createRule(\n                RuleEntity(\n                    name = app.getString(R.string.route_opt_block_ads),\n                    domains = \"geosite:category-ads-all\",\n                    outbound = -2\n                )\n            )\n            val fuckedCountry = mutableListOf(\"cn:中国\")\n            if (Locale.getDefault().country != Locale.CHINA.country) {\n                // 非中文用户\n                fuckedCountry += \"ir:Iran\"\n                fuckedCountry += \"ru:Russia\"\n            }\n            for (c in fuckedCountry) {\n                val country = c.substringBefore(\":\")\n                val displayCountry = c.substringAfter(\":\")\n                //\n                if (country == \"cn\") createRule(\n                    RuleEntity(\n                        name = app.getString(R.string.route_play_store, displayCountry),\n                        domains = \"googleapis.cn\",\n                    ), false\n                )\n                createRule(\n                    RuleEntity(\n                        name = app.getString(R.string.route_bypass_domain, displayCountry),\n                        domains = \"geosite:$country\",\n                        outbound = -1\n                    ), false\n                )\n                createRule(\n                    RuleEntity(\n                        name = app.getString(R.string.route_bypass_ip, displayCountry),\n                        ip = \"geoip:$country\",\n                        outbound = -1\n                    ), false\n                )\n            }\n            rules = SagerDatabase.rulesDao.allRules()\n        }\n        return rules\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/ProxyEntity.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.room.*\nimport com.esotericsoftware.kryo.io.ByteBufferInput\nimport com.esotericsoftware.kryo.io.ByteBufferOutput\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.fmt.*\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\nimport io.nekohasekai.sagernet.fmt.http.toUri\nimport io.nekohasekai.sagernet.fmt.hysteria.*\nimport io.nekohasekai.sagernet.fmt.internal.ChainBean\nimport io.nekohasekai.sagernet.fmt.mieru.MieruBean\nimport io.nekohasekai.sagernet.fmt.mieru.buildMieruConfig\nimport io.nekohasekai.sagernet.fmt.naive.NaiveBean\nimport io.nekohasekai.sagernet.fmt.naive.buildNaiveConfig\nimport io.nekohasekai.sagernet.fmt.naive.toUri\nimport io.nekohasekai.sagernet.fmt.shadowsocks.*\nimport moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean\nimport io.nekohasekai.sagernet.fmt.socks.SOCKSBean\nimport io.nekohasekai.sagernet.fmt.socks.toUri\nimport io.nekohasekai.sagernet.fmt.ssh.SSHBean\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\nimport io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean\nimport io.nekohasekai.sagernet.fmt.trojan_go.buildTrojanGoConfig\nimport io.nekohasekai.sagernet.fmt.trojan_go.toUri\nimport io.nekohasekai.sagernet.fmt.tuic.TuicBean\nimport io.nekohasekai.sagernet.fmt.tuic.toUri\nimport io.nekohasekai.sagernet.fmt.v2ray.*\nimport io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ui.profile.*\nimport moe.matsuri.nb4a.SingBoxOptions.MultiplexOptions\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSBean\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity\nimport moe.matsuri.nb4a.proxy.anytls.toUri\nimport moe.matsuri.nb4a.proxy.config.ConfigBean\nimport moe.matsuri.nb4a.proxy.config.ConfigSettingActivity\nimport moe.matsuri.nb4a.proxy.neko.*\nimport moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity\n\n@Entity(\n    tableName = \"proxy_entities\", indices = [Index(\"groupId\", name = \"groupId\")]\n)\ndata class ProxyEntity(\n    @PrimaryKey(autoGenerate = true) var id: Long = 0L,\n    var groupId: Long = 0L,\n    var type: Int = 0,\n    var userOrder: Long = 0L,\n    var tx: Long = 0L,\n    var rx: Long = 0L,\n    var status: Int = 0,\n    var ping: Int = 0,\n    var uuid: String = \"\",\n    var error: String? = null,\n    var socksBean: SOCKSBean? = null,\n    var httpBean: HttpBean? = null,\n    var ssBean: ShadowsocksBean? = null,\n    var vmessBean: VMessBean? = null,\n    var trojanBean: TrojanBean? = null,\n    var trojanGoBean: TrojanGoBean? = null,\n    var mieruBean: MieruBean? = null,\n    var naiveBean: NaiveBean? = null,\n    var hysteriaBean: HysteriaBean? = null,\n    var tuicBean: TuicBean? = null,\n    var sshBean: SSHBean? = null,\n    var wgBean: WireGuardBean? = null,\n    var shadowTLSBean: ShadowTLSBean? = null,\n    var anyTLSBean: AnyTLSBean? = null,\n    var chainBean: ChainBean? = null,\n    var nekoBean: NekoBean? = null,\n    var configBean: ConfigBean? = null,\n) : Serializable() {\n\n    companion object {\n        const val TYPE_SOCKS = 0\n        const val TYPE_HTTP = 1\n        const val TYPE_SS = 2\n        const val TYPE_VMESS = 4\n        const val TYPE_TROJAN = 6\n\n        const val TYPE_SSH = 17\n        const val TYPE_WG = 18\n\n        const val TYPE_TROJAN_GO = 7\n        const val TYPE_NAIVE = 9\n        const val TYPE_HYSTERIA = 15\n        const val TYPE_SHADOWTLS = 19\n        const val TYPE_TUIC = 20\n        const val TYPE_MIERU = 21\n        const val TYPE_ANYTLS = 22\n\n        const val TYPE_CONFIG = 998\n        const val TYPE_NEKO = 999\n\n        const val TYPE_CHAIN = 8\n\n        val chainName by lazy { app.getString(R.string.proxy_chain) }\n\n        @JvmField\n        val CREATOR = object : CREATOR<ProxyEntity>() {\n\n            override fun newInstance(): ProxyEntity {\n                return ProxyEntity()\n            }\n\n            override fun newArray(size: Int): Array<ProxyEntity?> {\n                return arrayOfNulls(size)\n            }\n        }\n    }\n\n    @Ignore\n    @Transient\n    var dirty: Boolean = false\n\n    override fun initializeDefaultValues() {\n    }\n\n    override fun serializeToBuffer(output: ByteBufferOutput) {\n        output.writeInt(0)\n\n        output.writeLong(id)\n        output.writeLong(groupId)\n        output.writeInt(type)\n        output.writeLong(userOrder)\n        output.writeLong(tx)\n        output.writeLong(rx)\n        output.writeInt(status)\n        output.writeInt(ping)\n        output.writeString(uuid)\n        output.writeString(error)\n\n        val data = KryoConverters.serialize(requireBean())\n        output.writeVarInt(data.size, true)\n        output.writeBytes(data)\n\n        output.writeBoolean(dirty)\n    }\n\n    override fun deserializeFromBuffer(input: ByteBufferInput) {\n        val version = input.readInt()\n\n        id = input.readLong()\n        groupId = input.readLong()\n        type = input.readInt()\n        userOrder = input.readLong()\n        tx = input.readLong()\n        rx = input.readLong()\n        status = input.readInt()\n        ping = input.readInt()\n        uuid = input.readString()\n        error = input.readString()\n        putByteArray(input.readBytes(input.readVarInt(true)))\n\n        dirty = input.readBoolean()\n    }\n\n\n    fun putByteArray(byteArray: ByteArray) {\n        when (type) {\n            TYPE_SOCKS -> socksBean = KryoConverters.socksDeserialize(byteArray)\n            TYPE_HTTP -> httpBean = KryoConverters.httpDeserialize(byteArray)\n            TYPE_SS -> ssBean = KryoConverters.shadowsocksDeserialize(byteArray)\n            TYPE_VMESS -> vmessBean = KryoConverters.vmessDeserialize(byteArray)\n            TYPE_TROJAN -> trojanBean = KryoConverters.trojanDeserialize(byteArray)\n            TYPE_TROJAN_GO -> trojanGoBean = KryoConverters.trojanGoDeserialize(byteArray)\n            TYPE_MIERU -> mieruBean = KryoConverters.mieruDeserialize(byteArray)\n            TYPE_NAIVE -> naiveBean = KryoConverters.naiveDeserialize(byteArray)\n            TYPE_HYSTERIA -> hysteriaBean = KryoConverters.hysteriaDeserialize(byteArray)\n            TYPE_SSH -> sshBean = KryoConverters.sshDeserialize(byteArray)\n            TYPE_WG -> wgBean = KryoConverters.wireguardDeserialize(byteArray)\n            TYPE_TUIC -> tuicBean = KryoConverters.tuicDeserialize(byteArray)\n            TYPE_SHADOWTLS -> shadowTLSBean = KryoConverters.shadowTLSDeserialize(byteArray)\n            TYPE_ANYTLS -> anyTLSBean = KryoConverters.anyTLSDeserialize(byteArray)\n            TYPE_CHAIN -> chainBean = KryoConverters.chainDeserialize(byteArray)\n            TYPE_NEKO -> nekoBean = KryoConverters.nekoDeserialize(byteArray)\n            TYPE_CONFIG -> configBean = KryoConverters.configDeserialize(byteArray)\n        }\n    }\n\n    fun displayType(): String = when (type) {\n        TYPE_SOCKS -> socksBean!!.protocolName()\n        TYPE_HTTP -> if (httpBean!!.isTLS()) \"HTTPS\" else \"HTTP\"\n        TYPE_SS -> \"Shadowsocks\"\n        TYPE_VMESS -> if (vmessBean!!.isVLESS) \"VLESS\" else \"VMess\"\n        TYPE_TROJAN -> \"Trojan\"\n        TYPE_TROJAN_GO -> \"Trojan-Go\"\n        TYPE_MIERU -> \"Mieru\"\n        TYPE_NAIVE -> \"Naïve\"\n        TYPE_HYSTERIA -> \"Hysteria\" + hysteriaBean!!.protocolVersion\n        TYPE_SSH -> \"SSH\"\n        TYPE_WG -> \"WireGuard\"\n        TYPE_TUIC -> \"TUIC\"\n        TYPE_SHADOWTLS -> \"ShadowTLS\"\n        TYPE_ANYTLS -> \"AnyTLS\"\n        TYPE_CHAIN -> chainName\n        TYPE_NEKO -> nekoBean!!.displayType()\n        TYPE_CONFIG -> configBean!!.displayType()\n        else -> \"Undefined type $type\"\n    }\n\n    fun displayName() = requireBean().displayName()\n    fun displayAddress() = requireBean().displayAddress()\n\n    fun requireBean(): AbstractBean {\n        return when (type) {\n            TYPE_SOCKS -> socksBean\n            TYPE_HTTP -> httpBean\n            TYPE_SS -> ssBean\n            TYPE_VMESS -> vmessBean\n            TYPE_TROJAN -> trojanBean\n            TYPE_TROJAN_GO -> trojanGoBean\n            TYPE_MIERU -> mieruBean\n            TYPE_NAIVE -> naiveBean\n            TYPE_HYSTERIA -> hysteriaBean\n            TYPE_SSH -> sshBean\n            TYPE_WG -> wgBean\n            TYPE_TUIC -> tuicBean\n            TYPE_SHADOWTLS -> shadowTLSBean\n            TYPE_ANYTLS -> anyTLSBean\n            TYPE_CHAIN -> chainBean\n            TYPE_NEKO -> nekoBean\n            TYPE_CONFIG -> configBean\n            else -> error(\"Undefined type $type\")\n        } ?: error(\"Null ${displayType()} profile\")\n    }\n\n    fun haveLink(): Boolean {\n        return when (type) {\n            TYPE_CHAIN -> false\n            else -> true\n        }\n    }\n\n    fun haveStandardLink(): Boolean {\n        return when (requireBean()) {\n            is SSHBean -> false\n            is WireGuardBean -> false\n            is ShadowTLSBean -> false\n            is NekoBean -> false\n            is ConfigBean -> false\n            else -> true\n        }\n    }\n\n    fun toStdLink(compact: Boolean = false): String = with(requireBean()) {\n        when (this) {\n            is SOCKSBean -> toUri()\n            is HttpBean -> toUri()\n            is ShadowsocksBean -> toUri()\n            is VMessBean -> toUriVMessVLESSTrojan(false)\n            is TrojanBean -> toUriVMessVLESSTrojan(true)\n            is TrojanGoBean -> toUri()\n            is NaiveBean -> toUri()\n            is HysteriaBean -> toUri()\n            is TuicBean -> toUri()\n            is AnyTLSBean -> toUri()\n            is NekoBean -> \"\"\n            else -> toUniversalLink()\n        }\n    }\n\n    fun exportConfig(): Pair<String, String> {\n        var name = \"${requireBean().displayName()}.json\"\n\n        return with(requireBean()) {\n            StringBuilder().apply {\n                val config = buildConfig(this@ProxyEntity, forExport = true)\n                append(config.config)\n\n                if (!config.externalIndex.all { it.chain.isEmpty() }) {\n                    name = \"profiles.txt\"\n                }\n\n                for ((chain) in config.externalIndex) {\n                    chain.entries.forEachIndexed { index, (port, profile) ->\n                        when (val bean = profile.requireBean()) {\n                            is TrojanGoBean -> {\n                                append(\"\\n\\n\")\n                                append(bean.buildTrojanGoConfig(port))\n                            }\n\n                            is MieruBean -> {\n                                append(\"\\n\\n\")\n                                append(bean.buildMieruConfig(port))\n                            }\n\n                            is NaiveBean -> {\n                                append(\"\\n\\n\")\n                                append(bean.buildNaiveConfig(port))\n                            }\n\n                            is HysteriaBean -> {\n                                append(\"\\n\\n\")\n                                append(bean.buildHysteria1Config(port, null))\n                            }\n                        }\n                    }\n                }\n            }.toString()\n        } to name\n    }\n\n    fun needExternal(): Boolean {\n        return when (type) {\n            TYPE_TROJAN_GO -> true\n            TYPE_MIERU -> true\n            TYPE_NAIVE -> true\n            TYPE_HYSTERIA -> !hysteriaBean!!.canUseSingBox()\n            TYPE_NEKO -> true\n            else -> false\n        }\n    }\n\n    fun singMux(): MultiplexOptions? {\n        return when (type) {\n            TYPE_VMESS -> MultiplexOptions().apply {\n                enabled = vmessBean!!.enableMux\n                padding = vmessBean!!.muxPadding\n                max_streams = vmessBean!!.muxConcurrency\n                protocol = when (vmessBean!!.muxType) {\n                    1 -> \"smux\"\n                    2 -> \"yamux\"\n                    else -> \"h2mux\"\n                }\n            }\n\n            TYPE_TROJAN -> MultiplexOptions().apply {\n                enabled = trojanBean!!.enableMux\n                padding = trojanBean!!.muxPadding\n                max_streams = trojanBean!!.muxConcurrency\n                protocol = when (trojanBean!!.muxType) {\n                    1 -> \"smux\"\n                    2 -> \"yamux\"\n                    else -> \"h2mux\"\n                }\n            }\n\n            else -> null\n        }\n    }\n\n    fun putBean(bean: AbstractBean): ProxyEntity {\n        socksBean = null\n        httpBean = null\n        ssBean = null\n        vmessBean = null\n        trojanBean = null\n        trojanGoBean = null\n        mieruBean = null\n        naiveBean = null\n        hysteriaBean = null\n        sshBean = null\n        wgBean = null\n        tuicBean = null\n        shadowTLSBean = null\n        anyTLSBean = null\n        chainBean = null\n        configBean = null\n        nekoBean = null\n\n        when (bean) {\n            is SOCKSBean -> {\n                type = TYPE_SOCKS\n                socksBean = bean\n            }\n\n            is HttpBean -> {\n                type = TYPE_HTTP\n                httpBean = bean\n            }\n\n            is ShadowsocksBean -> {\n                type = TYPE_SS\n                ssBean = bean\n            }\n\n            is VMessBean -> {\n                type = TYPE_VMESS\n                vmessBean = bean\n            }\n\n            is TrojanBean -> {\n                type = TYPE_TROJAN\n                trojanBean = bean\n            }\n\n            is TrojanGoBean -> {\n                type = TYPE_TROJAN_GO\n                trojanGoBean = bean\n            }\n\n            is MieruBean -> {\n                type = TYPE_MIERU\n                mieruBean = bean\n            }\n\n            is NaiveBean -> {\n                type = TYPE_NAIVE\n                naiveBean = bean\n            }\n\n            is HysteriaBean -> {\n                type = TYPE_HYSTERIA\n                hysteriaBean = bean\n            }\n\n            is SSHBean -> {\n                type = TYPE_SSH\n                sshBean = bean\n            }\n\n            is WireGuardBean -> {\n                type = TYPE_WG\n                wgBean = bean\n            }\n\n            is TuicBean -> {\n                type = TYPE_TUIC\n                tuicBean = bean\n            }\n\n            is ShadowTLSBean -> {\n                type = TYPE_SHADOWTLS\n                shadowTLSBean = bean\n            }\n\n            is AnyTLSBean -> {\n                type = TYPE_ANYTLS\n                anyTLSBean = bean\n            }\n\n            is ChainBean -> {\n                type = TYPE_CHAIN\n                chainBean = bean\n            }\n\n            is NekoBean -> {\n                type = TYPE_NEKO\n                nekoBean = bean\n            }\n\n            is ConfigBean -> {\n                type = TYPE_CONFIG\n                configBean = bean\n            }\n\n            else -> error(\"Undefined type $type\")\n        }\n        return this\n    }\n\n    fun settingIntent(ctx: Context, isSubscription: Boolean): Intent {\n        return Intent(\n            ctx, when (type) {\n                TYPE_SOCKS -> SocksSettingsActivity::class.java\n                TYPE_HTTP -> HttpSettingsActivity::class.java\n                TYPE_SS -> ShadowsocksSettingsActivity::class.java\n                TYPE_VMESS -> VMessSettingsActivity::class.java\n                TYPE_TROJAN -> TrojanSettingsActivity::class.java\n                TYPE_TROJAN_GO -> TrojanGoSettingsActivity::class.java\n                TYPE_MIERU -> MieruSettingsActivity::class.java\n                TYPE_NAIVE -> NaiveSettingsActivity::class.java\n                TYPE_HYSTERIA -> HysteriaSettingsActivity::class.java\n                TYPE_SSH -> SSHSettingsActivity::class.java\n                TYPE_WG -> WireGuardSettingsActivity::class.java\n                TYPE_TUIC -> TuicSettingsActivity::class.java\n                TYPE_SHADOWTLS -> ShadowTLSSettingsActivity::class.java\n                TYPE_ANYTLS -> AnyTLSSettingsActivity::class.java\n                TYPE_CHAIN -> ChainSettingsActivity::class.java\n                TYPE_CONFIG -> ConfigSettingActivity::class.java\n                else -> throw IllegalArgumentException()\n            }\n        ).apply {\n            putExtra(ProfileSettingsActivity.EXTRA_PROFILE_ID, id)\n            putExtra(ProfileSettingsActivity.EXTRA_IS_SUBSCRIPTION, isSubscription)\n        }\n    }\n\n    @androidx.room.Dao\n    interface Dao {\n\n        @Query(\"select * from proxy_entities\")\n        fun getAll(): List<ProxyEntity>\n\n        @Query(\"SELECT id FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder\")\n        fun getIdsByGroup(groupId: Long): List<Long>\n\n        @Query(\"SELECT * FROM proxy_entities WHERE groupId = :groupId ORDER BY userOrder\")\n        fun getByGroup(groupId: Long): List<ProxyEntity>\n\n        @Query(\"SELECT * FROM proxy_entities WHERE id in (:proxyIds)\")\n        fun getEntities(proxyIds: List<Long>): List<ProxyEntity>\n\n        @Query(\"SELECT COUNT(*) FROM proxy_entities WHERE groupId = :groupId\")\n        fun countByGroup(groupId: Long): Long\n\n        @Query(\"SELECT  MAX(userOrder) + 1 FROM proxy_entities WHERE groupId = :groupId\")\n        fun nextOrder(groupId: Long): Long?\n\n        @Query(\"SELECT * FROM proxy_entities WHERE id = :proxyId\")\n        fun getById(proxyId: Long): ProxyEntity?\n\n        @Query(\"DELETE FROM proxy_entities WHERE id IN (:proxyId)\")\n        fun deleteById(proxyId: Long): Int\n\n        @Query(\"DELETE FROM proxy_entities WHERE groupId = :groupId\")\n        fun deleteByGroup(groupId: Long)\n\n        @Query(\"DELETE FROM proxy_entities WHERE groupId in (:groupId)\")\n        fun deleteByGroup(groupId: LongArray)\n\n        @Delete\n        fun deleteProxy(proxy: ProxyEntity): Int\n\n        @Delete\n        fun deleteProxy(proxies: List<ProxyEntity>): Int\n\n        @Update\n        fun updateProxy(proxy: ProxyEntity): Int\n\n        @Update\n        fun updateProxy(proxies: List<ProxyEntity>): Int\n\n        @Insert\n        fun addProxy(proxy: ProxyEntity): Long\n\n        @Insert\n        fun insert(proxies: List<ProxyEntity>)\n\n        @Query(\"DELETE FROM proxy_entities WHERE groupId = :groupId\")\n        fun deleteAll(groupId: Long): Int\n\n        @Query(\"DELETE FROM proxy_entities\")\n        fun reset()\n\n    }\n\n    override fun describeContents(): Int {\n        return 0\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/ProxyGroup.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport androidx.room.*\nimport com.esotericsoftware.kryo.io.ByteBufferInput\nimport com.esotericsoftware.kryo.io.ByteBufferOutput\nimport io.nekohasekai.sagernet.GroupOrder\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.fmt.Serializable\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\n\n@Entity(tableName = \"proxy_groups\")\ndata class ProxyGroup(\n    @PrimaryKey(autoGenerate = true) var id: Long = 0L,\n    var userOrder: Long = 0L,\n    var ungrouped: Boolean = false,\n    var name: String? = null,\n    var type: Int = GroupType.BASIC,\n    var subscription: SubscriptionBean? = null,\n    var order: Int = GroupOrder.ORIGIN,\n    var isSelector: Boolean = false,\n    var frontProxy: Long = -1L,\n    var landingProxy: Long = -1L\n) : Serializable() {\n\n    @Transient\n    var export = false\n\n    override fun initializeDefaultValues() {\n        subscription?.applyDefaultValues()\n    }\n\n    override fun serializeToBuffer(output: ByteBufferOutput) {\n        if (export) {\n\n            output.writeInt(0)\n            output.writeString(name)\n            output.writeInt(type)\n            val subscription = subscription!!\n            subscription.serializeForShare(output)\n\n        } else {\n            output.writeInt(0)\n            output.writeLong(id)\n            output.writeLong(userOrder)\n            output.writeBoolean(ungrouped)\n            output.writeString(name)\n            output.writeInt(type)\n\n            if (type == GroupType.SUBSCRIPTION) {\n                subscription?.serializeToBuffer(output)\n            }\n            output.writeInt(order)\n        }\n    }\n\n    override fun deserializeFromBuffer(input: ByteBufferInput) {\n        if (export) {\n            val version = input.readInt()\n\n            name = input.readString()\n            type = input.readInt()\n            val subscription = SubscriptionBean()\n            this.subscription = subscription\n\n            subscription.deserializeFromShare(input)\n        } else {\n            val version = input.readInt()\n\n            id = input.readLong()\n            userOrder = input.readLong()\n            ungrouped = input.readBoolean()\n            name = input.readString()\n            type = input.readInt()\n\n            if (type == GroupType.SUBSCRIPTION) {\n                val subscription = SubscriptionBean()\n                this.subscription = subscription\n\n                subscription.deserializeFromBuffer(input)\n            }\n            order = input.readInt()\n        }\n    }\n\n    fun displayName(): String {\n        return name.takeIf { !it.isNullOrBlank() } ?: app.getString(R.string.group_default)\n    }\n\n    @androidx.room.Dao\n    interface Dao {\n\n        @Query(\"SELECT * FROM proxy_groups ORDER BY userOrder\")\n        fun allGroups(): List<ProxyGroup>\n\n        @Query(\"SELECT * FROM proxy_groups WHERE type = ${GroupType.SUBSCRIPTION}\")\n        suspend fun subscriptions(): List<ProxyGroup>\n\n        @Query(\"SELECT MAX(userOrder) + 1 FROM proxy_groups\")\n        fun nextOrder(): Long?\n\n        @Query(\"SELECT * FROM proxy_groups WHERE id = :groupId\")\n        fun getById(groupId: Long): ProxyGroup?\n\n        @Query(\"DELETE FROM proxy_groups WHERE id = :groupId\")\n        fun deleteById(groupId: Long): Int\n\n        @Delete\n        fun deleteGroup(group: ProxyGroup)\n\n        @Delete\n        fun deleteGroup(groupList: List<ProxyGroup>)\n\n        @Insert\n        fun createGroup(group: ProxyGroup): Long\n\n        @Update\n        fun updateGroup(group: ProxyGroup)\n\n        @Query(\"DELETE FROM proxy_groups\")\n        fun reset()\n\n        @Insert\n        fun insert(groupList: List<ProxyGroup>)\n\n    }\n\n    companion object {\n        @JvmField\n        val CREATOR = object : Serializable.CREATOR<ProxyGroup>() {\n\n            override fun newInstance(): ProxyGroup {\n                return ProxyGroup()\n            }\n\n            override fun newArray(size: Int): Array<ProxyGroup?> {\n                return arrayOfNulls(size)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/RuleEntity.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport android.os.Parcelable\nimport androidx.room.*\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.app\nimport kotlinx.parcelize.Parcelize\n\n@Entity(tableName = \"rules\")\n@Parcelize\n@TypeConverters(StringCollectionConverter::class)\ndata class RuleEntity(\n    @PrimaryKey(autoGenerate = true) var id: Long = 0L,\n    var name: String = \"\",\n    @ColumnInfo(defaultValue = \"\")\n    var config: String = \"\",\n    var userOrder: Long = 0L,\n    var enabled: Boolean = false,\n    var domains: String = \"\",\n    var ip: String = \"\",\n    var port: String = \"\",\n    var sourcePort: String = \"\",\n    var network: String = \"\",\n    var source: String = \"\",\n    var protocol: String = \"\",\n    var outbound: Long = 0,\n    var packages: Set<String> = emptySet(),\n) : Parcelable {\n\n    fun displayName(): String {\n        return name.takeIf { it.isNotBlank() } ?: \"Rule $id\"\n    }\n\n    fun mkSummary(): String {\n        var summary = \"\"\n        if (config.isNotBlank()) summary += \"[config]\\n\"\n        if (domains.isNotBlank()) summary += \"$domains\\n\"\n        if (ip.isNotBlank()) summary += \"$ip\\n\"\n        if (source.isNotBlank()) summary += \"src ip: $source\\n\"\n        if (sourcePort.isNotBlank()) summary += \"src port: $sourcePort\\n\"\n        if (port.isNotBlank()) summary += \"dst port: $port\\n\"\n        if (network.isNotBlank()) summary += \"network: $network\\n\"\n        if (protocol.isNotBlank()) summary += \"protocol: $protocol\\n\"\n        if (packages.isNotEmpty()) summary += app.getString(\n            R.string.apps_message, packages.size\n        ) + \"\\n\"\n        val lines = summary.trim().split(\"\\n\")\n        return if (lines.size > 3) {\n            lines.subList(0, 3).joinToString(\"\\n\", postfix = \"\\n...\")\n        } else {\n            summary.trim()\n        }\n    }\n\n    fun displayOutbound(): String {\n        return when (outbound) {\n            0L -> app.getString(R.string.route_proxy)\n            -1L -> app.getString(R.string.route_bypass)\n            -2L -> app.getString(R.string.route_block)\n            else -> ProfileManager.getProfile(outbound)?.displayName()\n                ?: app.getString(R.string.error_title)\n        }\n    }\n\n    @androidx.room.Dao\n    interface Dao {\n\n        @Query(\"SELECT * from rules WHERE (packages != '') AND enabled = 1\")\n        fun checkVpnNeeded(): List<RuleEntity>\n\n        @Query(\"SELECT * FROM rules ORDER BY userOrder\")\n        fun allRules(): List<RuleEntity>\n\n        @Query(\"SELECT * FROM rules WHERE enabled = :enabled ORDER BY userOrder\")\n        fun enabledRules(enabled: Boolean = true): List<RuleEntity>\n\n        @Query(\"SELECT MAX(userOrder) + 1 FROM rules\")\n        fun nextOrder(): Long?\n\n        @Query(\"SELECT * FROM rules WHERE id = :ruleId\")\n        fun getById(ruleId: Long): RuleEntity?\n\n        @Query(\"DELETE FROM rules WHERE id = :ruleId\")\n        fun deleteById(ruleId: Long): Int\n\n        @Delete\n        fun deleteRule(rule: RuleEntity)\n\n        @Delete\n        fun deleteRules(rules: List<RuleEntity>)\n\n        @Insert\n        fun createRule(rule: RuleEntity): Long\n\n        @Update\n        fun updateRule(rule: RuleEntity)\n\n        @Update\n        fun updateRules(rules: List<RuleEntity>)\n\n        @Query(\"DELETE FROM rules\")\n        fun reset()\n\n        @Insert\n        fun insert(rules: List<RuleEntity>)\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/SagerDatabase.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport androidx.room.AutoMigration\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.room.TypeConverters\nimport dev.matrix.roomigrant.GenerateRoomMigrations\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.fmt.KryoConverters\nimport io.nekohasekai.sagernet.fmt.gson.GsonConverters\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\n@Database(\n    entities = [ProxyGroup::class, ProxyEntity::class, RuleEntity::class],\n    version = 6,\n    autoMigrations = [\n        AutoMigration(from = 3, to = 4),\n        AutoMigration(from = 4, to = 5),\n        AutoMigration(from = 5, to = 6)\n    ]\n)\n@TypeConverters(value = [KryoConverters::class, GsonConverters::class])\n@GenerateRoomMigrations\nabstract class SagerDatabase : RoomDatabase() {\n\n    companion object {\n        @OptIn(DelicateCoroutinesApi::class)\n        @Suppress(\"EXPERIMENTAL_API_USAGE\")\n        val instance by lazy {\n            SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()\n            Room.databaseBuilder(SagerNet.application, SagerDatabase::class.java, Key.DB_PROFILE)\n//                .addMigrations(*SagerDatabase_Migrations.build())\n                .setJournalMode(JournalMode.TRUNCATE)\n                .allowMainThreadQueries()\n                .enableMultiInstanceInvalidation()\n                .fallbackToDestructiveMigration()\n                .setQueryExecutor { GlobalScope.launch { it.run() } }\n                .build()\n        }\n\n        val groupDao get() = instance.groupDao()\n        val proxyDao get() = instance.proxyDao()\n        val rulesDao get() = instance.rulesDao()\n\n    }\n\n    abstract fun groupDao(): ProxyGroup.Dao\n    abstract fun proxyDao(): ProxyEntity.Dao\n    abstract fun rulesDao(): RuleEntity.Dao\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/StringCollectionConverter.kt",
    "content": "package io.nekohasekai.sagernet.database\n\nimport androidx.room.TypeConverter\n\nclass StringCollectionConverter {\n    companion object {\n        const val SPLIT_FLAG = \",\"\n\n        /*\n        @TypeConverter\n        @JvmStatic\n        fun fromList(list: List<String>): String = if (list.isEmpty()) {\n            \"\"\n        } else {\n            list.joinToString(SPLIT_FLAG)\n        }\n\n        @TypeConverter\n        @JvmStatic\n        fun toList(str: String): List<String> = if (str.isBlank()) {\n            emptyList()\n        } else {\n            str.split(SPLIT_FLAG)\n        }\n        */\n\n\n        @TypeConverter\n        @JvmStatic\n        fun fromSet(set: Set<String>): String = if (set.isEmpty()) {\n            \"\"\n        } else {\n            set.joinToString(SPLIT_FLAG)\n        }\n\n        @TypeConverter\n        @JvmStatic\n        fun toSet(str: String): Set<String> = if (str.isBlank()) {\n            emptySet()\n        } else {\n            str.split(\",\").toSet()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/SubscriptionBean.java",
    "content": "package io.nekohasekai.sagernet.database;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.nekohasekai.sagernet.fmt.Serializable;\n\npublic class SubscriptionBean extends Serializable {\n\n    public Integer type;\n    public String link;\n    public String token;\n    public Boolean forceResolve;\n    public Boolean deduplication;\n    public Boolean updateWhenConnectedOnly;\n    public String customUserAgent;\n    public Boolean autoUpdate;\n    public Integer autoUpdateDelay;\n    public Integer lastUpdated;\n\n    // SIP008\n\n    public Long bytesUsed;\n    public Long bytesRemaining;\n\n    // Open Online Config\n\n    public String username;\n    public Integer expiryDate;\n    public List<String> protocols;\n\n\n    // https://github.com/crossutility/Quantumult/blob/master/extra-subscription-feature.md\n\n    public String subscriptionUserinfo;\n\n    public SubscriptionBean() {\n    }\n\n    @Override\n    public void serializeToBuffer(ByteBufferOutput output) {\n        output.writeInt(1);\n\n        output.writeInt(type);\n\n        output.writeString(link);\n\n        output.writeBoolean(forceResolve);\n        output.writeBoolean(deduplication);\n        output.writeBoolean(updateWhenConnectedOnly);\n        output.writeString(customUserAgent);\n        output.writeBoolean(autoUpdate);\n        output.writeInt(autoUpdateDelay);\n        output.writeInt(lastUpdated);\n\n        output.writeString(subscriptionUserinfo);\n    }\n\n    public void serializeForShare(ByteBufferOutput output) {\n        output.writeInt(0);\n\n        output.writeInt(type);\n\n        output.writeString(link);\n\n        output.writeBoolean(forceResolve);\n        output.writeBoolean(deduplication);\n        output.writeBoolean(updateWhenConnectedOnly);\n        output.writeString(customUserAgent);\n    }\n\n    @Override\n    public void deserializeFromBuffer(ByteBufferInput input) {\n        int version = input.readInt();\n\n        type = input.readInt();\n        link = input.readString();\n        forceResolve = input.readBoolean();\n        deduplication = input.readBoolean();\n        updateWhenConnectedOnly = input.readBoolean();\n        customUserAgent = input.readString();\n        autoUpdate = input.readBoolean();\n        autoUpdateDelay = input.readInt();\n        lastUpdated = input.readInt();\n        subscriptionUserinfo = input.readString();\n    }\n\n    public void deserializeFromShare(ByteBufferInput input) {\n        int version = input.readInt();\n\n        type = input.readInt();\n        link = input.readString();\n        forceResolve = input.readBoolean();\n        deduplication = input.readBoolean();\n        updateWhenConnectedOnly = input.readBoolean();\n        customUserAgent = input.readString();\n    }\n\n    @Override\n    public void initializeDefaultValues() {\n        if (type == null) type = 0;\n        if (link == null) link = \"\";\n        if (token == null) token = \"\";\n        if (forceResolve == null) forceResolve = false;\n        if (deduplication == null) deduplication = false;\n        if (updateWhenConnectedOnly == null) updateWhenConnectedOnly = false;\n        if (customUserAgent == null) customUserAgent = \"\";\n        if (autoUpdate == null) autoUpdate = false;\n        if (autoUpdateDelay == null) autoUpdateDelay = 1440;\n        if (lastUpdated == null) lastUpdated = 0;\n\n        if (bytesUsed == null) bytesUsed = 0L;\n        if (bytesRemaining == null) bytesRemaining = 0L;\n\n        if (username == null) username = \"\";\n        if (expiryDate == null) expiryDate = 0;\n        if (protocols == null) protocols = new ArrayList<>();\n    }\n\n    public static final Creator<SubscriptionBean> CREATOR = new CREATOR<SubscriptionBean>() {\n        @NonNull\n        @Override\n        public SubscriptionBean newInstance() {\n            return new SubscriptionBean();\n        }\n\n        @Override\n        public SubscriptionBean[] newArray(int size) {\n            return new SubscriptionBean[size];\n        }\n    };\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/preference/EditTextPreferenceModifiers.kt",
    "content": "package io.nekohasekai.sagernet.database.preference\n\nimport android.graphics.Typeface\nimport android.text.InputFilter\nimport android.view.inputmethod.EditorInfo\nimport android.widget.EditText\nimport androidx.preference.EditTextPreference\n\nobject EditTextPreferenceModifiers {\n    object Monospace : EditTextPreference.OnBindEditTextListener {\n        override fun onBindEditText(editText: EditText) {\n            editText.typeface = Typeface.MONOSPACE\n        }\n    }\n\n    object Hosts : EditTextPreference.OnBindEditTextListener {\n\n        override fun onBindEditText(editText: EditText) {\n            editText.setHorizontallyScrolling(true)\n            editText.setSelection(editText.text.length)\n        }\n    }\n\n    object Port : EditTextPreference.OnBindEditTextListener {\n        private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5))\n\n        override fun onBindEditText(editText: EditText) {\n            editText.inputType = EditorInfo.TYPE_CLASS_NUMBER\n            editText.filters = portLengthFilter\n            editText.setSingleLine()\n            editText.setSelection(editText.text.length)\n        }\n    }\n\n    object Number : EditTextPreference.OnBindEditTextListener {\n\n        override fun onBindEditText(editText: EditText) {\n            editText.inputType = EditorInfo.TYPE_CLASS_NUMBER\n            editText.setSingleLine()\n            editText.setSelection(editText.text.length)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/preference/KeyValuePair.kt",
    "content": "package io.nekohasekai.sagernet.database.preference\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport androidx.room.*\nimport java.io.ByteArrayOutputStream\nimport java.nio.ByteBuffer\n\n@Entity\nclass KeyValuePair() : Parcelable {\n    companion object {\n        const val TYPE_UNINITIALIZED = 0\n        const val TYPE_BOOLEAN = 1\n        const val TYPE_FLOAT = 2\n\n        @Deprecated(\"Use TYPE_LONG.\")\n        const val TYPE_INT = 3\n        const val TYPE_LONG = 4\n        const val TYPE_STRING = 5\n        const val TYPE_STRING_SET = 6\n\n        @JvmField\n        val CREATOR = object : Parcelable.Creator<KeyValuePair> {\n            override fun createFromParcel(parcel: Parcel): KeyValuePair {\n                return KeyValuePair(parcel)\n            }\n\n            override fun newArray(size: Int): Array<KeyValuePair?> {\n                return arrayOfNulls(size)\n            }\n        }\n    }\n\n    @androidx.room.Dao\n    interface Dao {\n\n        @Query(\"SELECT * FROM `KeyValuePair`\")\n        fun all(): List<KeyValuePair>\n\n        @Query(\"SELECT * FROM `KeyValuePair` WHERE `key` = :key\")\n        operator fun get(key: String): KeyValuePair?\n\n        @Insert(onConflict = OnConflictStrategy.REPLACE)\n        fun put(value: KeyValuePair): Long\n\n        @Query(\"DELETE FROM `KeyValuePair` WHERE `key` = :key\")\n        fun delete(key: String): Int\n\n        @Query(\"DELETE FROM `KeyValuePair`\")\n        fun reset(): Int\n\n        @Insert\n        fun insert(list: List<KeyValuePair>)\n    }\n\n    @PrimaryKey\n    var key: String = \"\"\n    var valueType: Int = TYPE_UNINITIALIZED\n    var value: ByteArray = ByteArray(0)\n\n    val boolean: Boolean?\n        get() = if (valueType == TYPE_BOOLEAN) ByteBuffer.wrap(value).get() != 0.toByte() else null\n    val float: Float?\n        get() = if (valueType == TYPE_FLOAT) ByteBuffer.wrap(value).float else null\n\n    @Suppress(\"DEPRECATION\")\n    @Deprecated(\"Use long.\", ReplaceWith(\"long\"))\n    val int: Int?\n        get() = if (valueType == TYPE_INT) ByteBuffer.wrap(value).int else null\n    val long: Long?\n        get() = when (valueType) {\n            @Suppress(\"DEPRECATION\") TYPE_INT,\n            -> ByteBuffer.wrap(value).int.toLong()\n            TYPE_LONG -> ByteBuffer.wrap(value).long\n            else -> null\n        }\n    val string: String?\n        get() = if (valueType == TYPE_STRING) String(value) else null\n    val stringSet: Set<String>?\n        get() = if (valueType == TYPE_STRING_SET) {\n            val buffer = ByteBuffer.wrap(value)\n            val result = HashSet<String>()\n            while (buffer.hasRemaining()) {\n                val chArr = ByteArray(buffer.int)\n                buffer.get(chArr)\n                result.add(String(chArr))\n            }\n            result\n        } else null\n\n    @Ignore\n    constructor(key: String) : this() {\n        this.key = key\n    }\n\n    // putting null requires using DataStore\n    fun put(value: Boolean): KeyValuePair {\n        valueType = TYPE_BOOLEAN\n        this.value = ByteBuffer.allocate(1).put((if (value) 1 else 0).toByte()).array()\n        return this\n    }\n\n    fun put(value: Float): KeyValuePair {\n        valueType = TYPE_FLOAT\n        this.value = ByteBuffer.allocate(4).putFloat(value).array()\n        return this\n    }\n\n    @Suppress(\"DEPRECATION\")\n    @Deprecated(\"Use long.\")\n    fun put(value: Int): KeyValuePair {\n        valueType = TYPE_INT\n        this.value = ByteBuffer.allocate(4).putInt(value).array()\n        return this\n    }\n\n    fun put(value: Long): KeyValuePair {\n        valueType = TYPE_LONG\n        this.value = ByteBuffer.allocate(8).putLong(value).array()\n        return this\n    }\n\n    fun put(value: String): KeyValuePair {\n        valueType = TYPE_STRING\n        this.value = value.toByteArray()\n        return this\n    }\n\n    fun put(value: Set<String>): KeyValuePair {\n        valueType = TYPE_STRING_SET\n        val stream = ByteArrayOutputStream()\n        val intBuffer = ByteBuffer.allocate(4)\n        for (v in value) {\n            intBuffer.rewind()\n            stream.write(intBuffer.putInt(v.length).array())\n            stream.write(v.toByteArray())\n        }\n        this.value = stream.toByteArray()\n        return this\n    }\n\n    @Suppress(\"IMPLICIT_CAST_TO_ANY\")\n    override fun toString(): String {\n        return when (valueType) {\n            TYPE_BOOLEAN -> boolean\n            TYPE_FLOAT -> float\n            TYPE_LONG -> long\n            TYPE_STRING -> string\n            TYPE_STRING_SET -> stringSet\n            else -> null\n        }?.toString() ?: \"null\"\n    }\n\n    constructor(parcel: Parcel) : this() {\n        key = parcel.readString()!!\n        valueType = parcel.readInt()\n        value = parcel.createByteArray()!!\n    }\n\n    override fun writeToParcel(parcel: Parcel, flags: Int) {\n        parcel.writeString(key)\n        parcel.writeInt(valueType)\n        parcel.writeByteArray(value)\n    }\n\n    override fun describeContents(): Int {\n        return 0\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/preference/OnPreferenceDataStoreChangeListener.kt",
    "content": "package io.nekohasekai.sagernet.database.preference\n\nimport androidx.preference.PreferenceDataStore\n\ninterface OnPreferenceDataStoreChangeListener {\n    fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/preference/PublicDatabase.kt",
    "content": "package io.nekohasekai.sagernet.database.preference\n\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport dev.matrix.roomigrant.GenerateRoomMigrations\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.SagerNet\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\n@Database(entities = [KeyValuePair::class], version = 1)\n@GenerateRoomMigrations\nabstract class PublicDatabase : RoomDatabase() {\n    companion object {\n        val instance by lazy {\n            SagerNet.application.getDatabasePath(Key.DB_PROFILE).parentFile?.mkdirs()\n            Room.databaseBuilder(SagerNet.application, PublicDatabase::class.java, Key.DB_PUBLIC)\n                .setJournalMode(JournalMode.TRUNCATE)\n                .allowMainThreadQueries()\n                .enableMultiInstanceInvalidation()\n                .fallbackToDestructiveMigration()\n                .setQueryExecutor { GlobalScope.launch { it.run() } }\n                .build()\n        }\n\n        val kvPairDao get() = instance.keyValuePairDao()\n    }\n\n    abstract fun keyValuePairDao(): KeyValuePair.Dao\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/database/preference/RoomPreferenceDataStore.kt",
    "content": "package io.nekohasekai.sagernet.database.preference\n\nimport androidx.preference.PreferenceDataStore\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nopen class RoomPreferenceDataStore(private val kvPairDao: KeyValuePair.Dao) :\n    PreferenceDataStore() {\n\n    fun getBoolean(key: String) = kvPairDao[key]?.boolean\n    fun getFloat(key: String) = kvPairDao[key]?.float\n    fun getInt(key: String) = kvPairDao[key]?.long?.toInt()\n    fun getLong(key: String) = kvPairDao[key]?.long\n    fun getString(key: String) = kvPairDao[key]?.string\n    fun getStringSet(key: String) = kvPairDao[key]?.stringSet\n    fun reset() = kvPairDao.reset()\n\n    override fun getBoolean(key: String, defValue: Boolean) = getBoolean(key) ?: defValue\n    override fun getFloat(key: String, defValue: Float) = getFloat(key) ?: defValue\n    override fun getInt(key: String, defValue: Int) = getInt(key) ?: defValue\n    override fun getLong(key: String, defValue: Long) = getLong(key) ?: defValue\n    override fun getString(key: String, defValue: String?) = getString(key) ?: defValue\n    override fun getStringSet(key: String, defValue: MutableSet<String>?) =\n        getStringSet(key) ?: defValue\n\n    fun putBoolean(key: String, value: Boolean?) =\n        if (value == null) remove(key) else putBoolean(key, value)\n\n    fun putFloat(key: String, value: Float?) =\n        if (value == null) remove(key) else putFloat(key, value)\n\n    fun putInt(key: String, value: Int?) =\n        if (value == null) remove(key) else putLong(key, value.toLong())\n\n    fun putLong(key: String, value: Long?) = if (value == null) remove(key) else putLong(key, value)\n    override fun putBoolean(key: String, value: Boolean) {\n        kvPairDao.put(KeyValuePair(key).put(value))\n        fireChangeListener(key)\n    }\n\n    override fun putFloat(key: String, value: Float) {\n        kvPairDao.put(KeyValuePair(key).put(value))\n        fireChangeListener(key)\n    }\n\n    override fun putInt(key: String, value: Int) {\n        kvPairDao.put(KeyValuePair(key).put(value.toLong()))\n        fireChangeListener(key)\n    }\n\n    override fun putLong(key: String, value: Long) {\n        kvPairDao.put(KeyValuePair(key).put(value))\n        fireChangeListener(key)\n    }\n\n    override fun putString(key: String, value: String?) = if (value == null) remove(key) else {\n        kvPairDao.put(KeyValuePair(key).put(value))\n        fireChangeListener(key)\n    }\n\n    override fun putStringSet(key: String, values: MutableSet<String>?) =\n        if (values == null) remove(key) else {\n            kvPairDao.put(KeyValuePair(key).put(values))\n            fireChangeListener(key)\n        }\n\n    fun remove(key: String) {\n        kvPairDao.delete(key)\n        fireChangeListener(key)\n    }\n\n    private val listeners = HashSet<OnPreferenceDataStoreChangeListener>()\n    private fun fireChangeListener(key: String) {\n        val listeners = synchronized(listeners) {\n            listeners.toList()\n        }\n        listeners.forEach { it.onPreferenceDataStoreChanged(this, key) }\n    }\n\n    fun registerChangeListener(listener: OnPreferenceDataStoreChangeListener) {\n        synchronized(listeners) {\n            listeners.add(listener)\n        }\n    }\n\n    fun unregisterChangeListener(listener: OnPreferenceDataStoreChangeListener) {\n        synchronized(listeners) {\n            listeners.remove(listener)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/AbstractBean.java",
    "content": "package io.nekohasekai.sagernet.fmt;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.Arrays;\n\nimport io.nekohasekai.sagernet.ktx.NetsKt;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic abstract class AbstractBean extends Serializable {\n\n    public String serverAddress;\n    public Integer serverPort;\n\n    public String name;\n\n    //\n\n    public String customOutboundJson;\n    public String customConfigJson;\n\n    //\n    public transient String finalAddress;\n    public transient int finalPort;\n\n    public String displayName() {\n        if (JavaUtil.isNotBlank(name)) {\n            return name;\n        } else {\n            return displayAddress();\n        }\n    }\n\n    public String displayAddress() {\n        return NetsKt.wrapIPV6Host(serverAddress) + \":\" + serverPort;\n    }\n\n    public String network() {\n        return \"tcp,udp\";\n    }\n\n    public boolean canICMPing() {\n        return true;\n    }\n\n    public boolean canTCPing() {\n        return true;\n    }\n\n    public boolean canMapping() {\n        return true;\n    }\n\n    @Override\n    public void initializeDefaultValues() {\n        if (JavaUtil.isNullOrBlank(serverAddress)) {\n            serverAddress = \"127.0.0.1\";\n        } else if (serverAddress.startsWith(\"[\") && serverAddress.endsWith(\"]\")) {\n            serverAddress = NetsKt.unwrapIPV6Host(serverAddress);\n        }\n        if (serverPort == null) {\n            serverPort = 1080;\n        }\n        if (name == null) name = \"\";\n\n        finalAddress = serverAddress;\n        finalPort = serverPort;\n\n        if (customOutboundJson == null) customOutboundJson = \"\";\n        if (customConfigJson == null) customConfigJson = \"\";\n    }\n\n\n    private transient boolean serializeWithoutName;\n\n    @Override\n    public void serializeToBuffer(@NonNull ByteBufferOutput output) {\n        serialize(output);\n\n        output.writeInt(1);\n        if (!serializeWithoutName) {\n            output.writeString(name);\n        }\n        output.writeString(customOutboundJson);\n        output.writeString(customConfigJson);\n    }\n\n    @Override\n    public void deserializeFromBuffer(@NonNull ByteBufferInput input) {\n        deserialize(input);\n\n        int extraVersion = input.readInt();\n\n        name = input.readString();\n        customOutboundJson = input.readString();\n        customConfigJson = input.readString();\n    }\n\n    public void serialize(ByteBufferOutput output) {\n        output.writeString(serverAddress);\n        output.writeInt(serverPort);\n    }\n\n    public void deserialize(ByteBufferInput input) {\n        serverAddress = input.readString();\n        serverPort = input.readInt();\n    }\n\n    @NotNull\n    @Override\n    public abstract AbstractBean clone();\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        try {\n            serializeWithoutName = true;\n            ((AbstractBean) o).serializeWithoutName = true;\n            return Arrays.equals(KryoConverters.serialize(this), KryoConverters.serialize((AbstractBean) o));\n        } finally {\n            serializeWithoutName = false;\n            ((AbstractBean) o).serializeWithoutName = false;\n        }\n    }\n\n    @Override\n    public int hashCode() {\n        try {\n            serializeWithoutName = true;\n            return Arrays.hashCode(KryoConverters.serialize(this));\n        } finally {\n            serializeWithoutName = false;\n        }\n    }\n\n    @NotNull\n    @Override\n    public String toString() {\n        return getClass().getSimpleName() + \" \" + JavaUtil.gson.toJson(this);\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt",
    "content": "package io.nekohasekai.sagernet.fmt\n\nimport android.widget.Toast\nimport io.nekohasekai.sagernet.*\nimport io.nekohasekai.sagernet.bg.VpnService\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.database.ProxyEntity.Companion.TYPE_CONFIG\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.fmt.ConfigBuildResult.IndexEntity\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.fmt.hysteria.buildSingBoxOutboundHysteriaBean\nimport io.nekohasekai.sagernet.fmt.internal.ChainBean\nimport io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean\nimport io.nekohasekai.sagernet.fmt.shadowsocks.buildSingBoxOutboundShadowsocksBean\nimport io.nekohasekai.sagernet.fmt.socks.SOCKSBean\nimport io.nekohasekai.sagernet.fmt.socks.buildSingBoxOutboundSocksBean\nimport io.nekohasekai.sagernet.fmt.ssh.SSHBean\nimport io.nekohasekai.sagernet.fmt.ssh.buildSingBoxOutboundSSHBean\nimport io.nekohasekai.sagernet.fmt.tuic.TuicBean\nimport io.nekohasekai.sagernet.fmt.tuic.buildSingBoxOutboundTuicBean\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean\nimport io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundStandardV2RayBean\nimport io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean\nimport io.nekohasekai.sagernet.fmt.wireguard.buildSingBoxOutboundWireguardBean\nimport io.nekohasekai.sagernet.ktx.isIpAddress\nimport io.nekohasekai.sagernet.ktx.mkPort\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport moe.matsuri.nb4a.*\nimport moe.matsuri.nb4a.SingBoxOptions.*\nimport moe.matsuri.nb4a.plugin.Plugins\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSBean\nimport moe.matsuri.nb4a.proxy.anytls.buildSingBoxOutboundAnyTLSBean\nimport moe.matsuri.nb4a.proxy.config.ConfigBean\nimport moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean\nimport moe.matsuri.nb4a.proxy.shadowtls.buildSingBoxOutboundShadowTLSBean\nimport moe.matsuri.nb4a.utils.JavaUtil.gson\nimport moe.matsuri.nb4a.utils.Util\nimport moe.matsuri.nb4a.utils.listByLineOrComma\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nconst val TAG_MIXED = \"mixed-in\"\n\nconst val TAG_PROXY = \"proxy\"\nconst val TAG_DIRECT = \"direct\"\nconst val TAG_BYPASS = \"bypass\"\nconst val TAG_BLOCK = \"block\"\n\nconst val LOCALHOST = \"127.0.0.1\"\n\nclass ConfigBuildResult(\n    var config: String,\n    var externalIndex: List<IndexEntity>,\n    var mainEntId: Long,\n    var trafficMap: Map<String, List<ProxyEntity>>,\n    var profileTagMap: Map<Long, String>,\n    val selectorGroupId: Long,\n) {\n    data class IndexEntity(var chain: LinkedHashMap<Int, ProxyEntity>)\n}\n\nfun buildConfig(\n    proxy: ProxyEntity, forTest: Boolean = false, forExport: Boolean = false\n): ConfigBuildResult {\n\n    if (proxy.type == TYPE_CONFIG) {\n        val bean = proxy.requireBean() as ConfigBean\n        if (bean.type == 0) {\n            return ConfigBuildResult(\n                bean.config,\n                listOf(),\n                proxy.id, //\n                mapOf(TAG_PROXY to listOf(proxy)), //\n                mapOf(proxy.id to TAG_PROXY), //\n                -1L\n            )\n        }\n    }\n\n    val trafficMap = HashMap<String, List<ProxyEntity>>()\n    val tagMap = HashMap<Long, String>()\n    val globalOutbounds = HashMap<Long, String>()\n    val selectorNames = ArrayList<String>()\n    val group = SagerDatabase.groupDao.getById(proxy.groupId)\n\n    fun ProxyEntity.resolveChainInternal(): MutableList<ProxyEntity> {\n        val bean = requireBean()\n        if (bean is ChainBean) {\n            val beans = SagerDatabase.proxyDao.getEntities(bean.proxies)\n            val beansMap = beans.associateBy { it.id }\n            val beanList = ArrayList<ProxyEntity>()\n            for (proxyId in bean.proxies) {\n                val item = beansMap[proxyId] ?: continue\n                beanList.addAll(item.resolveChainInternal())\n            }\n            return beanList.asReversed()\n        }\n        return mutableListOf(this)\n    }\n\n    fun selectorName(name_: String): String {\n        var name = name_\n        var count = 0\n        while (selectorNames.contains(name)) {\n            count++\n            name = \"$name_-$count\"\n        }\n        selectorNames.add(name)\n        return name\n    }\n\n    fun ProxyEntity.resolveChain(): MutableList<ProxyEntity> {\n        val thisGroup = SagerDatabase.groupDao.getById(groupId)\n        val frontProxy = thisGroup?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) }\n        val landingProxy = thisGroup?.landingProxy?.let { SagerDatabase.proxyDao.getById(it) }\n        val list = resolveChainInternal()\n        if (frontProxy != null) {\n            list.add(frontProxy)\n        }\n        if (landingProxy != null) {\n            list.add(0, landingProxy)\n        }\n        return list\n    }\n\n    val extraRules = if (forTest) listOf() else SagerDatabase.rulesDao.enabledRules()\n    val extraProxies =\n        if (forTest) mapOf() else SagerDatabase.proxyDao.getEntities(extraRules.mapNotNull { rule ->\n            rule.outbound.takeIf { it > 0 && it != proxy.id }\n        }.toHashSet().toList()).associateBy { it.id }\n    val buildSelector = !forTest && group?.isSelector == true && !forExport\n    val userDNSRuleList = mutableListOf<DNSRule_DefaultOptions>()\n    val domainListDNSDirectForce = mutableListOf<String>()\n    val bypassDNSBeans = hashSetOf<AbstractBean>()\n    val isVPN = DataStore.serviceMode == Key.MODE_VPN\n    val bind = if (!forTest && DataStore.allowAccess) \"0.0.0.0\" else LOCALHOST\n    val remoteDns = DataStore.remoteDns.split(\"\\n\")\n        .mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith(\"#\") } }\n    val directDNS = DataStore.directDns.split(\"\\n\")\n        .mapNotNull { dns -> dns.trim().takeIf { it.isNotBlank() && !it.startsWith(\"#\") } }\n    val enableDnsRouting = DataStore.enableDnsRouting\n    val useFakeDns = DataStore.enableFakeDns && !forTest\n    val needSniff = DataStore.trafficSniffing > 0\n    val needSniffOverride = DataStore.trafficSniffing == 2\n    val externalIndexMap = ArrayList<IndexEntity>()\n    val ipv6Mode = if (forTest) IPv6Mode.ENABLE else DataStore.ipv6Mode\n\n    fun genDomainStrategy(noAsIs: Boolean): String {\n        return when {\n            !noAsIs -> \"\"\n            ipv6Mode == IPv6Mode.DISABLE -> \"ipv4_only\"\n            ipv6Mode == IPv6Mode.PREFER -> \"prefer_ipv6\"\n            ipv6Mode == IPv6Mode.ONLY -> \"ipv6_only\"\n            else -> \"prefer_ipv4\"\n        }\n    }\n\n    return MyOptions().apply {\n        if (!forTest && DataStore.enableClashAPI) experimental = ExperimentalOptions().apply {\n            clash_api = ClashAPIOptions().apply {\n                external_controller = \"127.0.0.1:9090\"\n                external_ui = \"../files/yacd\"\n            }\n        }\n\n        log = LogOptions().apply {\n            level = when (DataStore.logLevel) {\n                0 -> \"panic\"\n                1 -> \"warn\"\n                2 -> \"info\"\n                3 -> \"debug\"\n                4 -> \"trace\"\n                else -> \"info\"\n            }\n        }\n\n        dns = DNSOptions().apply {\n            servers = mutableListOf()\n            rules = mutableListOf()\n            independent_cache = true\n        }\n\n        fun autoDnsDomainStrategy(s: String): String? {\n            if (s.isNotEmpty()) {\n                return s\n            }\n            return when (ipv6Mode) {\n                IPv6Mode.DISABLE -> \"ipv4_only\"\n                IPv6Mode.ENABLE -> \"prefer_ipv4\"\n                IPv6Mode.PREFER -> \"prefer_ipv6\"\n                IPv6Mode.ONLY -> \"ipv6_only\"\n                else -> null\n            }\n        }\n\n        inbounds = mutableListOf()\n\n        if (!forTest) {\n            if (isVPN) inbounds.add(Inbound_TunOptions().apply {\n                type = \"tun\"\n                tag = \"tun-in\"\n                stack = when (DataStore.tunImplementation) {\n                    TunImplementation.GVISOR -> \"gvisor\"\n                    TunImplementation.SYSTEM -> \"system\"\n                    else -> \"mixed\"\n                }\n                endpoint_independent_nat = true\n                mtu = DataStore.mtu\n                domain_strategy = genDomainStrategy(DataStore.resolveDestination)\n                sniff = needSniff\n                sniff_override_destination = needSniffOverride\n                when (ipv6Mode) {\n                    IPv6Mode.DISABLE -> {\n                        inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + \"/28\")\n                    }\n\n                    IPv6Mode.ONLY -> {\n                        inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + \"/126\")\n                    }\n\n                    else -> {\n                        inet4_address = listOf(VpnService.PRIVATE_VLAN4_CLIENT + \"/28\")\n                        inet6_address = listOf(VpnService.PRIVATE_VLAN6_CLIENT + \"/126\")\n                    }\n                }\n            })\n            inbounds.add(Inbound_MixedOptions().apply {\n                type = \"mixed\"\n                tag = TAG_MIXED\n                listen = bind\n                listen_port = DataStore.mixedPort\n                domain_strategy = genDomainStrategy(DataStore.resolveDestination)\n                sniff = needSniff\n                sniff_override_destination = needSniffOverride\n            })\n        }\n\n        outbounds = mutableListOf()\n\n        // init routing object\n        route = RouteOptions().apply {\n            auto_detect_interface = true\n            rules = mutableListOf()\n            rule_set = mutableListOf()\n        }\n\n        // returns outbound tag\n        fun buildChain(\n            chainId: Long, entity: ProxyEntity\n        ): String {\n            val profileList = entity.resolveChain()\n            val chainTrafficSet = HashSet<ProxyEntity>().apply {\n                plusAssign(profileList)\n                add(entity)\n            }\n\n            var currentOutbound: SingBoxOption\n            lateinit var pastOutbound: SingBoxOption\n            lateinit var pastInboundTag: String\n            var pastEntity: ProxyEntity? = null\n            val externalChainMap = LinkedHashMap<Int, ProxyEntity>()\n            externalIndexMap.add(IndexEntity(externalChainMap))\n            val chainOutbounds = ArrayList<SingBoxOption>()\n\n            // chainTagOut: v2ray outbound tag for this chain\n            var chainTagOut = \"\"\n            val chainTag = \"c-$chainId\"\n            var muxApplied = false\n\n            val defaultServerDomainStrategy = SingBoxOptionsUtil.domainStrategy(\"server\")\n\n            profileList.forEachIndexed { index, proxyEntity ->\n                val bean = proxyEntity.requireBean()\n\n                // tagOut: v2ray outbound tag for a profile\n                // profile2 (in) (global)   tag g-(id)\n                // profile1                 tag (chainTag)-(id)\n                // profile0 (out)           tag (chainTag)-(id) / single: \"proxy\"\n                var tagOut = \"$chainTag-${proxyEntity.id}\"\n\n                // needGlobal: can only contain one?\n                var needGlobal = false\n\n                // first profile set as global\n                if (index == profileList.lastIndex) {\n                    needGlobal = true\n                    tagOut = \"g-\" + proxyEntity.id\n                    bypassDNSBeans += proxyEntity.requireBean()\n                }\n\n                // last profile set as \"proxy\"\n                if (chainId == 0L && index == 0) {\n                    tagOut = TAG_PROXY\n                }\n\n                // selector human readable name\n                if (buildSelector && index == 0) {\n                    tagOut = selectorName(bean.displayName())\n                }\n\n\n                // chain rules\n                if (index > 0) {\n                    // chain route/proxy rules\n                    if (pastEntity!!.needExternal()) {\n                        route.rules.add(Rule_DefaultOptions().apply {\n                            inbound = listOf(pastInboundTag)\n                            outbound = tagOut\n                        })\n                    } else {\n                        pastOutbound._hack_config_map[\"detour\"] = tagOut\n                    }\n                } else {\n                    // index == 0 means last profile in chain / not chain\n                    chainTagOut = tagOut\n                }\n\n                // now tagOut is determined\n                if (needGlobal) {\n                    globalOutbounds[proxyEntity.id]?.let {\n                        if (index == 0) chainTagOut = it // single, duplicate chain\n                        return@forEachIndexed\n                    }\n                    globalOutbounds[proxyEntity.id] = tagOut\n                }\n\n                if (proxyEntity.needExternal()) { // externel outbound\n                    val localPort = mkPort()\n                    externalChainMap[localPort] = proxyEntity\n                    currentOutbound = Outbound_SocksOptions().apply {\n                        type = \"socks\"\n                        server = LOCALHOST\n                        server_port = localPort\n                    }\n                } else {\n                    // internal outbound\n\n                    currentOutbound = when (bean) {\n                        is ConfigBean -> CustomSingBoxOption(bean.config)\n\n                        is ShadowTLSBean -> // before StandardV2RayBean\n                            buildSingBoxOutboundShadowTLSBean(bean)\n\n                        is StandardV2RayBean -> // http/trojan/vmess/vless\n                            buildSingBoxOutboundStandardV2RayBean(bean)\n\n                        is HysteriaBean ->\n                            buildSingBoxOutboundHysteriaBean(bean)\n\n                        is TuicBean ->\n                            buildSingBoxOutboundTuicBean(bean)\n\n                        is SOCKSBean ->\n                            buildSingBoxOutboundSocksBean(bean)\n\n                        is ShadowsocksBean ->\n                            buildSingBoxOutboundShadowsocksBean(bean)\n\n                        is WireGuardBean ->\n                            buildSingBoxOutboundWireguardBean(bean)\n\n                        is SSHBean ->\n                            buildSingBoxOutboundSSHBean(bean)\n\n                        is AnyTLSBean ->\n                            buildSingBoxOutboundAnyTLSBean(bean)\n\n                        else -> throw IllegalStateException(\"can't reach\")\n                    }\n\n                    // internal mux\n                    if (!muxApplied) {\n                        val muxObj = proxyEntity.singMux()\n                        if (muxObj != null && muxObj.enabled) {\n                            muxApplied = true\n                            currentOutbound._hack_config_map[\"multiplex\"] = muxObj.asMap()\n                        }\n                    }\n                }\n\n                // internal & external\n                currentOutbound.apply {\n                    // udp over tcp\n                    try {\n                        val sUoT = bean.javaClass.getField(\"sUoT\").get(bean)\n                        if (sUoT is Boolean && sUoT) {\n                            _hack_config_map[\"udp_over_tcp\"] = true\n                        }\n                    } catch (_: Exception) {\n                    }\n\n                    // domain_strategy\n                    pastEntity?.requireBean()?.apply {\n                        // don't loopback\n                        if (defaultServerDomainStrategy != \"\" && !serverAddress.isIpAddress()) {\n                            domainListDNSDirectForce.add(\"full:$serverAddress\")\n                        }\n                    }\n                    _hack_config_map[\"domain_strategy\"] =\n                        if (forTest) \"\" else defaultServerDomainStrategy\n\n                    _hack_config_map[\"tag\"] = tagOut\n\n                    _hack_custom_config = bean.customOutboundJson\n                }\n\n                // External proxy need a dokodemo-door inbound to forward the traffic\n                // For external proxy software, their traffic must goes to v2ray-core to use protected fd.\n                bean.finalAddress = bean.serverAddress\n                bean.finalPort = bean.serverPort\n                if (bean.canMapping() && proxyEntity.needExternal()) {\n                    // With ss protect, don't use mapping\n                    var needExternal = true\n                    if (index == profileList.lastIndex) {\n                        val pluginId = when (bean) {\n                            is HysteriaBean -> if (bean.protocolVersion == 1) \"hysteria-plugin\" else \"hysteria2-plugin\"\n                            else -> \"\"\n                        }\n                        if (Plugins.isUsingMatsuriExe(pluginId)) {\n                            needExternal = false\n                        } else if (Plugins.getPluginExternal(pluginId) != null) {\n                            throw Exception(\"You are using an unsupported $pluginId, please download the correct plugin.\")\n                        }\n                    }\n                    if (needExternal) {\n                        val mappingPort = mkPort()\n                        bean.finalAddress = LOCALHOST\n                        bean.finalPort = mappingPort\n\n                        inbounds.add(Inbound_DirectOptions().apply {\n                            type = \"direct\"\n                            listen = LOCALHOST\n                            listen_port = mappingPort\n                            tag = \"$chainTag-mapping-${proxyEntity.id}\"\n\n                            override_address = bean.serverAddress\n                            override_port = bean.serverPort\n\n                            pastInboundTag = tag\n\n                            // no chain rule and not outbound, so need to set to direct\n                            if (index == profileList.lastIndex) {\n                                route.rules.add(Rule_DefaultOptions().apply {\n                                    inbound = listOf(tag)\n                                    outbound = TAG_DIRECT\n                                })\n                            }\n                        })\n                    }\n                }\n\n                outbounds.add(currentOutbound)\n                chainOutbounds.add(currentOutbound)\n                pastOutbound = currentOutbound\n                pastEntity = proxyEntity\n            }\n\n            trafficMap[chainTagOut] = chainTrafficSet.toList()\n            return chainTagOut\n        }\n\n        // build outbounds\n        if (buildSelector) {\n            val list = group.id.let { SagerDatabase.proxyDao.getByGroup(it) }\n            list.forEach {\n                tagMap[it.id] = buildChain(it.id, it)\n            }\n            outbounds.add(0, Outbound_SelectorOptions().apply {\n                type = \"selector\"\n                tag = TAG_PROXY\n                default_ = tagMap[proxy.id]\n                outbounds = tagMap.values.toList()\n            })\n        } else {\n            buildChain(0, proxy)\n        }\n        // build outbounds from route item\n        extraProxies.forEach { (key, p) ->\n            tagMap[key] = buildChain(key, p)\n        }\n\n        // apply user rules\n        for (rule in extraRules) {\n            if (rule.packages.isNotEmpty()) {\n                PackageCache.awaitLoadSync()\n            }\n            val uidList = rule.packages.map {\n                if (!isVPN) {\n                    Toast.makeText(\n                        SagerNet.application,\n                        SagerNet.application.getString(R.string.route_need_vpn, rule.displayName()),\n                        Toast.LENGTH_SHORT\n                    ).show()\n                }\n                PackageCache[it]?.takeIf { uid -> uid >= 1000 }\n            }.toHashSet().filterNotNull()\n            val ruleSets = mutableListOf<RuleSet>()\n\n            val ruleObj = Rule_DefaultOptions().apply {\n                if (uidList.isNotEmpty()) {\n                    PackageCache.awaitLoadSync()\n                    user_id = uidList\n                }\n                var domainList: List<String>? = null\n                if (rule.domains.isNotBlank()) {\n                    domainList = rule.domains.listByLineOrComma()\n                    makeSingBoxRule(domainList, false)\n                }\n                if (rule.ip.isNotBlank()) {\n                    makeSingBoxRule(rule.ip.listByLineOrComma(), true)\n                }\n\n                if (rule_set != null) generateRuleSet(rule_set, ruleSets)\n\n                if (rule.port.isNotBlank()) {\n                    port = mutableListOf<Int>()\n                    port_range = mutableListOf<String>()\n                    rule.port.listByLineOrComma().map {\n                        if (it.contains(\":\")) {\n                            port_range.add(it)\n                        } else {\n                            it.toIntOrNull()?.apply { port.add(this) }\n                        }\n                    }\n                }\n                if (rule.sourcePort.isNotBlank()) {\n                    source_port = mutableListOf<Int>()\n                    source_port_range = mutableListOf<String>()\n                    rule.sourcePort.listByLineOrComma().map {\n                        if (it.contains(\":\")) {\n                            source_port_range.add(it)\n                        } else {\n                            it.toIntOrNull()?.apply { source_port.add(this) }\n                        }\n                    }\n                }\n                if (rule.network.isNotBlank()) {\n                    network = listOf(rule.network)\n                }\n                if (rule.source.isNotBlank()) {\n                    source_ip_cidr = rule.source.listByLineOrComma()\n                }\n                if (rule.protocol.isNotBlank()) {\n                    protocol = rule.protocol.listByLineOrComma()\n                }\n\n                fun makeDnsRuleObj(): DNSRule_DefaultOptions {\n                    return DNSRule_DefaultOptions().apply {\n                        if (uidList.isNotEmpty()) user_id = uidList\n                        domainList?.let { makeSingBoxRule(it) }\n                    }\n                }\n\n                when (rule.outbound) {\n                    -1L -> {\n                        userDNSRuleList += makeDnsRuleObj().apply { server = \"dns-direct\" }\n                    }\n\n                    0L -> {\n                        if (useFakeDns) userDNSRuleList += makeDnsRuleObj().apply {\n                            server = \"dns-fake\"\n                            inbound = listOf(\"tun-in\")\n                        }\n                        userDNSRuleList += makeDnsRuleObj().apply {\n                            server = \"dns-remote\"\n                        }\n                    }\n\n                    -2L -> {\n                        userDNSRuleList += makeDnsRuleObj().apply {\n                            server = \"dns-block\"\n                            disable_cache = true\n                        }\n                    }\n                }\n\n                outbound = when (val outId = rule.outbound) {\n                    0L -> TAG_PROXY\n                    -1L -> TAG_BYPASS\n                    -2L -> TAG_BLOCK\n                    else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId] ?: \"\"\n                }\n\n                _hack_custom_config = rule.config\n            }\n\n            if (!ruleObj.checkEmpty()) {\n                if (ruleObj.outbound.isNullOrBlank()) {\n                    Toast.makeText(\n                        SagerNet.application,\n                        \"Warning: \" + rule.displayName() + \": A non-existent outbound was specified.\",\n                        Toast.LENGTH_LONG\n                    ).show()\n                } else {\n                    // block 改用新的写法\n                    if (ruleObj.outbound == TAG_BLOCK) {\n                        ruleObj.outbound = null\n                        ruleObj.action = \"reject\"\n                    }\n                    route.rules.add(ruleObj)\n                    route.rule_set.addAll(ruleSets)\n                }\n            }\n        }\n\n        // 对 rule_set tag 去重\n        if (route.rule_set != null) {\n            route.rule_set = route.rule_set.distinctBy { it.tag }\n        }\n\n        for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply {\n            tag = freedom\n            type = \"direct\"\n        })\n\n        // Bypass Lookup for the first profile\n        bypassDNSBeans.forEach {\n            var serverAddr = it.serverAddress\n\n            if (it is ConfigBean) {\n                var config = mutableMapOf<String, Any>()\n                config = gson.fromJson(it.config, config.javaClass)\n                config[\"server\"]?.apply {\n                    serverAddr = toString()\n                }\n            }\n\n            if (!serverAddr.isIpAddress()) {\n                domainListDNSDirectForce.add(\"full:${serverAddr}\")\n            }\n        }\n\n        remoteDns.forEach {\n            var address = it\n            if (address.contains(\"://\")) {\n                address = address.substringAfter(\"://\")\n            }\n            \"https://$address\".toHttpUrlOrNull()?.apply {\n                if (!host.isIpAddress()) {\n                    domainListDNSDirectForce.add(\"full:$host\")\n                }\n            }\n        }\n\n        dns.servers.add(DNSServerOptions().apply {\n            address = \"rcode://success\"\n            tag = \"dns-block\"\n        })\n\n        dns.servers.add(DNSServerOptions().apply {\n            address = \"local\"\n            tag = \"dns-local\"\n            detour = TAG_DIRECT\n        })\n\n        directDNS.firstOrNull().let {\n            dns.servers.add(DNSServerOptions().apply {\n                address = it ?: throw Exception(\"No direct DNS, check your settings!\")\n                tag = \"dns-direct\"\n                detour = TAG_DIRECT\n                address_resolver = \"dns-local\"\n                strategy = autoDnsDomainStrategy(SingBoxOptionsUtil.domainStrategy(tag))\n            })\n        }\n\n        remoteDns.firstOrNull().let {\n            // Always use direct DNS for urlTest\n            if (!forTest) dns.servers.add(DNSServerOptions().apply {\n                address = it ?: throw Exception(\"No remote DNS, check your settings!\")\n                tag = \"dns-remote\"\n                address_resolver = \"dns-direct\"\n                strategy = autoDnsDomainStrategy(SingBoxOptionsUtil.domainStrategy(tag))\n            })\n        }\n\n        dns.final_ = if (forTest) \"dns-direct\" else \"dns-remote\"\n\n        // dns object user rules\n        if (enableDnsRouting) {\n            userDNSRuleList.forEach {\n                if (!it.checkEmpty()) dns.rules.add(it)\n            }\n        }\n\n        if (forTest) {\n            dns.rules = listOf()\n        } else {\n            // built-in DNS rules\n            route.rules.add(0, Rule_DefaultOptions().apply {\n                protocol = listOf(\"dns\")\n                action = \"hijack-dns\"\n            })\n            route.rules.add(0, Rule_DefaultOptions().apply {\n                port = listOf(53)\n                action = \"hijack-dns\"\n            })\n            if (DataStore.bypassLanInCore) {\n                route.rules.add(Rule_DefaultOptions().apply {\n                    outbound = TAG_BYPASS\n                    ip_is_private = true\n                })\n            }\n            // block mcast\n            route.rules.add(Rule_DefaultOptions().apply {\n                ip_cidr = listOf(\"224.0.0.0/3\", \"ff00::/8\")\n                source_ip_cidr = listOf(\"224.0.0.0/3\", \"ff00::/8\")\n                action = \"reject\"\n            })\n            // FakeDNS obj\n            if (useFakeDns) {\n                dns.fakeip = DNSFakeIPOptions().apply {\n                    enabled = true\n                    inet4_range = \"198.18.0.0/15\"\n                    inet6_range = \"fc00::/18\"\n                }\n                dns.servers.add(DNSServerOptions().apply {\n                    address = \"fakeip\"\n                    tag = \"dns-fake\"\n                    strategy = \"ipv4_only\"\n                })\n                dns.rules.add(DNSRule_DefaultOptions().apply {\n                    inbound = listOf(\"tun-in\")\n                    server = \"dns-fake\"\n                    disable_cache = true\n                })\n            }\n            // avoid loopback\n            dns.rules.add(0, DNSRule_DefaultOptions().apply {\n                outbound = mutableListOf(\"any\")\n                server = \"dns-direct\"\n            })\n            // force bypass (always top DNS rule)\n            if (domainListDNSDirectForce.isNotEmpty()) {\n                dns.rules.add(0, DNSRule_DefaultOptions().apply {\n                    makeSingBoxRule(domainListDNSDirectForce.toHashSet().toList())\n                    server = \"dns-direct\"\n                })\n            }\n        }\n\n        if (!forTest) _hack_custom_config = DataStore.globalCustomConfig\n    }.let {\n        val configMap = it.asMap()\n        Util.mergeJSON(configMap, proxy.requireBean().customConfigJson)\n        ConfigBuildResult(\n            gson.toJson(configMap),\n            externalIndexMap,\n            proxy.id,\n            trafficMap,\n            tagMap,\n            if (buildSelector) group.id else -1L\n        )\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/KryoConverters.java",
    "content": "package io.nekohasekai.sagernet.fmt;\n\nimport androidx.room.TypeConverter;\n\nimport com.esotericsoftware.kryo.KryoException;\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\n\nimport io.nekohasekai.sagernet.database.SubscriptionBean;\nimport io.nekohasekai.sagernet.fmt.http.HttpBean;\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean;\nimport io.nekohasekai.sagernet.fmt.internal.ChainBean;\nimport io.nekohasekai.sagernet.fmt.mieru.MieruBean;\nimport io.nekohasekai.sagernet.fmt.naive.NaiveBean;\nimport io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean;\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSBean;\nimport moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean;\nimport io.nekohasekai.sagernet.fmt.socks.SOCKSBean;\nimport io.nekohasekai.sagernet.fmt.ssh.SSHBean;\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean;\nimport io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean;\nimport io.nekohasekai.sagernet.fmt.tuic.TuicBean;\nimport io.nekohasekai.sagernet.fmt.v2ray.VMessBean;\nimport io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean;\nimport io.nekohasekai.sagernet.ktx.KryosKt;\nimport io.nekohasekai.sagernet.ktx.Logs;\nimport moe.matsuri.nb4a.proxy.config.ConfigBean;\nimport moe.matsuri.nb4a.proxy.neko.NekoBean;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class KryoConverters {\n\n    private static final byte[] NULL = new byte[0];\n\n    @TypeConverter\n    public static byte[] serialize(Serializable bean) {\n        if (bean == null) return NULL;\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        ByteBufferOutput buffer = KryosKt.byteBuffer(out);\n        bean.serializeToBuffer(buffer);\n        buffer.flush();\n        buffer.close();\n        return out.toByteArray();\n    }\n\n    public static <T extends Serializable> T deserialize(T bean, byte[] bytes) {\n        if (bytes == null) return bean;\n        ByteArrayInputStream input = new ByteArrayInputStream(bytes);\n        ByteBufferInput buffer = KryosKt.byteBuffer(input);\n        try {\n            bean.deserializeFromBuffer(buffer);\n        } catch (KryoException e) {\n            Logs.INSTANCE.w(e);\n        }\n        bean.initializeDefaultValues();\n        return bean;\n    }\n\n    @TypeConverter\n    public static SOCKSBean socksDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new SOCKSBean(), bytes);\n    }\n\n    @TypeConverter\n    public static HttpBean httpDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new HttpBean(), bytes);\n    }\n\n    @TypeConverter\n    public static ShadowsocksBean shadowsocksDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new ShadowsocksBean(), bytes);\n    }\n\n    @TypeConverter\n    public static ConfigBean configDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new ConfigBean(), bytes);\n    }\n\n    @TypeConverter\n    public static VMessBean vmessDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new VMessBean(), bytes);\n    }\n\n    @TypeConverter\n    public static TrojanBean trojanDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new TrojanBean(), bytes);\n    }\n\n    @TypeConverter\n    public static TrojanGoBean trojanGoDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new TrojanGoBean(), bytes);\n    }\n\n    @TypeConverter\n    public static MieruBean mieruDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new MieruBean(), bytes);\n    }\n\n    @TypeConverter\n    public static NaiveBean naiveDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new NaiveBean(), bytes);\n    }\n\n    @TypeConverter\n    public static HysteriaBean hysteriaDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new HysteriaBean(), bytes);\n    }\n\n    @TypeConverter\n    public static SSHBean sshDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new SSHBean(), bytes);\n    }\n\n    @TypeConverter\n    public static WireGuardBean wireguardDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new WireGuardBean(), bytes);\n    }\n\n    @TypeConverter\n    public static TuicBean tuicDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new TuicBean(), bytes);\n    }\n\n    @TypeConverter\n    public static ShadowTLSBean shadowTLSDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new ShadowTLSBean(), bytes);\n    }\n\n    @TypeConverter\n    public static AnyTLSBean anyTLSDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new AnyTLSBean(), bytes);\n    }\n\n\n    @TypeConverter\n    public static ChainBean chainDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new ChainBean(), bytes);\n    }\n\n    @TypeConverter\n    public static NekoBean nekoDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new NekoBean(), bytes);\n    }\n\n    @TypeConverter\n    public static SubscriptionBean subscriptionDeserialize(byte[] bytes) {\n        if (JavaUtil.isEmpty(bytes)) return null;\n        return deserialize(new SubscriptionBean(), bytes);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/PluginEntry.kt",
    "content": "package io.nekohasekai.sagernet.fmt\n\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\n\nenum class PluginEntry(\n    val pluginId: String,\n    val displayName: String,\n    val packageName: String, // for play and f-droid page\n    val downloadSource: DownloadSource = DownloadSource()\n) {\n    TrojanGo(\n        \"trojan-go-plugin\",\n        SagerNet.application.getString(R.string.action_trojan_go),\n        \"io.nekohasekai.sagernet.plugin.trojan_go\"\n    ),\n    MieruProxy(\n        \"mieru-plugin\",\n        SagerNet.application.getString(R.string.action_mieru),\n        \"moe.matsuri.exe.mieru\",\n        DownloadSource(\n            playStore = false,\n            fdroid = false,\n            downloadLink = \"https://github.com/MatsuriDayo/plugins/releases?q=mieru\"\n        )\n    ),\n    NaiveProxy(\n        \"naive-plugin\",\n        SagerNet.application.getString(R.string.action_naive),\n        \"moe.matsuri.exe.naive\",\n        DownloadSource(\n            playStore = false,\n            fdroid = false,\n            downloadLink = \"https://github.com/MatsuriDayo/plugins/releases?q=naive\"\n        )\n    ),\n    Hysteria(\n        \"hysteria-plugin\",\n        SagerNet.application.getString(R.string.action_hysteria),\n        \"moe.matsuri.exe.hysteria\",\n        DownloadSource(\n            playStore = false,\n            fdroid = false,\n            downloadLink = \"https://github.com/MatsuriDayo/plugins/releases?q=Hysteria\"\n        )\n    ),\n    ;\n\n    data class DownloadSource(\n        val playStore: Boolean = true,\n        val fdroid: Boolean = true,\n        val downloadLink: String = \"https://matsuridayo.github.io/\"\n    )\n\n    companion object {\n\n        fun find(name: String): PluginEntry? {\n            for (pluginEntry in enumValues<PluginEntry>()) {\n                if (name == pluginEntry.pluginId) {\n                    return pluginEntry\n                }\n            }\n            return null\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/Serializable.kt",
    "content": "package io.nekohasekai.sagernet.fmt\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport com.esotericsoftware.kryo.io.ByteBufferInput\nimport com.esotericsoftware.kryo.io.ByteBufferOutput\n\nabstract class Serializable : Parcelable {\n    abstract fun initializeDefaultValues()\n    abstract fun serializeToBuffer(output: ByteBufferOutput)\n    abstract fun deserializeFromBuffer(input: ByteBufferInput)\n\n    override fun describeContents() = 0\n\n    override fun writeToParcel(dest: Parcel, flags: Int) {\n        dest.writeByteArray(KryoConverters.serialize(this))\n    }\n\n    abstract class CREATOR<T : Serializable> : Parcelable.Creator<T> {\n        abstract fun newInstance(): T\n\n        override fun createFromParcel(source: Parcel): T {\n            return KryoConverters.deserialize(newInstance(), source.createByteArray())\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/TypeMap.kt",
    "content": "package io.nekohasekai.sagernet.fmt\n\nimport io.nekohasekai.sagernet.database.ProxyEntity\n\nobject TypeMap : HashMap<String, Int>() {\n    init {\n        this[\"socks\"] = ProxyEntity.TYPE_SOCKS\n        this[\"http\"] = ProxyEntity.TYPE_HTTP\n        this[\"ss\"] = ProxyEntity.TYPE_SS\n        this[\"vmess\"] = ProxyEntity.TYPE_VMESS\n        this[\"trojan\"] = ProxyEntity.TYPE_TROJAN\n        this[\"trojan-go\"] = ProxyEntity.TYPE_TROJAN_GO\n        this[\"mieru\"] = ProxyEntity.TYPE_MIERU\n        this[\"naive\"] = ProxyEntity.TYPE_NAIVE\n        this[\"hysteria\"] = ProxyEntity.TYPE_HYSTERIA\n        this[\"ssh\"] = ProxyEntity.TYPE_SSH\n        this[\"wg\"] = ProxyEntity.TYPE_WG\n        this[\"tuic\"] = ProxyEntity.TYPE_TUIC\n        this[\"anytls\"] = ProxyEntity.TYPE_ANYTLS\n        this[\"neko\"] = ProxyEntity.TYPE_NEKO\n        this[\"config\"] = ProxyEntity.TYPE_CONFIG\n    }\n\n    val reversed = HashMap<Int, String>()\n\n    init {\n        TypeMap.forEach { (key, type) ->\n            reversed[type] = key\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/UniversalFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt\n\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.database.ProxyGroup\nimport moe.matsuri.nb4a.utils.Util\n\nfun parseUniversal(link: String): AbstractBean {\n    return if (link.contains(\"?\")) {\n        val type = link.substringAfter(\"sn://\").substringBefore(\"?\")\n        ProxyEntity(type = TypeMap[type] ?: error(\"Type $type not found\")).apply {\n            putByteArray(Util.zlibDecompress(Util.b64Decode(link.substringAfter(\"?\"))))\n        }.requireBean()\n    } else {\n        val type = link.substringAfter(\"sn://\").substringBefore(\":\")\n        ProxyEntity(type = TypeMap[type] ?: error(\"Type $type not found\")).apply {\n            putByteArray(Util.b64Decode(link.substringAfter(\":\").substringAfter(\":\")))\n        }.requireBean()\n    }\n}\n\nfun AbstractBean.toUniversalLink(): String {\n    var link = \"sn://\"\n    link += TypeMap.reversed[ProxyEntity().putBean(this).type]\n    link += \"?\"\n    link += Util.b64EncodeUrlSafe(Util.zlibCompress(KryoConverters.serialize(this), 9))\n    return link\n}\n\n\nfun ProxyGroup.toUniversalLink(): String {\n    var link = \"sn://subscription?\"\n    export = true\n    link += Util.b64EncodeUrlSafe(Util.zlibCompress(KryoConverters.serialize(this), 9))\n    export = false\n    return link\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/gson/GsonConverters.java",
    "content": "package io.nekohasekai.sagernet.fmt.gson;\n\nimport androidx.room.TypeConverter;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\n\nimport kotlin.collections.CollectionsKt;\nimport kotlin.collections.SetsKt;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class GsonConverters {\n\n    @TypeConverter\n    public static String toJson(Object value) {\n        if (value instanceof Collection) {\n            if (((Collection<?>) value).isEmpty()) return \"\";\n        }\n        return JavaUtil.gson.toJson(value);\n    }\n\n    @TypeConverter\n    public static List toList(String value) {\n        if (JavaUtil.isNullOrBlank(value)) return CollectionsKt.listOf();\n        return JavaUtil.gson.fromJson(value, List.class);\n    }\n\n    @TypeConverter\n    public static Set toSet(String value) {\n        if (JavaUtil.isNullOrBlank(value)) return SetsKt.setOf();\n        return JavaUtil.gson.fromJson(value, Set.class);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.http;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean;\n\npublic class HttpBean extends StandardV2RayBean {\n\n    public String username;\n    public String password;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (username == null) username = \"\";\n        if (password == null) password = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeString(username);\n        output.writeString(password);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        username = input.readString();\n        password = input.readString();\n    }\n\n    @NotNull\n    @Override\n    public HttpBean clone() {\n        return KryoConverters.deserialize(new HttpBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<HttpBean> CREATOR = new CREATOR<HttpBean>() {\n        @NonNull\n        @Override\n        public HttpBean newInstance() {\n            return new HttpBean();\n        }\n\n        @Override\n        public HttpBean[] newArray(int size) {\n            return new HttpBean[size];\n        }\n    };\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/http/HttpFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.http\n\nimport io.nekohasekai.sagernet.fmt.v2ray.isTLS\nimport io.nekohasekai.sagernet.fmt.v2ray.setTLS\nimport io.nekohasekai.sagernet.ktx.urlSafe\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nfun parseHttp(link: String): HttpBean {\n    val httpUrl = link.toHttpUrlOrNull() ?: error(\"Invalid http(s) link: $link\")\n\n    if (httpUrl.encodedPath != \"/\") error(\"Not http proxy\")\n\n    return HttpBean().apply {\n        serverAddress = httpUrl.host\n        serverPort = httpUrl.port\n        username = httpUrl.username\n        password = httpUrl.password\n        sni = httpUrl.queryParameter(\"sni\")\n        name = httpUrl.fragment\n        setTLS(httpUrl.scheme == \"https\")\n    }\n}\n\nfun HttpBean.toUri(): String {\n    val builder = HttpUrl.Builder().scheme(if (isTLS()) \"https\" else \"http\").host(serverAddress)\n\n    if (serverPort in 1..65535) {\n        builder.port(serverPort)\n    }\n\n    if (username.isNotBlank()) {\n        builder.username(username)\n    }\n    if (password.isNotBlank()) {\n        builder.password(password)\n    }\n    if (sni.isNotBlank()) {\n        builder.addQueryParameter(\"sni\", sni)\n    }\n    if (name.isNotBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n\n    return builder.toString()\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.hysteria;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.ktx.NetsKt;\nimport kotlin.text.StringsKt;\n\npublic class HysteriaBean extends AbstractBean {\n    public Integer protocolVersion;\n\n    // Use serverPorts instead of serverPort\n    public String serverPorts;\n\n    // HY1 & 2\n\n    public String authPayload;\n    public String obfuscation;\n    public String sni;\n    public String caText;\n    public Integer uploadMbps;\n    public Integer downloadMbps;\n    public Boolean allowInsecure;\n    public Integer streamReceiveWindow;\n    public Integer connectionReceiveWindow;\n    public Boolean disableMtuDiscovery;\n    public Integer hopInterval;\n\n    // HY1\n\n    public String alpn;\n\n    public static final int TYPE_NONE = 0;\n    public static final int TYPE_STRING = 1;\n    public static final int TYPE_BASE64 = 2;\n    public Integer authPayloadType;\n\n    public static final int PROTOCOL_UDP = 0;\n    public static final int PROTOCOL_FAKETCP = 1;\n    public static final int PROTOCOL_WECHAT_VIDEO = 2;\n    public Integer protocol;\n\n    @Override\n    public boolean canMapping() {\n        return protocol != PROTOCOL_FAKETCP;\n    }\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (protocolVersion == null) protocolVersion = 2;\n\n        if (authPayloadType == null) authPayloadType = TYPE_NONE;\n        if (authPayload == null) authPayload = \"\";\n        if (protocol == null) protocol = PROTOCOL_UDP;\n        if (obfuscation == null) obfuscation = \"\";\n        if (sni == null) sni = \"\";\n        if (alpn == null) alpn = \"\";\n        if (caText == null) caText = \"\";\n        if (allowInsecure == null) allowInsecure = false;\n\n        if (protocolVersion == 1) {\n            if (uploadMbps == null) uploadMbps = 10;\n            if (downloadMbps == null) downloadMbps = 50;\n        } else {\n            if (uploadMbps == null) uploadMbps = 0;\n            if (downloadMbps == null) downloadMbps = 0;\n        }\n\n        if (streamReceiveWindow == null) streamReceiveWindow = 0;\n        if (connectionReceiveWindow == null) connectionReceiveWindow = 0;\n        if (disableMtuDiscovery == null) disableMtuDiscovery = false;\n        if (hopInterval == null) hopInterval = 10;\n        if (serverPorts == null) serverPorts = \"443\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(7);\n        super.serialize(output);\n\n        output.writeInt(protocolVersion);\n\n        output.writeInt(authPayloadType);\n        output.writeString(authPayload);\n        output.writeInt(protocol);\n        output.writeString(obfuscation);\n        output.writeString(sni);\n        output.writeString(alpn);\n\n        output.writeInt(uploadMbps);\n        output.writeInt(downloadMbps);\n        output.writeBoolean(allowInsecure);\n\n        output.writeString(caText);\n        output.writeInt(streamReceiveWindow);\n        output.writeInt(connectionReceiveWindow);\n        output.writeBoolean(disableMtuDiscovery);\n        output.writeInt(hopInterval);\n        output.writeString(serverPorts);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        if (version >= 7) {\n            protocolVersion = input.readInt();\n        } else {\n            protocolVersion = 1;\n        }\n        authPayloadType = input.readInt();\n        authPayload = input.readString();\n        if (version >= 3) {\n            protocol = input.readInt();\n        }\n        obfuscation = input.readString();\n        sni = input.readString();\n        if (version >= 2) {\n            alpn = input.readString();\n        }\n        uploadMbps = input.readInt();\n        downloadMbps = input.readInt();\n        allowInsecure = input.readBoolean();\n        if (version >= 1) {\n            caText = input.readString();\n            streamReceiveWindow = input.readInt();\n            connectionReceiveWindow = input.readInt();\n            if (version != 4) disableMtuDiscovery = input.readBoolean(); // note: skip 4\n        }\n        if (version >= 5) {\n            hopInterval = input.readInt();\n        }\n        if (version >= 6) {\n            serverPorts = input.readString();\n        } else {\n            // old update to new\n            if (HysteriaFmtKt.isMultiPort(serverAddress)) {\n                serverPorts = StringsKt.substringAfterLast(serverAddress, \":\", serverAddress);\n                serverAddress = StringsKt.substringBeforeLast(serverAddress, \":\", serverAddress);\n            } else {\n                serverPorts = serverPort.toString();\n            }\n        }\n    }\n\n    @Override\n    public String displayAddress() {\n        return NetsKt.wrapIPV6Host(serverAddress) + \":\" + serverPorts;\n    }\n\n    @Override\n    public boolean canTCPing() {\n        return false;\n    }\n\n    @NotNull\n    @Override\n    public HysteriaBean clone() {\n        return KryoConverters.deserialize(new HysteriaBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<HysteriaBean> CREATOR = new CREATOR<HysteriaBean>() {\n        @NonNull\n        @Override\n        public HysteriaBean newInstance() {\n            return new HysteriaBean();\n        }\n\n        @Override\n        public HysteriaBean[] newArray(int size) {\n            return new HysteriaBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/hysteria/HysteriaFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.hysteria\n\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.LOCALHOST\nimport io.nekohasekai.sagernet.ktx.*\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.listByLineOrComma\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.json.JSONObject\nimport java.io.File\n\n\n// hysteria://host:port?auth=123456&peer=sni.domain&insecure=1|0&upmbps=100&downmbps=100&alpn=hysteria&obfs=xplus&obfsParam=123456#remarks\nfun parseHysteria1(url: String): HysteriaBean {\n    val link = url.replace(\"hysteria://\", \"https://\").toHttpUrlOrNull() ?: error(\n        \"invalid hysteria link $url\"\n    )\n    return HysteriaBean().apply {\n        protocolVersion = 1\n        serverAddress = link.host\n        serverPorts = link.port.toString()\n        name = link.fragment\n\n        link.queryParameter(\"mport\")?.also {\n            serverPorts = it\n        }\n        link.queryParameter(\"peer\")?.also {\n            sni = it\n        }\n        link.queryParameter(\"auth\")?.takeIf { it.isNotBlank() }?.also {\n            authPayloadType = HysteriaBean.TYPE_STRING\n            authPayload = it\n        }\n        link.queryParameter(\"insecure\")?.also {\n            allowInsecure = it == \"1\" || it == \"true\"\n        }\n        link.queryParameter(\"upmbps\")?.also {\n            uploadMbps = it.toIntOrNull() ?: uploadMbps\n        }\n        link.queryParameter(\"downmbps\")?.also {\n            downloadMbps = it.toIntOrNull() ?: downloadMbps\n        }\n        link.queryParameter(\"alpn\")?.also {\n            alpn = it\n        }\n        link.queryParameter(\"obfsParam\")?.also {\n            obfuscation = it\n        }\n        link.queryParameter(\"protocol\")?.also {\n            when (it) {\n                \"faketcp\" -> {\n                    protocol = HysteriaBean.PROTOCOL_FAKETCP\n                }\n\n                \"wechat-video\" -> {\n                    protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO\n                }\n            }\n        }\n    }\n}\n\n// hysteria2://[auth@]hostname[:port]/?[key=value]&[key=value]...\nfun parseHysteria2(url: String): HysteriaBean {\n    val link = url\n        .replace(\"hysteria2://\", \"https://\")\n        .replace(\"hy2://\", \"https://\")\n        .toHttpUrlOrNull() ?: error(\"invalid hysteria link $url\")\n    return HysteriaBean().apply {\n        protocolVersion = 2\n        serverAddress = link.host\n        serverPorts = link.port.toString()\n        authPayload = if (link.password.isNotBlank()) {\n            link.username + \":\" + link.password\n        } else {\n            link.username\n        }\n        name = link.fragment\n\n        link.queryParameter(\"mport\")?.also {\n            serverPorts = it\n        }\n        link.queryParameter(\"sni\")?.also {\n            sni = it\n        }\n        link.queryParameter(\"insecure\")?.also {\n            allowInsecure = it == \"1\" || it == \"true\"\n        }\n//        link.queryParameter(\"upmbps\")?.also {\n//            uploadMbps = it.toIntOrNull() ?: uploadMbps\n//        }\n//        link.queryParameter(\"downmbps\")?.also {\n//            downloadMbps = it.toIntOrNull() ?: downloadMbps\n//        }\n        link.queryParameter(\"obfs-password\")?.also {\n            obfuscation = it\n        }\n//        link.queryParameter(\"pinSHA256\")?.also {\n//            // TODO your box do not support it\n//        }\n    }\n}\n\nfun HysteriaBean.toUri(): String {\n    var un = \"\"\n    var pw = \"\"\n    if (protocolVersion == 2) {\n        if (authPayload.contains(\":\")) {\n            un = authPayload.substringBefore(\":\")\n            pw = authPayload.substringAfter(\":\")\n        } else {\n            un = authPayload\n        }\n    }\n    //\n    val builder = linkBuilder()\n        .host(serverAddress)\n        .port(getFirstPort(serverPorts))\n        .username(un)\n        .password(pw)\n    if (isMultiPort(displayAddress())) {\n        builder.addQueryParameter(\"mport\", serverPorts)\n    }\n    if (name.isNotBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n    if (allowInsecure) {\n        builder.addQueryParameter(\"insecure\", \"1\")\n    }\n    if (protocolVersion == 1) {\n        if (sni.isNotBlank()) {\n            builder.addQueryParameter(\"peer\", sni)\n        }\n        if (authPayload.isNotBlank()) {\n            builder.addQueryParameter(\"auth\", authPayload)\n        }\n        builder.addQueryParameter(\"upmbps\", \"$uploadMbps\")\n        builder.addQueryParameter(\"downmbps\", \"$downloadMbps\")\n        if (alpn.isNotBlank()) {\n            builder.addQueryParameter(\"alpn\", alpn)\n        }\n        if (obfuscation.isNotBlank()) {\n            builder.addQueryParameter(\"obfs\", \"xplus\")\n            builder.addQueryParameter(\"obfsParam\", obfuscation)\n        }\n        when (protocol) {\n            HysteriaBean.PROTOCOL_FAKETCP -> {\n                builder.addQueryParameter(\"protocol\", \"faketcp\")\n            }\n\n            HysteriaBean.PROTOCOL_WECHAT_VIDEO -> {\n                builder.addQueryParameter(\"protocol\", \"wechat-video\")\n            }\n        }\n    } else {\n        if (sni.isNotBlank()) {\n            builder.addQueryParameter(\"sni\", sni)\n        }\n        if (obfuscation.isNotBlank()) {\n            builder.addQueryParameter(\"obfs\", \"salamander\")\n            builder.addQueryParameter(\"obfs-password\", obfuscation)\n        }\n    }\n    return builder.toLink(if (protocolVersion == 2) \"hy2\" else \"hysteria\")\n}\n\nfun JSONObject.parseHysteria1Json(): HysteriaBean {\n    // TODO parse HY2 JSON+YAML\n    return HysteriaBean().apply {\n        protocolVersion = 1\n        serverAddress = optString(\"server\").substringBeforeLast(\":\")\n        serverPorts = optString(\"server\").substringAfterLast(\":\")\n        uploadMbps = getIntNya(\"up_mbps\")\n        downloadMbps = getIntNya(\"down_mbps\")\n        obfuscation = getStr(\"obfs\")\n        getStr(\"auth\")?.also {\n            authPayloadType = HysteriaBean.TYPE_BASE64\n            authPayload = it\n        }\n        getStr(\"auth_str\")?.also {\n            authPayloadType = HysteriaBean.TYPE_STRING\n            authPayload = it\n        }\n        getStr(\"protocol\")?.also {\n            when (it) {\n                \"faketcp\" -> {\n                    protocol = HysteriaBean.PROTOCOL_FAKETCP\n                }\n\n                \"wechat-video\" -> {\n                    protocol = HysteriaBean.PROTOCOL_WECHAT_VIDEO\n                }\n            }\n        }\n        sni = getStr(\"server_name\")\n        alpn = getStr(\"alpn\")\n        allowInsecure = getBool(\"insecure\")\n\n        streamReceiveWindow = getIntNya(\"recv_window_conn\")\n        connectionReceiveWindow = getIntNya(\"recv_window\")\n        disableMtuDiscovery = getBool(\"disable_mtu_discovery\")\n    }\n}\n\nfun HysteriaBean.buildHysteria1Config(port: Int, cacheFile: (() -> File)?): String {\n    if (protocolVersion != 1) {\n        throw Exception(\"error version: $protocolVersion\")\n    }\n    return JSONObject().apply {\n        put(\"server\", displayAddress())\n        when (protocol) {\n            HysteriaBean.PROTOCOL_FAKETCP -> {\n                put(\"protocol\", \"faketcp\")\n            }\n\n            HysteriaBean.PROTOCOL_WECHAT_VIDEO -> {\n                put(\"protocol\", \"wechat-video\")\n            }\n        }\n        put(\"up_mbps\", uploadMbps)\n        put(\"down_mbps\", downloadMbps)\n        put(\n            \"socks5\", JSONObject(\n                mapOf(\n                    \"listen\" to \"$LOCALHOST:$port\",\n                )\n            )\n        )\n        put(\"retry\", 5)\n        put(\"fast_open\", true)\n        put(\"lazy_start\", true)\n        put(\"obfs\", obfuscation)\n        when (authPayloadType) {\n            HysteriaBean.TYPE_BASE64 -> put(\"auth\", authPayload)\n            HysteriaBean.TYPE_STRING -> put(\"auth_str\", authPayload)\n        }\n        if (sni.isBlank() && finalAddress == LOCALHOST && !serverAddress.isIpAddress()) {\n            sni = serverAddress\n        }\n        if (sni.isNotBlank()) {\n            put(\"server_name\", sni)\n        }\n        if (alpn.isNotBlank()) put(\"alpn\", alpn)\n        if (caText.isNotBlank() && cacheFile != null) {\n            val caFile = cacheFile()\n            caFile.writeText(caText)\n            put(\"ca\", caFile.absolutePath)\n        }\n\n        if (allowInsecure) put(\"insecure\", true)\n        if (streamReceiveWindow > 0) put(\"recv_window_conn\", streamReceiveWindow)\n        if (connectionReceiveWindow > 0) put(\"recv_window\", connectionReceiveWindow)\n        if (disableMtuDiscovery) put(\"disable_mtu_discovery\", true)\n\n        put(\"hop_interval\", hopInterval)\n    }.toStringPretty()\n}\n\nfun isMultiPort(hyAddr: String): Boolean {\n    if (!hyAddr.contains(\":\")) return false\n    val p = hyAddr.substringAfterLast(\":\")\n    if (p.contains(\"-\") || p.contains(\",\")) return true\n    return false\n}\n\nfun getFirstPort(portStr: String): Int {\n    return portStr.substringBefore(\":\").substringBefore(\",\").toIntOrNull() ?: 443\n}\n\nfun HysteriaBean.canUseSingBox(): Boolean {\n    if (protocol != HysteriaBean.PROTOCOL_UDP) return false\n    return true\n}\n\nfun buildSingBoxOutboundHysteriaBean(bean: HysteriaBean): SingBoxOptions.SingBoxOption {\n    return when (bean.protocolVersion) {\n        1 -> SingBoxOptions.Outbound_HysteriaOptions().apply {\n            type = \"hysteria\"\n            server = bean.serverAddress\n            val port = bean.serverPorts.toIntOrNull()\n            if (port != null) {\n                server_port = port\n            } else {\n                server_ports = hopPortsToSingboxList(bean.serverPorts)\n            }\n            hop_interval = \"${bean.hopInterval}s\"\n            up_mbps = bean.uploadMbps\n            down_mbps = bean.downloadMbps\n            obfs = bean.obfuscation\n            disable_mtu_discovery = bean.disableMtuDiscovery\n            when (bean.authPayloadType) {\n                HysteriaBean.TYPE_BASE64 -> auth = bean.authPayload\n                HysteriaBean.TYPE_STRING -> auth_str = bean.authPayload\n            }\n            if (bean.streamReceiveWindow > 0) {\n                recv_window_conn = bean.streamReceiveWindow.toLong()\n            }\n            if (bean.connectionReceiveWindow > 0) {\n                recv_window_conn = bean.connectionReceiveWindow.toLong()\n            }\n            tls = SingBoxOptions.OutboundTLSOptions().apply {\n                if (bean.sni.isNotBlank()) {\n                    server_name = bean.sni\n                }\n                if (bean.alpn.isNotBlank()) {\n                    alpn = bean.alpn.listByLineOrComma()\n                }\n                if (bean.caText.isNotBlank()) {\n                    certificate = bean.caText\n                }\n                insecure = bean.allowInsecure || DataStore.globalAllowInsecure\n                enabled = true\n            }\n        }\n\n        2 -> SingBoxOptions.Outbound_Hysteria2Options().apply {\n            type = \"hysteria2\"\n            server = bean.serverAddress\n            val port = bean.serverPorts.toIntOrNull()\n            if (port != null) {\n                server_port = port\n            } else {\n                server_ports = hopPortsToSingboxList(bean.serverPorts)\n            }\n            hop_interval = \"${bean.hopInterval}s\"\n            up_mbps = bean.uploadMbps\n            down_mbps = bean.downloadMbps\n            if (bean.obfuscation.isNotBlank()) {\n                obfs = SingBoxOptions.Hysteria2Obfs().apply {\n                    type = \"salamander\"\n                    password = bean.obfuscation\n                }\n            }\n//            disable_mtu_discovery = bean.disableMtuDiscovery\n            password = bean.authPayload\n//            if (bean.streamReceiveWindow > 0) {\n//                recv_window_conn = bean.streamReceiveWindow.toLong()\n//            }\n//            if (bean.connectionReceiveWindow > 0) {\n//                recv_window_conn = bean.connectionReceiveWindow.toLong()\n//            }\n            tls = SingBoxOptions.OutboundTLSOptions().apply {\n                if (bean.sni.isNotBlank()) {\n                    server_name = bean.sni\n                }\n                alpn = listOf(\"h3\")\n                if (bean.caText.isNotBlank()) {\n                    certificate = bean.caText\n                }\n                insecure = bean.allowInsecure || DataStore.globalAllowInsecure\n                enabled = true\n            }\n        }\n\n        else -> error(\"error_version $bean.protocolVersion\")\n    }\n}\n\nfun hopPortsToSingboxList(s: String): List<String> {\n    return s.split(\",\").mapNotNull {\n        val pRange = it.replace(\"-\", \":\")\n        if (pRange.split(\":\").size == 2) {\n            pRange\n        } else {\n            null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/internal/ChainBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.internal;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class ChainBean extends InternalBean {\n\n    public List<Long> proxies;\n\n    @Override\n    public String displayName() {\n        if (JavaUtil.isNotBlank(name)) {\n            return name;\n        } else {\n            return \"Chain \" + Math.abs(hashCode());\n        }\n    }\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (name == null) name = \"\";\n\n        if (proxies == null) {\n            proxies = new ArrayList<>();\n        }\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(1);\n        output.writeInt(proxies.size());\n        for (Long proxy : proxies) {\n            output.writeLong(proxy);\n        }\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        if (version < 1) {\n            input.readString();\n            input.readInt();\n        }\n        int length = input.readInt();\n        proxies = new ArrayList<>();\n        for (int i = 0; i < length; i++) {\n            proxies.add(input.readLong());\n        }\n    }\n\n    @NotNull\n    @Override\n    public ChainBean clone() {\n        return KryoConverters.deserialize(new ChainBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<ChainBean> CREATOR = new CREATOR<ChainBean>() {\n        @NonNull\n        @Override\n        public ChainBean newInstance() {\n            return new ChainBean();\n        }\n\n        @Override\n        public ChainBean[] newArray(int size) {\n            return new ChainBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/internal/InternalBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.internal;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\n\npublic abstract class InternalBean extends AbstractBean {\n\n    @Override\n    public String displayAddress() {\n        return \"\";\n    }\n\n    @Override\n    public boolean canICMPing() {\n        return false;\n    }\n\n    @Override\n    public boolean canTCPing() {\n        return false;\n    }\n\n    @Override\n    public boolean canMapping() {\n        return false;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruBean.java",
    "content": "/******************************************************************************\n * Copyright (C) 2022 by nekohasekai <contact-git@sekai.icu>                  *\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 *                                                                            *\n ******************************************************************************/\n\npackage io.nekohasekai.sagernet.fmt.mieru;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class MieruBean extends AbstractBean {\n\n    public String protocol;\n    public String username;\n    public String password;\n    public Integer mtu;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (protocol == null) protocol = \"TCP\";\n        if (username == null) username = \"\";\n        if (password == null) password = \"\";\n        if (mtu == null) mtu = 1400;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeString(protocol);\n        output.writeString(username);\n        output.writeString(password);\n        if (protocol.equals(\"UDP\")) {\n            output.writeInt(mtu);\n        }\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        protocol = input.readString();\n        username = input.readString();\n        password = input.readString();\n        if (protocol.equals(\"UDP\")) {\n            mtu = input.readInt();\n        }\n    }\n\n    @NotNull\n    @Override\n    public MieruBean clone() {\n        return KryoConverters.deserialize(new MieruBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<MieruBean> CREATOR = new CREATOR<MieruBean>() {\n        @NonNull\n        @Override\n        public MieruBean newInstance() {\n            return new MieruBean();\n        }\n\n        @Override\n        public MieruBean[] newArray(int size) {\n            return new MieruBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/mieru/MieruFmt.kt",
    "content": "/******************************************************************************\n * Copyright (C) 2022 by nekohasekai <contact-git@sekai.icu>                  *\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 *                                                                            *\n ******************************************************************************/\n\npackage io.nekohasekai.sagernet.fmt.mieru\n\nimport io.nekohasekai.sagernet.ktx.toStringPretty\nimport org.json.JSONArray\nimport org.json.JSONObject\n\nfun MieruBean.buildMieruConfig(port: Int): String {\n    val serverInfo = JSONArray().apply {\n        put(JSONObject().apply {\n            put(\"ipAddress\", finalAddress)\n            put(\"portBindings\", JSONArray().apply {\n                put(JSONObject().apply {\n                    put(\"port\", finalPort)\n                    put(\"protocol\", protocol)\n                })\n            })\n        })\n    }\n    return JSONObject().apply {\n        put(\"activeProfile\", \"default\")\n        put(\"socks5Port\", port)\n        // TODO: follow NekoBox logging level.\n        put(\"loggingLevel\", \"INFO\")\n        put(\"profiles\", JSONArray().apply {\n            put(JSONObject().apply {\n                put(\"profileName\", \"default\")\n                put(\"user\", JSONObject().apply {\n                    put(\"name\", username)\n                    put(\"password\", password)\n                })\n                put(\"servers\", serverInfo)\n                put(\"mtu\", mtu)\n            })\n        })\n    }.toStringPretty()\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.naive;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class NaiveBean extends AbstractBean {\n\n    /**\n     * Available proto: https, quic.\n     */\n    public String proto;\n    public String username;\n    public String password;\n    public String extraHeaders;\n    public String sni;\n    public String certificates;\n    public Integer insecureConcurrency;\n\n    // sing-box socks\n    public Boolean sUoT;\n\n    @Override\n    public void initializeDefaultValues() {\n        if (serverPort == null) serverPort = 443;\n        super.initializeDefaultValues();\n        if (proto == null) proto = \"https\";\n        if (username == null) username = \"\";\n        if (password == null) password = \"\";\n        if (extraHeaders == null) extraHeaders = \"\";\n        if (certificates == null) certificates = \"\";\n        if (sni == null) sni = \"\";\n        if (insecureConcurrency == null) insecureConcurrency = 0;\n        if (sUoT == null) sUoT = false;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(3);\n        super.serialize(output);\n        output.writeString(proto);\n        output.writeString(username);\n        output.writeString(password);\n        // note: sequence is different from SagerNet,,,\n        output.writeString(extraHeaders);\n        output.writeString(certificates);\n        output.writeString(sni);\n        output.writeInt(insecureConcurrency);\n        output.writeBoolean(sUoT);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        proto = input.readString();\n        username = input.readString();\n        password = input.readString();\n        extraHeaders = input.readString();\n        if (version >= 2) {\n            certificates = input.readString();\n            sni = input.readString();\n        }\n        if (version >= 1) {\n            insecureConcurrency = input.readInt();\n        }\n        if (version >= 3) {\n            sUoT = input.readBoolean();\n        }\n    }\n\n    @NotNull\n    @Override\n    public NaiveBean clone() {\n        return KryoConverters.deserialize(new NaiveBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<NaiveBean> CREATOR = new CREATOR<NaiveBean>() {\n        @NonNull\n        @Override\n        public NaiveBean newInstance() {\n            return new NaiveBean();\n        }\n\n        @Override\n        public NaiveBean[] newArray(int size) {\n            return new NaiveBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/naive/NaiveFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.naive\n\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.LOCALHOST\nimport io.nekohasekai.sagernet.ktx.*\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.json.JSONObject\n\nfun parseNaive(link: String): NaiveBean {\n    val proto = link.substringAfter(\"+\").substringBefore(\":\")\n    val url = (\"https://\" + link.substringAfter(\"://\")).toHttpUrlOrNull()\n        ?: error(\"Invalid naive link: $link\")\n    return NaiveBean().also {\n        it.proto = proto\n    }.apply {\n        serverAddress = url.host\n        serverPort = url.port\n        username = url.username\n        password = url.password\n        sni = url.queryParameter(\"sni\")\n        certificates = url.queryParameter(\"cert\")\n        extraHeaders = url.queryParameter(\"extra-headers\")?.unUrlSafe()?.replace(\"\\r\\n\", \"\\n\")\n        insecureConcurrency = url.queryParameter(\"insecure-concurrency\")?.toIntOrNull()\n        name = url.fragment\n        initializeDefaultValues()\n    }\n}\n\nfun NaiveBean.toUri(proxyOnly: Boolean = false): String {\n    val builder = linkBuilder().host(finalAddress).port(finalPort)\n    if (username.isNotBlank()) {\n        builder.username(username)\n        if (password.isNotBlank()) {\n            builder.password(password)\n        }\n    }\n    if (!proxyOnly) {\n        if (sni.isNotBlank()) {\n            builder.addQueryParameter(\"sni\", sni)\n        }\n        if (certificates.isNotBlank()) {\n            builder.addQueryParameter(\"cert\", certificates)\n        }\n        if (extraHeaders.isNotBlank()) {\n            builder.addQueryParameter(\"extra-headers\", extraHeaders)\n        }\n        if (name.isNotBlank()) {\n            builder.encodedFragment(name.urlSafe())\n        }\n        if (insecureConcurrency > 0) {\n            builder.addQueryParameter(\"insecure-concurrency\", \"$insecureConcurrency\")\n        }\n    }\n    return builder.toLink(if (proxyOnly) proto else \"naive+$proto\", false)\n}\n\nfun NaiveBean.buildNaiveConfig(port: Int): String {\n    return JSONObject().apply {\n        // process ipv6\n        finalAddress = finalAddress.wrapIPV6Host()\n        serverAddress = serverAddress.wrapIPV6Host()\n\n        // process sni\n        if (sni.isNotBlank()) {\n            put(\"host-resolver-rules\", \"MAP $sni $finalAddress\")\n            finalAddress = sni\n        } else {\n            if (serverAddress.isIpAddress()) {\n                // for naive, using IP as SNI name hardly happens\n                // and host-resolver-rules cannot resolve the SNI problem\n                // so do nothing\n            } else {\n                put(\"host-resolver-rules\", \"MAP $serverAddress $finalAddress\")\n                finalAddress = serverAddress\n            }\n        }\n\n        put(\"listen\", \"socks://$LOCALHOST:$port\")\n        put(\"proxy\", toUri(true))\n        if (extraHeaders.isNotBlank()) {\n            put(\"extra-headers\", extraHeaders.split(\"\\n\").joinToString(\"\\r\\n\"))\n        }\n        if (DataStore.logLevel > 0) {\n            put(\"log\", \"\")\n        }\n        if (insecureConcurrency > 0) {\n            put(\"insecure-concurrency\", insecureConcurrency)\n        }\n    }.toStringPretty()\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.shadowsocks;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class ShadowsocksBean extends AbstractBean {\n\n    public String method;\n    public String password;\n    public String plugin;\n\n    public Boolean sUoT;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        if (JavaUtil.isNullOrBlank(method)) method = \"aes-256-gcm\";\n        if (method == null) method = \"\";\n        if (password == null) password = \"\";\n        if (plugin == null) plugin = \"\";\n        if (sUoT == null) sUoT = false;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(2);\n        super.serialize(output);\n        output.writeString(method);\n        output.writeString(password);\n        output.writeString(plugin);\n        output.writeBoolean(sUoT);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        method = input.readString();\n        password = input.readString();\n        plugin = input.readString();\n        sUoT = input.readBoolean();\n    }\n\n    @NotNull\n    @Override\n    public ShadowsocksBean clone() {\n        return KryoConverters.deserialize(new ShadowsocksBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<ShadowsocksBean> CREATOR = new CREATOR<ShadowsocksBean>() {\n        @NonNull\n        @Override\n        public ShadowsocksBean newInstance() {\n            return new ShadowsocksBean();\n        }\n\n        @Override\n        public ShadowsocksBean[] newArray(int size) {\n            return new ShadowsocksBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/shadowsocks/ShadowsocksFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.shadowsocks\n\nimport io.nekohasekai.sagernet.ktx.*\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.Util\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.json.JSONObject\n\nfun ShadowsocksBean.fixPluginName() {\n    if (plugin.startsWith(\"simple-obfs\")) {\n        plugin = plugin.replaceFirst(\"simple-obfs\", \"obfs-local\")\n    }\n}\n\nfun parseShadowsocks(url: String): ShadowsocksBean {\n\n    if (url.substringBefore(\"#\").contains(\"@\")) {\n        var link = url.replace(\"ss://\", \"https://\").toHttpUrlOrNull() ?: error(\n            \"invalid ss-android link $url\"\n        )\n\n        if (link.username.isBlank()) { // fix justmysocks's shit link\n            link = ((\"https://\" + url.substringAfter(\"ss://\")\n                .substringBefore(\"#\")\n                .decodeBase64UrlSafe()).toHttpUrlOrNull()\n                ?: error(\"invalid jms link $url\")\n                    ).newBuilder().fragment(url.substringAfter(\"#\")).build()\n        }\n\n        // ss-android style\n\n        if (link.password.isNotBlank()) {\n            return ShadowsocksBean().apply {\n                serverAddress = link.host\n                serverPort = link.port\n                method = link.username\n                password = link.password\n                plugin = link.queryParameter(\"plugin\") ?: \"\"\n                name = link.fragment\n                fixPluginName()\n            }\n        }\n\n        val methodAndPswd = link.username.decodeBase64UrlSafe()\n\n        return ShadowsocksBean().apply {\n            serverAddress = link.host\n            serverPort = link.port\n            method = methodAndPswd.substringBefore(\":\")\n            password = methodAndPswd.substringAfter(\":\")\n            plugin = link.queryParameter(\"plugin\") ?: \"\"\n            name = link.fragment\n            fixPluginName()\n        }\n    } else {\n        // v2rayN style\n        var v2Url = url\n\n        if (v2Url.contains(\"#\")) v2Url = v2Url.substringBefore(\"#\")\n\n        val link = (\"https://\" + v2Url.substringAfter(\"ss://\")\n            .decodeBase64UrlSafe()).toHttpUrlOrNull() ?: error(\"invalid v2rayN link $url\")\n\n        return ShadowsocksBean().apply {\n            serverAddress = link.host\n            serverPort = link.port\n            method = link.username\n            password = link.password\n            plugin = \"\"\n            val remarks = url.substringAfter(\"#\").unUrlSafe()\n            if (remarks.isNotBlank()) name = remarks\n        }\n    }\n\n}\n\nfun ShadowsocksBean.toUri(): String {\n\n    val builder = linkBuilder().username(Util.b64EncodeUrlSafe(\"$method:$password\"))\n        .host(serverAddress)\n        .port(serverPort)\n\n    if (plugin.isNotBlank()) {\n        builder.addQueryParameter(\"plugin\", plugin)\n    }\n\n    if (name.isNotBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n\n    return builder.toLink(\"ss\").replace(\"$serverPort/\", \"$serverPort\")\n\n}\n\nfun JSONObject.parseShadowsocks(): ShadowsocksBean {\n    return ShadowsocksBean().apply {\n        serverAddress = getStr(\"server\")\n        serverPort = getIntNya(\"server_port\")\n        password = getStr(\"password\")\n        method = getStr(\"method\")\n        name = optString(\"remarks\", \"\")\n\n        val pId = getStr(\"plugin\")\n        if (!pId.isNullOrBlank()) {\n            plugin = pId + \";\" + optString(\"plugin_opts\", \"\")\n        }\n    }\n}\n\nfun buildSingBoxOutboundShadowsocksBean(bean: ShadowsocksBean): SingBoxOptions.Outbound_ShadowsocksOptions {\n    return SingBoxOptions.Outbound_ShadowsocksOptions().apply {\n        type = \"shadowsocks\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        password = bean.password\n        method = bean.method\n        if (bean.plugin.isNotBlank()) {\n            plugin = bean.plugin.substringBefore(\";\")\n            plugin_opts = bean.plugin.substringAfter(\";\")\n            if (plugin == \"none\") {\n                plugin = null\n                plugin_opts = null\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.socks;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class SOCKSBean extends AbstractBean {\n\n    public Integer protocol;\n\n    public Boolean sUoT;\n\n    public int protocolVersion() {\n        switch (protocol) {\n            case 0:\n            case 1:\n                return 4;\n            default:\n                return 5;\n        }\n    }\n\n    public String protocolName() {\n        switch (protocol) {\n            case 0:\n                return \"SOCKS4\";\n            case 1:\n                return \"SOCKS4A\";\n            default:\n                return \"SOCKS5\";\n        }\n    }\n\n    public String protocolVersionName() {\n        switch (protocol) {\n            case 0:\n                return \"4\";\n            case 1:\n                return \"4a\";\n            default:\n                return \"5\";\n        }\n    }\n\n    public String username;\n    public String password;\n\n    public static final int PROTOCOL_SOCKS4 = 0;\n    public static final int PROTOCOL_SOCKS4A = 1;\n    public static final int PROTOCOL_SOCKS5 = 2;\n\n    @Override\n    public String network() {\n        if (protocol < PROTOCOL_SOCKS5) return \"tcp\";\n        return super.network();\n    }\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        if (protocol == null) protocol = PROTOCOL_SOCKS5;\n        if (username == null) username = \"\";\n        if (password == null) password = \"\";\n        if (sUoT == null) sUoT = false;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(2);\n        super.serialize(output);\n        output.writeInt(protocol);\n        output.writeString(username);\n        output.writeString(password);\n        output.writeBoolean(sUoT);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        if (version >= 1) {\n            protocol = input.readInt();\n        }\n        username = input.readString();\n        password = input.readString();\n        if (version >= 2) {\n            sUoT = input.readBoolean();\n        }\n    }\n\n    @NotNull\n    @Override\n    public SOCKSBean clone() {\n        return KryoConverters.deserialize(new SOCKSBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<SOCKSBean> CREATOR = new CREATOR<SOCKSBean>() {\n        @NonNull\n        @Override\n        public SOCKSBean newInstance() {\n            return new SOCKSBean();\n        }\n\n        @Override\n        public SOCKSBean[] newArray(int size) {\n            return new SOCKSBean[size];\n        }\n    };\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/socks/SOCKSFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.socks\n\nimport io.nekohasekai.sagernet.ktx.decodeBase64UrlSafe\nimport io.nekohasekai.sagernet.ktx.toLink\nimport io.nekohasekai.sagernet.ktx.unUrlSafe\nimport io.nekohasekai.sagernet.ktx.urlSafe\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.NGUtil\nimport moe.matsuri.nb4a.utils.Util\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nfun parseSOCKS(link: String): SOCKSBean {\n    val url = (\"http://\" + link.substringAfter(\"://\")).toHttpUrlOrNull()\n        ?: error(\"Not supported: $link\")\n\n    return SOCKSBean().apply {\n        protocol = when {\n            link.startsWith(\"socks4://\") -> SOCKSBean.PROTOCOL_SOCKS4\n            link.startsWith(\"socks4a://\") -> SOCKSBean.PROTOCOL_SOCKS4A\n            else -> SOCKSBean.PROTOCOL_SOCKS5\n        }\n        name = url.fragment\n        serverAddress = url.host\n        serverPort = url.port\n        username = url.username\n        password = url.password\n        // v2rayN fmt\n        if (password.isNullOrBlank() && !username.isNullOrBlank()) {\n            try {\n                val n = username.decodeBase64UrlSafe()\n                username = n.substringBefore(\":\")\n                password = n.substringAfter(\":\")\n            } catch (_: Exception) {\n            }\n        }\n    }\n}\n\nfun SOCKSBean.toUri(): String {\n\n    val builder = HttpUrl.Builder().scheme(\"http\").host(serverAddress).port(serverPort)\n    if (!username.isNullOrBlank()) builder.username(username)\n    if (!password.isNullOrBlank()) builder.password(password)\n    if (!name.isNullOrBlank()) builder.encodedFragment(name.urlSafe())\n    return builder.toLink(\"socks${protocolVersion()}\")\n\n}\n\nfun SOCKSBean.toV2rayN(): String {\n\n    var link = \"\"\n    if (username.isNotBlank()) {\n        link += username.urlSafe() + \":\" + password.urlSafe() + \"@\"\n    }\n    link += \"$serverAddress:$serverPort\"\n    link = \"socks://\" + NGUtil.encode(link)\n    if (name.isNotBlank()) {\n        link += \"#\" + name.urlSafe()\n    }\n\n    return link\n\n}\n\nfun buildSingBoxOutboundSocksBean(bean: SOCKSBean): SingBoxOptions.Outbound_SocksOptions {\n    return SingBoxOptions.Outbound_SocksOptions().apply {\n        type = \"socks\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        username = bean.username\n        password = bean.password\n        version = bean.protocolVersionName()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.ssh;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class SSHBean extends AbstractBean {\n\n    public static final int AUTH_TYPE_NONE = 0;\n    public static final int AUTH_TYPE_PASSWORD = 1;\n    public static final int AUTH_TYPE_PRIVATE_KEY = 2;\n\n    public String username;\n    public Integer authType;\n    public String password;\n    public String privateKey;\n    public String privateKeyPassphrase;\n    public String publicKey;\n\n    @Override\n    public void initializeDefaultValues() {\n        if (serverPort == null) serverPort = 22;\n\n        super.initializeDefaultValues();\n\n        if (username == null) username = \"root\";\n        if (authType == null) authType = AUTH_TYPE_PASSWORD;\n        if (password == null) password = \"\";\n        if (privateKey == null) privateKey = \"\";\n        if (privateKeyPassphrase == null) privateKeyPassphrase = \"\";\n        if (publicKey == null) publicKey = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeString(username);\n        output.writeInt(authType);\n        switch (authType) {\n            case AUTH_TYPE_NONE:\n                break;\n            case AUTH_TYPE_PASSWORD:\n                output.writeString(password);\n                break;\n            case AUTH_TYPE_PRIVATE_KEY:\n                output.writeString(privateKey);\n                output.writeString(privateKeyPassphrase);\n                break;\n        }\n        output.writeString(publicKey);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        username = input.readString();\n        authType = input.readInt();\n        switch (authType) {\n            case AUTH_TYPE_NONE:\n                break;\n            case AUTH_TYPE_PASSWORD:\n                password = input.readString();\n                break;\n            case AUTH_TYPE_PRIVATE_KEY:\n                privateKey = input.readString();\n                privateKeyPassphrase = input.readString();\n                break;\n        }\n        publicKey = input.readString();\n    }\n\n    @NotNull\n    @Override\n    public SSHBean clone() {\n        return KryoConverters.deserialize(new SSHBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<SSHBean> CREATOR = new CREATOR<SSHBean>() {\n        @NonNull\n        @Override\n        public SSHBean newInstance() {\n            return new SSHBean();\n        }\n\n        @Override\n        public SSHBean[] newArray(int size) {\n            return new SSHBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/ssh/SSHFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.ssh\n\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.listByLineOrComma\n\nfun buildSingBoxOutboundSSHBean(bean: SSHBean): SingBoxOptions.Outbound_SSHOptions {\n    return SingBoxOptions.Outbound_SSHOptions().apply {\n        type = \"ssh\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        user = bean.username\n        if (bean.publicKey.isNotBlank()) {\n            host_key = bean.publicKey.listByLineOrComma()\n        }\n        when (bean.authType) {\n            SSHBean.AUTH_TYPE_PRIVATE_KEY -> {\n                private_key = bean.privateKey\n                private_key_passphrase = bean.privateKeyPassphrase\n            }\n            else -> {\n                password = bean.password\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.trojan;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean;\n\npublic class TrojanBean extends StandardV2RayBean {\n\n    public String password;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (security == null || security.isEmpty()) security = \"tls\";\n        if (password == null) password = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(2);\n        super.serialize(output);\n        output.writeString(password);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        if (version >= 2) {\n            super.deserialize(input); // StandardV2RayBean\n            password = input.readString();\n        } else {\n            // From AbstractBean\n            serverAddress = input.readString();\n            serverPort = input.readInt();\n            // From TrojanBean\n            password = input.readString();\n            security = input.readString();\n            sni = input.readString();\n            alpn = input.readString();\n            if (version == 1) allowInsecure = input.readBoolean();\n        }\n    }\n\n    @NotNull\n    @Override\n    public TrojanBean clone() {\n        return KryoConverters.deserialize(new TrojanBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<TrojanBean> CREATOR = new CREATOR<TrojanBean>() {\n        @NonNull\n        @Override\n        public TrojanBean newInstance() {\n            return new TrojanBean();\n        }\n\n        @Override\n        public TrojanBean[] newArray(int size) {\n            return new TrojanBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/trojan/TrojanFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.trojan\n\nimport io.nekohasekai.sagernet.fmt.v2ray.parseDuckSoft\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nfun parseTrojan(server: String): TrojanBean {\n\n    val link = server.replace(\"trojan://\", \"https://\").toHttpUrlOrNull()\n        ?: error(\"invalid trojan link $server\")\n\n    return TrojanBean().apply {\n        parseDuckSoft(link)\n        link.queryParameter(\"allowInsecure\")\n            ?.apply { if (this == \"1\" || this == \"true\") allowInsecure = true }\n        link.queryParameter(\"peer\")?.apply { if (this.isNotBlank()) sni = this }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.trojan_go;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class TrojanGoBean extends AbstractBean {\n\n    /**\n     * Trojan 的密码。\n     * 不可省略，不能为空字符串，不建议含有非 ASCII 可打印字符。\n     * 必须使用 encodeURIComponent 编码。\n     */\n    public String password;\n\n    /**\n     * 自定义 TLS 的 SNI。\n     * 省略时默认与 trojan-host 同值。不得为空字符串。\n     * <p>\n     * 必须使用 encodeURIComponent 编码。\n     */\n    public String sni;\n\n    /**\n     * 传输类型。\n     * 省略时默认为 original，但不可为空字符串。\n     * 目前可选值只有 original 和 ws，未来可能会有 h2、h2+ws 等取值。\n     * <p>\n     * 当取值为 original 时，使用原始 Trojan 传输方式，无法方便通过 CDN。\n     * 当取值为 ws 时，使用 wss 作为传输层。\n     */\n    public String type;\n\n    /**\n     * 自定义 HTTP Host 头。\n     * 可以省略，省略时值同 trojan-host。\n     * 可以为空字符串，但可能带来非预期情形。\n     * <p>\n     * 警告：若你的端口非标准端口（不是 80 / 443），RFC 标准规定 Host 应在主机名后附上端口号，例如 example.com:44333。至于是否遵守，请自行斟酌。\n     * <p>\n     * 必须使用 encodeURIComponent 编码。\n     */\n    public String host;\n\n    /**\n     * 当传输类型 type 取 ws、h2、h2+ws 时，此项有效。\n     * 不可省略，不可为空。\n     * 必须以 / 开头。\n     * 可以使用 URL 中的 & # ? 等字符，但应当是合法的 URL 路径。\n     * <p>\n     * 必须使用 encodeURIComponent 编码。\n     */\n    public String path;\n\n    /**\n     * 用于保证 Trojan 流量密码学安全的加密层。\n     * 可省略，默认为 none，即不使用加密。\n     * 不可以为空字符串。\n     * <p>\n     * 必须使用 encodeURIComponent 编码。\n     * <p>\n     * 使用 Shadowsocks 算法进行流量加密时，其格式为：\n     * <p>\n     * ss;method:password\n     * <p>\n     * 其中 ss 是固定内容，method 是加密方法，必须为下列之一：\n     * <p>\n     * aes-128-gcm\n     * aes-256-gcm\n     * chacha20-ietf-poly1305\n     */\n    public String encryption;\n\n    /**\n     * 额外的插件选项。本字段保留。\n     * 可省略，但不可以为空字符串。\n     */\n    // not used in NB4A\n    public String plugin;\n\n    // ---\n\n    public Boolean allowInsecure;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        if (password == null) password = \"\";\n        if (sni == null) sni = \"\";\n        if (JavaUtil.isNullOrBlank(type)) type = \"original\";\n        if (host == null) host = \"\";\n        if (path == null) path = \"\";\n        if (JavaUtil.isNullOrBlank(encryption)) encryption = \"none\";\n        if (plugin == null) plugin = \"\";\n        if (allowInsecure == null) allowInsecure = false;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(1);\n        super.serialize(output);\n        output.writeString(password);\n        output.writeString(sni);\n        output.writeString(type);\n        //noinspection SwitchStatementWithTooFewBranches\n        switch (type) {\n            case \"ws\": {\n                output.writeString(host);\n                output.writeString(path);\n                break;\n            }\n        }\n        output.writeString(encryption);\n        output.writeString(plugin);\n        output.writeBoolean(allowInsecure);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n\n        password = input.readString();\n        sni = input.readString();\n        type = input.readString();\n        //noinspection SwitchStatementWithTooFewBranches\n        switch (type) {\n            case \"ws\": {\n                host = input.readString();\n                path = input.readString();\n                break;\n            }\n        }\n        encryption = input.readString();\n        plugin = input.readString();\n        if (version >= 1) {\n            allowInsecure = input.readBoolean();\n        }\n    }\n\n    @NotNull\n    @Override\n    public TrojanGoBean clone() {\n        return KryoConverters.deserialize(new TrojanGoBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<TrojanGoBean> CREATOR = new CREATOR<TrojanGoBean>() {\n        @NonNull\n        @Override\n        public TrojanGoBean newInstance() {\n            return new TrojanGoBean();\n        }\n\n        @Override\n        public TrojanGoBean[] newArray(int size) {\n            return new TrojanGoBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/trojan_go/TrojanGoFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.trojan_go\n\nimport io.nekohasekai.sagernet.IPv6Mode\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.LOCALHOST\nimport io.nekohasekai.sagernet.ktx.*\nimport moe.matsuri.nb4a.Protocols\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport org.json.JSONArray\nimport org.json.JSONObject\n\nfun parseTrojanGo(server: String): TrojanGoBean {\n    val link = server.replace(\"trojan-go://\", \"https://\").toHttpUrlOrNull() ?: error(\n        \"invalid trojan-link link $server\"\n    )\n    return TrojanGoBean().apply {\n        serverAddress = link.host\n        serverPort = link.port\n        password = link.username\n        link.queryParameter(\"sni\")?.let {\n            sni = it\n        }\n        link.queryParameter(\"type\")?.let { lType ->\n            type = lType\n\n            when (type) {\n                \"ws\" -> {\n                    link.queryParameter(\"host\")?.let {\n                        host = it\n                    }\n                    link.queryParameter(\"path\")?.let {\n                        path = it\n                    }\n                }\n                else -> {\n                }\n            }\n        }\n        link.queryParameter(\"encryption\")?.let {\n            encryption = it\n        }\n        link.queryParameter(\"plugin\")?.let {\n            plugin = it\n        }\n        link.fragment.takeIf { !it.isNullOrBlank() }?.let {\n            name = it\n        }\n    }\n}\n\nfun TrojanGoBean.toUri(): String {\n    val builder = linkBuilder().username(password).host(serverAddress).port(serverPort)\n    if (sni.isNotBlank()) {\n        builder.addQueryParameter(\"sni\", sni)\n    }\n    if (type.isNotBlank() && type != \"original\") {\n        builder.addQueryParameter(\"type\", type)\n\n        when (type) {\n            \"ws\" -> {\n                if (host.isNotBlank()) {\n                    builder.addQueryParameter(\"host\", host)\n                }\n                if (path.isNotBlank()) {\n                    builder.addQueryParameter(\"path\", path)\n                }\n            }\n        }\n    }\n    if (type.isNotBlank() && type != \"none\") {\n        builder.addQueryParameter(\"encryption\", encryption)\n    }\n    if (plugin.isNotBlank()) {\n        builder.addQueryParameter(\"plugin\", plugin)\n    }\n\n    if (name.isNotBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n\n    return builder.toLink(\"trojan-go\")\n}\n\nfun TrojanGoBean.buildTrojanGoConfig(port: Int): String {\n    return JSONObject().apply {\n        put(\"run_type\", \"client\")\n        put(\"local_addr\", LOCALHOST)\n        put(\"local_port\", port)\n        put(\"remote_addr\", finalAddress)\n        put(\"remote_port\", finalPort)\n        put(\"password\", JSONArray().apply {\n            put(password)\n        })\n        put(\"log_level\", if (DataStore.logLevel > 0) 0 else 2)\n//        if (Protocols.shouldEnableMux(\"trojan-go\")) put(\"mux\", JSONObject().apply {\n//            put(\"enabled\", true)\n//            put(\"concurrency\", DataStore.muxConcurrency)\n//        })\n        put(\"tcp\", JSONObject().apply {\n            put(\"prefer_ipv4\", DataStore.ipv6Mode <= IPv6Mode.ENABLE)\n        })\n\n        when (type) {\n            \"original\" -> {\n            }\n            \"ws\" -> put(\"websocket\", JSONObject().apply {\n                put(\"enabled\", true)\n                put(\"host\", host)\n                put(\"path\", path)\n            })\n        }\n\n        if (sni.isBlank() && finalAddress == LOCALHOST && !serverAddress.isIpAddress()) {\n            sni = serverAddress\n        }\n\n        put(\"ssl\", JSONObject().apply {\n            if (sni.isNotBlank()) put(\"sni\", sni)\n            if (allowInsecure) put(\"verify\", false)\n        })\n\n        when {\n            encryption == \"none\" -> {\n            }\n            encryption.startsWith(\"ss;\") -> put(\"shadowsocks\", JSONObject().apply {\n                put(\"enabled\", true)\n                put(\"method\", encryption.substringAfter(\";\").substringBefore(\":\"))\n                put(\"password\", encryption.substringAfter(\":\"))\n            })\n        }\n    }.toStringPretty()\n}\n\nfun JSONObject.parseTrojanGo(): TrojanGoBean {\n    return TrojanGoBean().applyDefaultValues().apply {\n        serverAddress = optString(\"remote_addr\", serverAddress)\n        serverPort = optInt(\"remote_port\", serverPort)\n        when (val pass = get(\"password\")) {\n            is String -> {\n                password = pass\n            }\n            is List<*> -> {\n                password = pass[0] as String\n            }\n        }\n        optJSONArray(\"ssl\")?.apply {\n            sni = optString(\"sni\", sni)\n        }\n        optJSONArray(\"websocket\")?.apply {\n            if (optBoolean(\"enabled\", false)) {\n                type = \"ws\"\n                host = optString(\"host\", host)\n                path = optString(\"path\", path)\n            }\n        }\n        optJSONArray(\"shadowsocks\")?.apply {\n            if (optBoolean(\"enabled\", false)) {\n                encryption = \"ss;${optString(\"method\", \"\")}:${optString(\"password\", \"\")}\"\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.tuic;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class TuicBean extends AbstractBean {\n\n    public String token;\n    public String caText;\n    public String udpRelayMode;\n    public String congestionController;\n    public String alpn;\n    public Boolean disableSNI;\n    public Boolean reduceRTT;\n    public Integer mtu;\n    public String sni;\n\n    // TUIC zep\n\n    public Boolean fastConnect;\n    public Boolean allowInsecure;\n\n    // TUIC v5\n\n    public String customJSON;\n    public Integer protocolVersion;\n    public String uuid;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (token == null) token = \"\";\n        if (caText == null) caText = \"\";\n        if (udpRelayMode == null) udpRelayMode = \"native\";\n        if (congestionController == null) congestionController = \"cubic\";\n        if (alpn == null) alpn = \"\";\n        if (disableSNI == null) disableSNI = false;\n        if (reduceRTT == null) reduceRTT = false;\n        if (mtu == null) mtu = 1400;\n        if (sni == null) sni = \"\";\n        if (fastConnect == null) fastConnect = false;\n        if (allowInsecure == null) allowInsecure = false;\n        if (customJSON == null) customJSON = \"\";\n        if (protocolVersion == null) protocolVersion = 5;\n        if (uuid == null) uuid = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(2);\n        super.serialize(output);\n        output.writeString(token);\n        output.writeString(caText);\n        output.writeString(udpRelayMode);\n        output.writeString(congestionController);\n        output.writeString(alpn);\n        output.writeBoolean(disableSNI);\n        output.writeBoolean(reduceRTT);\n        output.writeInt(mtu);\n        output.writeString(sni);\n        output.writeBoolean(fastConnect);\n        output.writeBoolean(allowInsecure);\n        output.writeString(customJSON);\n        output.writeInt(protocolVersion);\n        output.writeString(uuid);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        token = input.readString();\n        caText = input.readString();\n        udpRelayMode = input.readString();\n        congestionController = input.readString();\n        alpn = input.readString();\n        disableSNI = input.readBoolean();\n        reduceRTT = input.readBoolean();\n        mtu = input.readInt();\n        sni = input.readString();\n        if (version >= 1) {\n            fastConnect = input.readBoolean();\n            allowInsecure = input.readBoolean();\n        }\n        if (version >= 2) {\n            customJSON = input.readString();\n            protocolVersion = input.readInt();\n            uuid = input.readString();\n        } else {\n            protocolVersion = 4;\n        }\n    }\n\n    @Override\n    public boolean canTCPing() {\n        return false;\n    }\n\n    @NotNull\n    @Override\n    public TuicBean clone() {\n        return KryoConverters.deserialize(new TuicBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<TuicBean> CREATOR = new CREATOR<TuicBean>() {\n        @NonNull\n        @Override\n        public TuicBean newInstance() {\n            return new TuicBean();\n        }\n\n        @Override\n        public TuicBean[] newArray(int size) {\n            return new TuicBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/tuic/TuicFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.tuic\n\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.linkBuilder\nimport io.nekohasekai.sagernet.ktx.toLink\nimport io.nekohasekai.sagernet.ktx.urlSafe\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.listByLineOrComma\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nfun parseTuic(url: String): TuicBean {\n    // https://github.com/daeuniverse/dae/discussions/182\n    val link = url.replace(\"tuic://\", \"https://\").toHttpUrlOrNull() ?: error(\n        \"invalid tuic link $url\"\n    )\n    return TuicBean().apply {\n        protocolVersion = 5\n\n        name = link.fragment\n        serverAddress = link.host\n        serverPort = link.port\n\n        val rawUser = link.username\n        val rawPass = link.password\n\n        if (rawUser.contains(\":\")) {\n            val parts = rawUser.split(\":\", limit = 2)\n            uuid = parts[0]\n            token = parts.getOrElse(1) { \"\" }\n        } else {\n            uuid = rawUser\n            token = rawPass\n        }\n\n        link.queryParameter(\"sni\")?.let {\n            sni = it\n        }\n        link.queryParameter(\"congestion_control\")?.let {\n            congestionController = it\n        }\n        link.queryParameter(\"udp_relay_mode\")?.let {\n            udpRelayMode = it\n        }\n        link.queryParameter(\"alpn\")?.let {\n            alpn = it\n        }\n        link.queryParameter(\"allow_insecure\")?.let {\n            if (it == \"1\") allowInsecure = true\n        }\n        link.queryParameter(\"disable_sni\")?.let {\n            if (it == \"1\") disableSNI = true\n        }\n    }\n}\n\nfun TuicBean.toUri(): String {\n    val builder = linkBuilder().username(uuid).password(token).host(serverAddress).port(serverPort)\n\n    builder.addQueryParameter(\"congestion_control\", congestionController)\n    builder.addQueryParameter(\"udp_relay_mode\", udpRelayMode)\n\n    if (sni.isNotBlank()) builder.addQueryParameter(\"sni\", sni)\n    if (alpn.isNotBlank()) builder.addQueryParameter(\"alpn\", alpn)\n    if (allowInsecure) builder.addQueryParameter(\"allow_insecure\", \"1\")\n    if (disableSNI) builder.addQueryParameter(\"disable_sni\", \"1\")\n    if (name.isNotBlank()) builder.encodedFragment(name.urlSafe())\n\n    return builder.toLink(\"tuic\")\n}\n\nfun buildSingBoxOutboundTuicBean(bean: TuicBean): SingBoxOptions.Outbound_TUICOptions {\n    if (bean.protocolVersion == 4) throw Exception(\"TUIC v4 is no longer supported\")\n    return SingBoxOptions.Outbound_TUICOptions().apply {\n        type = \"tuic\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        uuid = bean.uuid\n        password = bean.token\n        congestion_control = bean.congestionController\n        when (bean.udpRelayMode) {\n            \"quic\" -> udp_relay_mode = \"quic\"\n        }\n        zero_rtt_handshake = bean.reduceRTT\n        tls = SingBoxOptions.OutboundTLSOptions().apply {\n            if (bean.sni.isNotBlank()) {\n                server_name = bean.sni\n            }\n            if (bean.alpn.isNotBlank()) {\n                alpn = bean.alpn.listByLineOrComma()\n            }\n            if (bean.caText.isNotBlank()) {\n                certificate = bean.caText\n            }\n            disable_sni = bean.disableSNI\n            insecure = bean.allowInsecure || DataStore.globalAllowInsecure\n            enabled = true\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/StandardV2RayBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.v2ray;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic abstract class StandardV2RayBean extends AbstractBean {\n\n    public String uuid;\n    public String encryption; // or VLESS flow\n\n    //////// End of VMess & VLESS ////////\n\n    // \"V2Ray Transport\" tcp/http/ws/quic/grpc/httpupgrade\n    public String type;\n\n    public String host;\n\n    public String path;\n\n    // --------------------------------------- tls?\n\n    public String security;\n\n    public String sni;\n\n    public String alpn;\n\n    public String utlsFingerprint;\n\n    public Boolean allowInsecure;\n\n    // --------------------------------------- reality\n\n\n    public String realityPubKey;\n\n    public String realityShortId;\n\n\n    // --------------------------------------- //\n\n    public Integer wsMaxEarlyData;\n    public String earlyDataHeaderName;\n\n    public String certificates;\n\n    // --------------------------------------- ech\n\n    public Boolean enableECH;\n\n    public String echConfig;\n\n    // --------------------------------------- Mux\n\n    public Boolean enableMux;\n    public Boolean muxPadding;\n    public Integer muxType;\n    public Integer muxConcurrency;\n\n\n    // --------------------------------------- //\n\n    public Integer packetEncoding; // 1:packet 2:xudp\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        if (JavaUtil.isNullOrBlank(uuid)) uuid = \"\";\n\n        if (JavaUtil.isNullOrBlank(type)) type = \"tcp\";\n        else if (\"h2\".equals(type)) type = \"http\";\n\n        type = type.toLowerCase();\n\n        if (JavaUtil.isNullOrBlank(host)) host = \"\";\n        if (JavaUtil.isNullOrBlank(path)) path = \"\";\n\n        if (JavaUtil.isNullOrBlank(security)) {\n            if (this instanceof TrojanBean) {\n                security = \"tls\";\n            } else {\n                security = \"none\";\n            }\n        }\n        if (JavaUtil.isNullOrBlank(sni)) sni = \"\";\n        if (JavaUtil.isNullOrBlank(alpn)) alpn = \"\";\n\n        if (JavaUtil.isNullOrBlank(certificates)) certificates = \"\";\n        if (JavaUtil.isNullOrBlank(earlyDataHeaderName)) earlyDataHeaderName = \"\";\n        if (JavaUtil.isNullOrBlank(utlsFingerprint)) utlsFingerprint = \"\";\n\n        if (wsMaxEarlyData == null) wsMaxEarlyData = 0;\n        if (allowInsecure == null) allowInsecure = false;\n        if (packetEncoding == null) packetEncoding = 0;\n\n        if (realityPubKey == null) realityPubKey = \"\";\n        if (realityShortId == null) realityShortId = \"\";\n\n        if (enableECH == null) enableECH = false;\n        if (JavaUtil.isNullOrBlank(echConfig)) echConfig = \"\";\n\n        if (enableMux == null) enableMux = false;\n        if (muxPadding == null) muxPadding = false;\n        if (muxType == null) muxType = 0;\n        if (muxConcurrency == null) muxConcurrency = 1;\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(4);\n        super.serialize(output);\n        output.writeString(uuid);\n        output.writeString(encryption);\n        if (this instanceof VMessBean) {\n            output.writeInt(((VMessBean) this).alterId);\n        }\n\n        output.writeString(type);\n        switch (type) {\n            case \"tcp\":\n            case \"quic\": {\n                break;\n            }\n            case \"ws\": {\n                output.writeString(host);\n                output.writeString(path);\n                output.writeInt(wsMaxEarlyData);\n                output.writeString(earlyDataHeaderName);\n                break;\n            }\n            case \"http\":\n            case \"httpupgrade\": {\n                output.writeString(host);\n                output.writeString(path);\n                break;\n            }\n            case \"grpc\": {\n                output.writeString(path);\n                break;\n            }\n        }\n\n        output.writeString(security);\n        if (\"tls\".equals(security)) {\n            output.writeString(sni);\n            output.writeString(alpn);\n            output.writeString(certificates);\n            output.writeBoolean(allowInsecure);\n            output.writeString(utlsFingerprint);\n            output.writeString(realityPubKey);\n            output.writeString(realityShortId);\n        }\n\n        output.writeBoolean(enableECH);\n        output.writeString(echConfig);\n\n        output.writeInt(packetEncoding);\n\n        output.writeBoolean(enableMux);\n        output.writeBoolean(muxPadding);\n        output.writeInt(muxType);\n        output.writeInt(muxConcurrency);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        uuid = input.readString();\n        encryption = input.readString();\n        if (this instanceof VMessBean) {\n            ((VMessBean) this).alterId = input.readInt();\n        }\n\n        type = input.readString();\n        switch (type) {\n            case \"tcp\":\n            case \"quic\": {\n                break;\n            }\n            case \"ws\": {\n                host = input.readString();\n                path = input.readString();\n                wsMaxEarlyData = input.readInt();\n                earlyDataHeaderName = input.readString();\n                break;\n            }\n            case \"http\":\n            case \"httpupgrade\": {\n                host = input.readString();\n                path = input.readString();\n                break;\n            }\n            case \"grpc\": {\n                path = input.readString();\n                if (version < 4) {\n                    // 解决老版本数据的读取问题\n                    input.readString();\n                    input.readString();\n                }\n                break;\n            }\n        }\n\n        security = input.readString();\n        if (\"tls\".equals(security)) {\n            sni = input.readString();\n            alpn = input.readString();\n            certificates = input.readString();\n            allowInsecure = input.readBoolean();\n            utlsFingerprint = input.readString();\n            realityPubKey = input.readString();\n            realityShortId = input.readString();\n        }\n\n        if (version >= 1) {\n            enableECH = input.readBoolean();\n            if (version >= 3) {\n                echConfig = input.readString();\n            } else {\n                if (enableECH) {\n                    input.readBoolean();\n                    input.readBoolean();\n                    echConfig = input.readString();\n                }\n            }\n        } else if (version == 0) {\n            // 从老版本升级上来但是 version == 0, 可能有 enableECH 也可能没有，需要做判断\n            int position = input.getByteBuffer().position(); // 当前位置\n\n            boolean tmpEnableECH = input.readBoolean();\n            int tmpPacketEncoding = input.readInt();\n\n            input.setPosition(position); // 读后归位\n\n            if (tmpPacketEncoding != 1 && tmpPacketEncoding != 2) {\n                enableECH = tmpEnableECH;\n                if (enableECH) {\n                    input.readBoolean();\n                    input.readBoolean();\n                    echConfig = input.readString();\n                }\n            } // 否则后一位就是 packetEncoding\n        }\n\n        packetEncoding = input.readInt();\n\n        if (version >= 2) {\n            enableMux = input.readBoolean();\n            muxPadding = input.readBoolean();\n            muxType = input.readInt();\n            muxConcurrency = input.readInt();\n        }\n    }\n\n    public boolean isVLESS() {\n        if (this instanceof VMessBean) {\n            Integer aid = ((VMessBean) this).alterId;\n            return aid != null && aid == -1;\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/V2RayFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.v2ray\n\nimport android.text.TextUtils\nimport com.google.gson.Gson\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\nimport io.nekohasekai.sagernet.ktx.*\nimport moe.matsuri.nb4a.SingBoxOptions.*\nimport moe.matsuri.nb4a.utils.NGUtil\nimport moe.matsuri.nb4a.utils.listByLineOrComma\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport org.json.JSONObject\n\ndata class VmessQRCode(\n    var v: String = \"\",\n    var ps: String = \"\",\n    var add: String = \"\",\n    var port: String = \"\",\n    var id: String = \"\",\n    var aid: String = \"0\",\n    var scy: String = \"\",\n    var net: String = \"\",\n    var type: String = \"\",\n    var host: String = \"\",\n    var path: String = \"\",\n    var tls: String = \"\",\n    var sni: String = \"\",\n    var alpn: String = \"\",\n    var fp: String = \"\",\n)\n\nfun StandardV2RayBean.isTLS(): Boolean {\n    return security == \"tls\"\n}\n\nfun StandardV2RayBean.setTLS(boolean: Boolean) {\n    security = if (boolean) \"tls\" else \"\"\n}\n\nfun parseV2Ray(link: String): StandardV2RayBean {\n    // Try parse stupid formats first\n\n    if (!link.contains(\"?\")) {\n        try {\n            return parseV2RayN(link)\n        } catch (e: Exception) {\n            Logs.i(\"try v2rayN: \" + e.readableMessage)\n        }\n    }\n\n    try {\n        return tryResolveVmess4Kitsunebi(link)\n    } catch (e: Exception) {\n        Logs.i(\"try Kitsunebi: \" + e.readableMessage)\n    }\n\n    // \"std\" format\n\n    val bean = VMessBean().apply { if (link.startsWith(\"vless://\")) alterId = -1 }\n    val url = link.replace(\"vmess://\", \"https://\").replace(\"vless://\", \"https://\").toHttpUrl()\n\n    if (url.password.isNotBlank()) {\n        // https://github.com/v2fly/v2fly-github-io/issues/26 (rarely use)\n        bean.serverAddress = url.host\n        bean.serverPort = url.port\n        bean.name = url.fragment\n\n        var protocol = url.username\n        bean.type = protocol\n        bean.alterId = url.password.substringAfterLast('-').toInt()\n        bean.uuid = url.password.substringBeforeLast('-')\n\n        if (protocol.endsWith(\"+tls\")) {\n            bean.security = \"tls\"\n            protocol = protocol.substring(0, protocol.length - 4)\n\n            url.queryParameter(\"tlsServerName\")?.let {\n                if (it.isNotBlank()) {\n                    bean.sni = it\n                }\n            }\n        }\n\n        when (protocol) {\n//            \"tcp\" -> {\n//                url.queryParameter(\"type\")?.let { type ->\n//                    if (type == \"http\") {\n//                        bean.headerType = \"http\"\n//                        url.queryParameter(\"host\")?.let {\n//                            bean.host = it\n//                        }\n//                    }\n//                }\n//            }\n            \"http\" -> {\n                url.queryParameter(\"path\")?.let {\n                    bean.path = it\n                }\n                url.queryParameter(\"host\")?.let {\n                    bean.host = it.split(\"|\").joinToString(\",\")\n                }\n            }\n\n            \"ws\" -> {\n                url.queryParameter(\"path\")?.let {\n                    bean.path = it\n                }\n                url.queryParameter(\"host\")?.let {\n                    bean.host = it\n                }\n            }\n\n            \"grpc\" -> {\n                url.queryParameter(\"serviceName\")?.let {\n                    bean.path = it\n                }\n            }\n\n            \"httpupgrade\" -> {\n                url.queryParameter(\"path\")?.let {\n                    bean.path = it\n                }\n                url.queryParameter(\"host\")?.let {\n                    bean.host = it\n                }\n            }\n        }\n    } else {\n        // also vless format\n        bean.parseDuckSoft(url)\n    }\n\n    return bean\n}\n\n// https://github.com/XTLS/Xray-core/issues/91\nfun StandardV2RayBean.parseDuckSoft(url: HttpUrl) {\n    serverAddress = url.host\n    serverPort = url.port\n    name = url.fragment\n\n    if (this is TrojanBean) {\n        password = url.username\n    } else {\n        uuid = url.username\n    }\n\n    // not ducksoft fmt path\n    if (url.pathSegments.size > 1 || url.pathSegments[0].isNotBlank()) {\n        path = url.pathSegments.joinToString(\"/\")\n    }\n\n    type = url.queryParameter(\"type\") ?: \"tcp\"\n    if (type == \"h2\" || url.queryParameter(\"headerType\") == \"http\") type = \"http\"\n\n    security = url.queryParameter(\"security\")\n    if (security.isNullOrBlank()) {\n        security = if (this is TrojanBean) \"tls\" else \"none\"\n    }\n\n    when (security) {\n        \"tls\", \"reality\" -> {\n            security = \"tls\"\n            url.queryParameter(\"allowInsecure\")?.let {\n                allowInsecure = it == \"1\" || it == \"true\"\n            }\n            url.queryParameter(\"sni\")?.let {\n                sni = it\n            }\n            url.queryParameter(\"host\")?.let {\n                if (sni.isNullOrBlank()) sni = it\n            }\n            url.queryParameter(\"alpn\")?.let {\n                alpn = it\n            }\n            url.queryParameter(\"cert\")?.let {\n                certificates = it\n            }\n            url.queryParameter(\"pbk\")?.let {\n                realityPubKey = it\n            }\n            url.queryParameter(\"sid\")?.let {\n                realityShortId = it\n            }\n        }\n    }\n\n    when (type) {\n        \"http\" -> {\n            url.queryParameter(\"host\")?.let {\n                host = it\n            }\n            url.queryParameter(\"path\")?.let {\n                path = it\n            }\n        }\n\n        \"ws\" -> {\n            url.queryParameter(\"host\")?.let {\n                host = it\n            }\n            url.queryParameter(\"path\")?.let {\n                path = it\n            }\n            url.queryParameter(\"ed\")?.let { ed ->\n                wsMaxEarlyData = ed.toInt()\n\n                url.queryParameter(\"eh\")?.let {\n                    earlyDataHeaderName = it\n                }\n            }\n        }\n\n        \"grpc\" -> {\n            url.queryParameter(\"serviceName\")?.let {\n                path = it\n            }\n        }\n\n        \"httpupgrade\" -> {\n            url.queryParameter(\"host\")?.let {\n                host = it\n            }\n            url.queryParameter(\"path\")?.let {\n                path = it\n            }\n        }\n    }\n\n    // maybe from matsuri vmess exoprt\n    if (this is VMessBean && !isVLESS) {\n        url.queryParameter(\"encryption\")?.let {\n            encryption = it\n        }\n    }\n\n    url.queryParameter(\"packetEncoding\")?.let {\n        when (it) {\n            \"packet\" -> packetEncoding = 1\n            \"xudp\" -> packetEncoding = 2\n        }\n    }\n\n    url.queryParameter(\"flow\")?.let {\n        if (isVLESS) {\n            encryption = it.removeSuffix(\"-udp443\")\n        }\n    }\n\n    url.queryParameter(\"fp\")?.let {\n        utlsFingerprint = it\n    }\n}\n\n// 不确定是谁的格式\nprivate fun tryResolveVmess4Kitsunebi(server: String): VMessBean {\n    // vmess://YXV0bzo1YWY1ZDBlYy02ZWEwLTNjNDMtOTNkYi1jYTMwMDg1MDNiZGJAMTgzLjIzMi41Ni4xNjE6MTIwMg\n    // ?remarks=*%F0%9F%87%AF%F0%9F%87%B5JP%20-355%20TG@moon365free&obfsParam=%7B%22Host%22:%22183.232.56.161%22%7D&path=/v2ray&obfs=websocket&alterId=0\n\n    var result = server.replace(\"vmess://\", \"\")\n    val indexSplit = result.indexOf(\"?\")\n    if (indexSplit > 0) {\n        result = result.substring(0, indexSplit)\n    }\n    result = NGUtil.decode(result)\n\n    val arr1 = result.split('@')\n    if (arr1.count() != 2) {\n        throw IllegalStateException(\"invalid kitsunebi format\")\n    }\n    val arr21 = arr1[0].split(':')\n    val arr22 = arr1[1].split(':')\n    if (arr21.count() != 2) {\n        throw IllegalStateException(\"invalid kitsunebi format\")\n    }\n\n    return VMessBean().apply {\n        serverAddress = arr22[0]\n        serverPort = NGUtil.parseInt(arr22[1])\n        uuid = arr21[1]\n        encryption = arr21[0]\n        if (indexSplit < 0) return@apply\n\n        val url = (\"https://localhost/path?\" + server.substringAfter(\"?\")).toHttpUrl()\n        url.queryParameter(\"remarks\")?.apply { name = this }\n        url.queryParameter(\"alterId\")?.apply { alterId = this.toInt() }\n        url.queryParameter(\"path\")?.apply { path = this }\n        url.queryParameter(\"tls\")?.apply { security = \"tls\" }\n        url.queryParameter(\"allowInsecure\")\n            ?.apply { if (this == \"1\" || this == \"true\") allowInsecure = true }\n        url.queryParameter(\"obfs\")?.apply {\n            type = this.replace(\"websocket\", \"ws\").replace(\"none\", \"tcp\")\n            if (type == \"ws\") {\n                url.queryParameter(\"obfsParam\")?.apply {\n                    if (this.startsWith(\"{\")) {\n                        host = JSONObject(this).getStr(\"Host\")\n                    } else if (security == \"tls\") {\n                        sni = this\n                    }\n                }\n            }\n        }\n    }\n}\n\n// SagerNet's\n// Do not support some format and then throw exception\nfun parseV2RayN(link: String): VMessBean {\n    val result = link.substringAfter(\"vmess://\").decodeBase64UrlSafe()\n    if (result.contains(\"= vmess\")) {\n        return parseCsvVMess(result)\n    }\n    val bean = VMessBean()\n    val vmessQRCode = Gson().fromJson(result, VmessQRCode::class.java)\n\n    // Although VmessQRCode fields are non null, looks like Gson may still create null fields\n    if (TextUtils.isEmpty(vmessQRCode.add)\n        || TextUtils.isEmpty(vmessQRCode.port)\n        || TextUtils.isEmpty(vmessQRCode.id)\n        || TextUtils.isEmpty(vmessQRCode.net)\n    ) {\n        throw Exception(\"invalid VmessQRCode\")\n    }\n\n    bean.name = vmessQRCode.ps\n    bean.serverAddress = vmessQRCode.add\n    bean.serverPort = vmessQRCode.port.toIntOrNull()\n    bean.encryption = vmessQRCode.scy\n    bean.uuid = vmessQRCode.id\n    bean.alterId = vmessQRCode.aid.toIntOrNull()\n    bean.type = vmessQRCode.net\n    bean.host = vmessQRCode.host\n    bean.path = vmessQRCode.path\n    val headerType = vmessQRCode.type\n\n    when (bean.type) {\n        \"tcp\" -> {\n            if (headerType == \"http\") {\n                bean.type = \"http\"\n            }\n        }\n    }\n    when (vmessQRCode.tls) {\n        \"tls\", \"reality\" -> {\n            bean.security = \"tls\"\n            bean.sni = vmessQRCode.sni\n            if (bean.sni.isNullOrBlank()) bean.sni = bean.host\n            bean.alpn = vmessQRCode.alpn\n            bean.utlsFingerprint = vmessQRCode.fp\n        }\n    }\n\n    return bean\n}\n\nprivate fun parseCsvVMess(csv: String): VMessBean {\n\n    val args = csv.split(\",\")\n\n    val bean = VMessBean()\n\n    bean.serverAddress = args[1]\n    bean.serverPort = args[2].toInt()\n    bean.encryption = args[3]\n    bean.uuid = args[4].replace(\"\\\"\", \"\")\n\n    args.subList(5, args.size).forEach {\n\n        when {\n            it == \"over-tls=true\" -> bean.security = \"tls\"\n            it.startsWith(\"tls-host=\") -> bean.host = it.substringAfter(\"=\")\n            it.startsWith(\"obfs=\") -> bean.type = it.substringAfter(\"=\")\n            it.startsWith(\"obfs-path=\") || it.contains(\"Host:\") -> {\n                runCatching {\n                    bean.path = it.substringAfter(\"obfs-path=\\\"\").substringBefore(\"\\\"obfs\")\n                }\n                runCatching {\n                    bean.host = it.substringAfter(\"Host:\").substringBefore(\"[\")\n                }\n\n            }\n\n        }\n\n    }\n\n    return bean\n\n}\n\nfun VMessBean.toV2rayN(): String {\n    val bean = this\n    return \"vmess://\" + VmessQRCode().apply {\n        v = \"2\"\n        ps = bean.name\n        add = bean.serverAddress\n        port = bean.serverPort.toString()\n        id = bean.uuid\n        aid = bean.alterId.toString()\n        net = bean.type\n        host = bean.host\n        path = bean.path\n\n        when (net) {\n            \"http\" -> {\n                if (!isTLS()) {\n                    type = \"http\"\n                    net = \"tcp\"\n                }\n            }\n        }\n\n        if (isTLS()) {\n            tls = \"tls\"\n            if (bean.realityPubKey.isNotBlank()) {\n                tls = \"reality\"\n            }\n        }\n\n        scy = bean.encryption\n        sni = bean.sni\n        alpn = bean.alpn.replace(\"\\n\", \",\")\n        fp = bean.utlsFingerprint\n    }.let {\n        NGUtil.encode(Gson().toJson(it))\n    }\n}\n\nfun StandardV2RayBean.toUriVMessVLESSTrojan(isTrojan: Boolean): String {\n    // VMess\n    if (this is VMessBean && !isVLESS) {\n        return toV2rayN()\n    }\n\n    // VLESS & Trojan (ducksoft fmt)\n    val builder = linkBuilder()\n        .username(if (this is TrojanBean) password else uuid)\n        .host(serverAddress)\n        .port(serverPort)\n        .addQueryParameter(\"type\", type)\n\n    if (isVLESS) {\n        builder.addQueryParameter(\"encryption\", \"none\")\n        if (encryption != \"auto\") builder.addQueryParameter(\"flow\", encryption)\n    }\n\n    when (type) {\n        \"tcp\" -> {}\n        \"ws\", \"http\", \"httpupgrade\" -> {\n            if (host.isNotBlank()) {\n                builder.addQueryParameter(\"host\", host)\n            }\n            if (path.isNotBlank()) {\n                builder.addQueryParameter(\"path\", path)\n            }\n            if (type == \"ws\") {\n                if (wsMaxEarlyData > 0) {\n                    builder.addQueryParameter(\"ed\", \"$wsMaxEarlyData\")\n                    if (earlyDataHeaderName.isNotBlank()) {\n                        builder.addQueryParameter(\"eh\", earlyDataHeaderName)\n                    }\n                }\n            } else if (type == \"http\" && !isTLS()) {\n                builder.setQueryParameter(\"type\", \"tcp\")\n                builder.addQueryParameter(\"headerType\", \"http\")\n            }\n        }\n\n        \"grpc\" -> {\n            if (path.isNotBlank()) {\n                builder.setQueryParameter(\"serviceName\", path)\n            }\n        }\n    }\n\n    if (security.isNotBlank() && security != \"none\") {\n        builder.addQueryParameter(\"security\", security)\n        when (security) {\n            \"tls\" -> {\n                if (sni.isNotBlank()) {\n                    builder.addQueryParameter(\"sni\", sni)\n                }\n                if (alpn.isNotBlank()) {\n                    builder.addQueryParameter(\"alpn\", alpn.replace(\"\\n\", \",\"))\n                }\n                if (certificates.isNotBlank()) {\n                    builder.addQueryParameter(\"cert\", certificates)\n                }\n                if (allowInsecure) {\n                    builder.addQueryParameter(\"allowInsecure\", \"1\")\n                }\n                if (utlsFingerprint.isNotBlank()) {\n                    builder.addQueryParameter(\"fp\", utlsFingerprint)\n                }\n                if (realityPubKey.isNotBlank()) {\n                    builder.setQueryParameter(\"security\", \"reality\")\n                    builder.addQueryParameter(\"pbk\", realityPubKey)\n                    builder.addQueryParameter(\"sid\", realityShortId)\n                }\n            }\n        }\n    }\n\n    when (packetEncoding) {\n        1 -> {\n            builder.addQueryParameter(\"packetEncoding\", \"packetaddr\")\n        }\n\n        2 -> {\n            builder.addQueryParameter(\"packetEncoding\", \"xudp\")\n        }\n    }\n\n    if (name.isNotBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n\n    return builder.toLink(if (isTrojan) \"trojan\" else \"vless\")\n}\n\nfun buildSingBoxOutboundStreamSettings(bean: StandardV2RayBean): V2RayTransportOptions? {\n    when (bean.type) {\n        \"tcp\" -> {\n            return null\n        }\n\n        \"ws\" -> {\n            return V2RayTransportOptions_WebsocketOptions().apply {\n                type = \"ws\"\n                headers = mutableMapOf()\n\n                if (bean.host.isNotBlank()) {\n                    headers[\"Host\"] = bean.host\n                }\n\n                if (bean.path.contains(\"?ed=\")) {\n                    path = bean.path.substringBefore(\"?ed=\")\n                    max_early_data = bean.path.substringAfter(\"?ed=\").toIntOrNull() ?: 2048\n                    early_data_header_name = \"Sec-WebSocket-Protocol\"\n                } else {\n                    path = bean.path.takeIf { it.isNotBlank() } ?: \"/\"\n                }\n\n                if (bean.wsMaxEarlyData > 0) {\n                    max_early_data = bean.wsMaxEarlyData\n                }\n\n                if (bean.earlyDataHeaderName.isNotBlank()) {\n                    early_data_header_name = bean.earlyDataHeaderName\n                }\n            }\n        }\n\n        \"http\" -> {\n            return V2RayTransportOptions_HTTPOptions().apply {\n                type = \"http\"\n                if (!bean.isTLS()) method = \"GET\" // v2ray tcp header\n                if (bean.host.isNotBlank()) {\n                    host = bean.host.split(\",\")\n                }\n                path = bean.path.takeIf { it.isNotBlank() } ?: \"/\"\n            }\n        }\n\n        \"quic\" -> {\n            return V2RayTransportOptions().apply {\n                type = \"quic\"\n            }\n        }\n\n        \"grpc\" -> {\n            return V2RayTransportOptions_GRPCOptions().apply {\n                type = \"grpc\"\n                service_name = bean.path\n            }\n        }\n\n        \"httpupgrade\" -> {\n            return V2RayTransportOptions_HTTPUpgradeOptions().apply {\n                type = \"httpupgrade\"\n                host = bean.host\n                path = bean.path\n            }\n        }\n    }\n\n    return null\n}\n\nfun buildSingBoxOutboundTLS(bean: StandardV2RayBean): OutboundTLSOptions? {\n    if (bean.security != \"tls\") return null\n    return OutboundTLSOptions().apply {\n        enabled = true\n        insecure = bean.allowInsecure || DataStore.globalAllowInsecure\n        if (bean.sni.isNotBlank()) server_name = bean.sni\n        if (bean.alpn.isNotBlank()) alpn = bean.alpn.listByLineOrComma()\n        if (bean.certificates.isNotBlank()) certificate = bean.certificates\n        var fp = bean.utlsFingerprint\n        if (bean.realityPubKey.isNotBlank()) {\n            reality = OutboundRealityOptions().apply {\n                enabled = true\n                public_key = bean.realityPubKey\n                short_id = bean.realityShortId\n            }\n            if (fp.isNullOrBlank()) fp = \"chrome\"\n        }\n        if (fp.isNotBlank()) {\n            utls = OutboundUTLSOptions().apply {\n                enabled = true\n                fingerprint = fp\n            }\n        }\n        if (bean.enableECH) {\n            ech = OutboundECHOptions().apply {\n                enabled = true\n                if (bean.echConfig.isNotBlank()) {\n                    config = bean.echConfig.lines()\n                }\n            }\n        }\n    }\n}\n\nfun buildSingBoxOutboundStandardV2RayBean(bean: StandardV2RayBean): Outbound {\n    when (bean) {\n        is HttpBean -> {\n            return Outbound_HTTPOptions().apply {\n                type = \"http\"\n                server = bean.serverAddress\n                server_port = bean.serverPort\n                username = bean.username\n                password = bean.password\n                tls = buildSingBoxOutboundTLS(bean)\n            }\n        }\n\n        is VMessBean -> {\n            if (bean.isVLESS) return Outbound_VLESSOptions().apply {\n                type = \"vless\"\n                server = bean.serverAddress\n                server_port = bean.serverPort\n                uuid = bean.uuid\n                if (bean.encryption.isNotBlank() && bean.encryption != \"auto\") {\n                    flow = bean.encryption\n                }\n                when (bean.packetEncoding) {\n                    0 -> packet_encoding = \"\"\n                    1 -> packet_encoding = \"packetaddr\"\n                    2 -> packet_encoding = \"xudp\"\n                }\n                tls = buildSingBoxOutboundTLS(bean)\n                transport = buildSingBoxOutboundStreamSettings(bean)\n            }\n            return Outbound_VMessOptions().apply {\n                type = \"vmess\"\n                server = bean.serverAddress\n                server_port = bean.serverPort\n                uuid = bean.uuid\n                alter_id = bean.alterId\n                security = bean.encryption.takeIf { it.isNotBlank() } ?: \"auto\"\n                when (bean.packetEncoding) {\n                    0 -> packet_encoding = \"\"\n                    1 -> packet_encoding = \"packetaddr\"\n                    2 -> packet_encoding = \"xudp\"\n                }\n                tls = buildSingBoxOutboundTLS(bean)\n                transport = buildSingBoxOutboundStreamSettings(bean)\n            }\n        }\n\n        is TrojanBean -> {\n            return Outbound_TrojanOptions().apply {\n                type = \"trojan\"\n                server = bean.serverAddress\n                server_port = bean.serverPort\n                password = bean.password\n                tls = buildSingBoxOutboundTLS(bean)\n                transport = buildSingBoxOutboundStreamSettings(bean)\n            }\n        }\n\n        else -> throw IllegalStateException(\"can't reach\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/v2ray/VMessBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.v2ray;\n\nimport androidx.annotation.NonNull;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class VMessBean extends StandardV2RayBean {\n\n    public Integer alterId; // alterID == -1 --> VLESS\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        alterId = alterId != null ? alterId : 0;\n\n        if (alterId == -1) {\n            encryption = JavaUtil.isNotBlank(encryption) ? encryption : \"\";\n        } else {\n            encryption = JavaUtil.isNotBlank(encryption) ? encryption : \"auto\";\n        }\n    }\n\n    @NotNull\n    @Override\n    public VMessBean clone() {\n        return KryoConverters.deserialize(new VMessBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<VMessBean> CREATOR = new CREATOR<VMessBean>() {\n        @NonNull\n        @Override\n        public VMessBean newInstance() {\n            return new VMessBean();\n        }\n\n        @Override\n        public VMessBean[] newArray(int size) {\n            return new VMessBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardBean.java",
    "content": "package io.nekohasekai.sagernet.fmt.wireguard;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class WireGuardBean extends AbstractBean {\n\n    public String localAddress;\n    public String privateKey;\n    public String peerPublicKey;\n    public String peerPreSharedKey;\n    public Integer mtu;\n    public String reserved;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (localAddress == null) localAddress = \"\";\n        if (privateKey == null) privateKey = \"\";\n        if (peerPublicKey == null) peerPublicKey = \"\";\n        if (peerPreSharedKey == null) peerPreSharedKey = \"\";\n        if (mtu == null) mtu = 1420;\n        if (reserved == null) reserved = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(2);\n        super.serialize(output);\n        output.writeString(localAddress);\n        output.writeString(privateKey);\n        output.writeString(peerPublicKey);\n        output.writeString(peerPreSharedKey);\n        output.writeInt(mtu);\n        output.writeString(reserved);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        localAddress = input.readString();\n        privateKey = input.readString();\n        peerPublicKey = input.readString();\n        peerPreSharedKey = input.readString();\n        mtu = input.readInt();\n        reserved = input.readString();\n    }\n\n    @Override\n    public boolean canTCPing() {\n        return false;\n    }\n\n    @NotNull\n    @Override\n    public WireGuardBean clone() {\n        return KryoConverters.deserialize(new WireGuardBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<WireGuardBean> CREATOR = new CREATOR<WireGuardBean>() {\n        @NonNull\n        @Override\n        public WireGuardBean newInstance() {\n            return new WireGuardBean();\n        }\n\n        @Override\n        public WireGuardBean[] newArray(int size) {\n            return new WireGuardBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/fmt/wireguard/WireGuardFmt.kt",
    "content": "package io.nekohasekai.sagernet.fmt.wireguard\n\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.Util\nimport moe.matsuri.nb4a.utils.listByLineOrComma\n\nfun genReserved(anyStr: String): String {\n    try {\n        val list = anyStr.listByLineOrComma()\n        val ba = ByteArray(3)\n        if (list.size == 3) {\n            list.forEachIndexed { index, s ->\n                val i = s\n                    .replace(\"[\", \"\")\n                    .replace(\"]\", \"\")\n                    .replace(\" \", \"\")\n                    .toIntOrNull() ?: return anyStr\n                ba[index] = i.toByte()\n            }\n            return Util.b64EncodeOneLine(ba)\n        } else {\n            return anyStr\n        }\n    } catch (e: Exception) {\n        return anyStr\n    }\n}\n\nfun buildSingBoxOutboundWireguardBean(bean: WireGuardBean): SingBoxOptions.Outbound_WireGuardOptions {\n    return SingBoxOptions.Outbound_WireGuardOptions().apply {\n        type = \"wireguard\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        local_address = bean.localAddress.listByLineOrComma()\n        private_key = bean.privateKey\n        peer_public_key = bean.peerPublicKey\n        pre_shared_key = bean.peerPreSharedKey\n        mtu = bean.mtu\n        if (bean.reserved.isNotBlank()) reserved = genReserved(bean.reserved)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/group/GroupInterfaceAdapter.kt",
    "content": "package io.nekohasekai.sagernet.group\n\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.GroupManager\nimport io.nekohasekai.sagernet.database.ProxyGroup\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\nimport io.nekohasekai.sagernet.ui.ThemedActivity\nimport kotlinx.coroutines.delay\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.suspendCoroutine\n\nclass GroupInterfaceAdapter(val context: ThemedActivity) : GroupManager.Interface {\n\n    override suspend fun confirm(message: String): Boolean {\n        return suspendCoroutine {\n            runOnMainDispatcher {\n                MaterialAlertDialogBuilder(context).setTitle(R.string.confirm)\n                    .setMessage(message)\n                    .setPositiveButton(R.string.yes) { _, _ -> it.resume(true) }\n                    .setNegativeButton(R.string.no) { _, _ -> it.resume(false) }\n                    .setOnCancelListener { _ -> it.resume(false) }\n                    .show()\n            }\n        }\n    }\n\n    override suspend fun onUpdateSuccess(\n        group: ProxyGroup,\n        changed: Int,\n        added: List<String>,\n        updated: Map<String, String>,\n        deleted: List<String>,\n        duplicate: List<String>,\n        byUser: Boolean\n    ) {\n        if (changed == 0 && duplicate.isEmpty()) {\n            if (byUser) context.snackbar(\n                    context.getString(\n                            R.string.group_no_difference, group.displayName()\n                    )\n            ).show()\n        } else {\n            context.snackbar(context.getString(R.string.group_updated, group.name, changed)).show()\n\n            var status = \"\"\n            if (added.isNotEmpty()) {\n                status += context.getString(\n                        R.string.group_added, added.joinToString(\"\\n\", postfix = \"\\n\\n\")\n                )\n            }\n            if (updated.isNotEmpty()) {\n                status += context.getString(R.string.group_changed,\n                        updated.map { it }.joinToString(\"\\n\", postfix = \"\\n\\n\") {\n                            if (it.key == it.value) it.key else \"${it.key} => ${it.value}\"\n                        })\n            }\n            if (deleted.isNotEmpty()) {\n                status += context.getString(\n                        R.string.group_deleted, deleted.joinToString(\"\\n\", postfix = \"\\n\\n\")\n                )\n            }\n            if (duplicate.isNotEmpty()) {\n                status += context.getString(\n                        R.string.group_duplicate, duplicate.joinToString(\"\\n\", postfix = \"\\n\\n\")\n                )\n            }\n\n            onMainDispatcher {\n                delay(1000L)\n\n                MaterialAlertDialogBuilder(context).setTitle(\n                        context.getString(\n                                R.string.group_diff, group.displayName()\n                        )\n                ).setMessage(status.trim()).setPositiveButton(android.R.string.ok, null).show()\n            }\n\n        }\n\n    }\n\n    override suspend fun onUpdateFailure(group: ProxyGroup, message: String) {\n        onMainDispatcher {\n            context.snackbar(message).show()\n        }\n    }\n\n    override suspend fun alert(message: String) {\n        return suspendCoroutine {\n            runOnMainDispatcher {\n                MaterialAlertDialogBuilder(context).setTitle(R.string.ooc_warning)\n                    .setMessage(message)\n                    .setPositiveButton(android.R.string.ok) { _, _ -> it.resume(Unit) }\n                    .setOnCancelListener { _ -> it.resume(Unit) }\n                    .show()\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/group/GroupUpdater.kt",
    "content": "package io.nekohasekai.sagernet.group\n\nimport io.nekohasekai.sagernet.*\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.GroupManager\nimport io.nekohasekai.sagernet.database.ProxyGroup\nimport io.nekohasekai.sagernet.database.SubscriptionBean\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.fmt.naive.NaiveBean\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\nimport io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean\nimport io.nekohasekai.sagernet.fmt.v2ray.isTLS\nimport io.nekohasekai.sagernet.ktx.*\nimport kotlinx.coroutines.*\nimport java.net.Inet4Address\nimport java.net.InetAddress\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicInteger\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nabstract class GroupUpdater {\n\n    abstract suspend fun doUpdate(\n        proxyGroup: ProxyGroup,\n        subscription: SubscriptionBean,\n        userInterface: GroupManager.Interface?,\n        byUser: Boolean\n    )\n\n    data class Progress(\n        var max: Int\n    ) {\n        var progress by AtomicInteger()\n    }\n\n    protected suspend fun forceResolve(\n        profiles: List<AbstractBean>, groupId: Long?\n    ) {\n        val ipv6Mode = DataStore.ipv6Mode\n        val lookupPool = newFixedThreadPoolContext(5, \"DNS Lookup\")\n        val lookupJobs = mutableListOf<Job>()\n        val progress = Progress(profiles.size)\n        if (groupId != null) {\n            GroupUpdater.progress[groupId] = progress\n            GroupManager.postReload(groupId)\n        }\n        val ipv6First = ipv6Mode >= IPv6Mode.PREFER\n\n        for (profile in profiles) {\n            when (profile) {\n                // SNI rewrite unsupported\n                is NaiveBean -> continue\n            }\n\n            if (profile.serverAddress.isIpAddress()) continue\n\n            lookupJobs.add(GlobalScope.launch(lookupPool) {\n                try {\n                    val results = if (\n                        SagerNet.underlyingNetwork != null &&\n                        DataStore.enableFakeDns &&\n                        DataStore.serviceState.started &&\n                        DataStore.serviceMode == Key.MODE_VPN\n                    ) {\n                        // FakeDNS\n                        SagerNet.underlyingNetwork!!\n                            .getAllByName(profile.serverAddress)\n                            .filterNotNull()\n                    } else {\n                        // System DNS is enough (when VPN connected, it uses v2ray-core)\n                        InetAddress.getAllByName(profile.serverAddress).filterNotNull()\n                    }\n                    if (results.isEmpty()) error(\"empty response\")\n                    rewriteAddress(profile, results, ipv6First)\n                } catch (e: Exception) {\n                    Logs.d(\"Lookup ${profile.serverAddress} failed: ${e.readableMessage}\", e)\n                }\n                if (groupId != null) {\n                    progress.progress++\n                    GroupManager.postReload(groupId)\n                }\n            })\n        }\n\n        lookupJobs.joinAll()\n        lookupPool.close()\n    }\n\n    protected fun rewriteAddress(\n        bean: AbstractBean, addresses: List<InetAddress>, ipv6First: Boolean\n    ) {\n        val address = addresses.sortedBy { (it is Inet4Address) xor ipv6First }[0].hostAddress\n\n        with(bean) {\n            when (this) {\n                is HttpBean -> {\n                    if (isTLS() && sni.isBlank()) sni = bean.serverAddress\n                }\n                is StandardV2RayBean -> {\n                    when (security) {\n                        \"tls\" -> if (sni.isBlank()) sni = bean.serverAddress\n                    }\n                }\n                is TrojanBean -> {\n                    if (sni.isBlank()) sni = bean.serverAddress\n                }\n                is TrojanGoBean -> {\n                    if (sni.isBlank()) sni = bean.serverAddress\n                }\n                is HysteriaBean -> {\n                    if (sni.isBlank()) sni = bean.serverAddress\n                }\n            }\n\n            bean.serverAddress = address\n        }\n    }\n\n    companion object {\n\n        val updating = Collections.synchronizedSet<Long>(mutableSetOf())\n        val progress = Collections.synchronizedMap<Long, Progress>(mutableMapOf())\n\n        fun startUpdate(proxyGroup: ProxyGroup, byUser: Boolean) {\n            runOnDefaultDispatcher {\n                executeUpdate(proxyGroup, byUser)\n            }\n        }\n\n        suspend fun executeUpdate(proxyGroup: ProxyGroup, byUser: Boolean): Boolean {\n            return coroutineScope {\n                if (!updating.add(proxyGroup.id)) cancel()\n                GroupManager.postReload(proxyGroup.id)\n\n                val subscription = proxyGroup.subscription!!\n                val connected = DataStore.serviceState.connected\n                val userInterface = GroupManager.userInterface\n\n                if (byUser && (subscription.link?.startsWith(\"http://\") == true || subscription.updateWhenConnectedOnly) && !connected) {\n                    if (userInterface == null || !userInterface.confirm(app.getString(R.string.update_subscription_warning))) {\n                        finishUpdate(proxyGroup)\n                        cancel()\n                        return@coroutineScope true\n                    }\n                }\n\n                try {\n                    RawUpdater.doUpdate(proxyGroup, subscription, userInterface, byUser)\n                    true\n                } catch (e: Throwable) {\n                    Logs.w(e)\n                    userInterface?.onUpdateFailure(proxyGroup, e.readableMessage)\n                    finishUpdate(proxyGroup)\n                    false\n                }\n            }\n        }\n\n\n        suspend fun finishUpdate(proxyGroup: ProxyGroup) {\n            updating.remove(proxyGroup.id)\n            progress.remove(proxyGroup.id)\n            GroupManager.postUpdate(proxyGroup)\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/group/RawUpdater.kt",
    "content": "package io.nekohasekai.sagernet.group\n\nimport android.annotation.SuppressLint\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.*\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.fmt.hysteria.parseHysteria1Json\nimport io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean\nimport io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks\nimport io.nekohasekai.sagernet.fmt.socks.SOCKSBean\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\nimport io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo\nimport io.nekohasekai.sagernet.fmt.tuic.TuicBean\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean\nimport io.nekohasekai.sagernet.fmt.v2ray.VMessBean\nimport io.nekohasekai.sagernet.fmt.v2ray.isTLS\nimport io.nekohasekai.sagernet.fmt.v2ray.setTLS\nimport io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean\nimport io.nekohasekai.sagernet.ktx.*\nimport libcore.Libcore\nimport moe.matsuri.nb4a.Protocols\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSBean\nimport moe.matsuri.nb4a.proxy.config.ConfigBean\nimport moe.matsuri.nb4a.utils.Util\nimport org.ini4j.Ini\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport org.json.JSONTokener\nimport org.yaml.snakeyaml.TypeDescription\nimport org.yaml.snakeyaml.Yaml\nimport org.yaml.snakeyaml.error.YAMLException\nimport java.io.StringReader\nimport androidx.core.net.toUri\n\n@Suppress(\"EXPERIMENTAL_API_USAGE\")\nobject RawUpdater : GroupUpdater() {\n\n    @SuppressLint(\"Recycle\")\n    override suspend fun doUpdate(\n        proxyGroup: ProxyGroup,\n        subscription: SubscriptionBean,\n        userInterface: GroupManager.Interface?,\n        byUser: Boolean\n    ) {\n\n        val link = subscription.link\n        var proxies: List<AbstractBean>\n        if (link.startsWith(\"content://\")) {\n            val contentText = app.contentResolver.openInputStream(link.toUri())\n                ?.bufferedReader()\n                ?.readText()\n\n            proxies = contentText?.let { parseRaw(contentText) }\n                ?: error(app.getString(R.string.no_proxies_found_in_subscription))\n        } else {\n\n            val response = Libcore.newHttpClient().apply {\n                trySocks5(DataStore.mixedPort)\n                tryH3Direct()\n                when (DataStore.appTLSVersion) {\n                    \"1.3\" -> restrictedTLS()\n                }\n            }.newRequest().apply {\n                if (DataStore.allowInsecureOnRequest) {\n                    allowInsecure()\n                }\n                setURL(subscription.link)\n                setUserAgent(subscription.customUserAgent.takeIf { it.isNotBlank() } ?: USER_AGENT)\n            }.execute()\n            proxies = parseRaw(Util.getStringBox(response.contentString))\n                ?: error(app.getString(R.string.no_proxies_found))\n\n            subscription.subscriptionUserinfo =\n                Util.getStringBox(response.getHeader(\"Subscription-Userinfo\"))\n\n            // 修改默认名字\n            if (proxyGroup.name?.startsWith(\"Subscription #\") == true) {\n                var remoteName = Util.getStringBox(response.getHeader(\"content-disposition\"))\n                if (remoteName.isNotBlank()) {\n                    remoteName = Util.decodeFilename(remoteName)\n                    if (remoteName.isNotBlank()) {\n                        proxyGroup.name = remoteName\n                    }\n                }\n            }\n        }\n\n        val proxiesMap = LinkedHashMap<String, AbstractBean>()\n        for (proxy in proxies) {\n            var index = 0\n            var name = proxy.displayName()\n            while (proxiesMap.containsKey(name)) {\n                println(\"Exists name: $name\")\n                index++\n                name = name.replace(\" (${index - 1})\", \"\")\n                name = \"$name ($index)\"\n                proxy.name = name\n            }\n            proxiesMap[proxy.displayName()] = proxy\n        }\n        proxies = proxiesMap.values.toList()\n\n        if (subscription.forceResolve) forceResolve(proxies, proxyGroup.id)\n\n        val exists = SagerDatabase.proxyDao.getByGroup(proxyGroup.id)\n        val duplicate = ArrayList<String>()\n        if (subscription.deduplication) {\n            Logs.d(\"Before deduplication: ${proxies.size}\")\n            val uniqueProxies = LinkedHashSet<Protocols.Deduplication>()\n            val uniqueNames = HashMap<Protocols.Deduplication, String>()\n            for (_proxy in proxies) {\n                val proxy = Protocols.Deduplication(_proxy, _proxy.javaClass.toString())\n                if (!uniqueProxies.add(proxy)) {\n                    val index = uniqueProxies.indexOf(proxy)\n                    if (uniqueNames.containsKey(proxy)) {\n                        val name = uniqueNames[proxy]!!.replace(\" ($index)\", \"\")\n                        if (name.isNotBlank()) {\n                            duplicate.add(\"$name ($index)\")\n                            uniqueNames[proxy] = \"\"\n                        }\n                    }\n                    duplicate.add(_proxy.displayName() + \" ($index)\")\n                } else {\n                    uniqueNames[proxy] = _proxy.displayName()\n                }\n            }\n            uniqueProxies.retainAll(uniqueNames.keys)\n            proxies = uniqueProxies.toList().map { it.bean }\n        }\n\n        Logs.d(\"New profiles: ${proxies.size}\")\n\n        val nameMap = proxies.associateBy { bean ->\n            bean.displayName()\n        }\n\n        Logs.d(\"Unique profiles: ${nameMap.size}\")\n\n        val toDelete = ArrayList<ProxyEntity>()\n        val toReplace = exists.mapNotNull { entity ->\n            val name = entity.displayName()\n            if (nameMap.contains(name)) name to entity else let {\n                toDelete.add(entity)\n                null\n            }\n        }.toMap()\n\n        Logs.d(\"toDelete profiles: ${toDelete.size}\")\n        Logs.d(\"toReplace profiles: ${toReplace.size}\")\n\n        val toUpdate = ArrayList<ProxyEntity>()\n        val added = mutableListOf<String>()\n        val updated = mutableMapOf<String, String>()\n        val deleted = toDelete.map { it.displayName() }\n\n        var userOrder = 1L\n        var changed = toDelete.size\n        for ((name, bean) in nameMap.entries) {\n            if (toReplace.contains(name)) {\n                val entity = toReplace[name]!!\n                val existsBean = entity.requireBean()\n                // 更新订阅，保留自定义覆写设置\n                bean.customOutboundJson = existsBean.customOutboundJson\n                bean.customConfigJson = existsBean.customConfigJson\n                when {\n                    existsBean != bean -> {\n                        changed++\n                        entity.putBean(bean)\n                        toUpdate.add(entity)\n                        updated[entity.displayName()] = name\n\n                        Logs.d(\"Updated profile: $name\")\n                    }\n\n                    entity.userOrder != userOrder -> {\n                        entity.putBean(bean)\n                        toUpdate.add(entity)\n                        entity.userOrder = userOrder\n\n                        Logs.d(\"Reordered profile: $name\")\n                    }\n\n                    else -> {\n                        Logs.d(\"Ignored profile: $name\")\n                    }\n                }\n            } else {\n                changed++\n                SagerDatabase.proxyDao.addProxy(\n                    ProxyEntity(\n                        groupId = proxyGroup.id, userOrder = userOrder\n                    ).apply {\n                        putBean(bean)\n                    })\n                added.add(name)\n                Logs.d(\"Inserted profile: $name\")\n            }\n            userOrder++\n        }\n\n        SagerDatabase.proxyDao.updateProxy(toUpdate).also {\n            Logs.d(\"Updated profiles: $it\")\n        }\n\n        SagerDatabase.proxyDao.deleteProxy(toDelete).also {\n            Logs.d(\"Deleted profiles: $it\")\n        }\n\n        val existCount = SagerDatabase.proxyDao.countByGroup(proxyGroup.id).toInt()\n\n        if (existCount != proxies.size) {\n            Logs.e(\"Exist profiles: $existCount, new profiles: ${proxies.size}\")\n        }\n\n        subscription.lastUpdated = (System.currentTimeMillis() / 1000).toInt()\n        SagerDatabase.groupDao.updateGroup(proxyGroup)\n        finishUpdate(proxyGroup)\n\n        userInterface?.onUpdateSuccess(\n            proxyGroup, changed, added, updated, deleted, duplicate, byUser\n        )\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    suspend fun parseRaw(text: String, fileName: String = \"\"): List<AbstractBean>? {\n\n        val proxies = mutableListOf<AbstractBean>()\n\n        if (text.contains(\"proxies:\")) {\n\n            // clash & meta\n\n            try {\n\n                val yaml = Yaml().apply {\n                    addTypeDescription(TypeDescription(String::class.java, \"str\"))\n                }.loadAs(text, Map::class.java)\n\n                val globalClientFingerprint = yaml[\"global-client-fingerprint\"]?.toString() ?: \"\"\n\n                for (proxy in (yaml[\"proxies\"] as? (List<Map<String, Any?>>) ?: error(\n                    app.getString(R.string.no_proxies_found_in_file)\n                ))) {\n                    // Note: YAML numbers parsed as \"Long\"\n\n                    when (proxy[\"type\"] as String) {\n                        \"socks5\" -> {\n                            proxies.add(SOCKSBean().apply {\n                                serverAddress = proxy[\"server\"] as String\n                                serverPort = proxy[\"port\"].toString().toInt()\n                                username = proxy[\"username\"]?.toString()\n                                password = proxy[\"password\"]?.toString()\n                                name = proxy[\"name\"]?.toString()\n                            })\n                        }\n\n                        \"http\" -> {\n                            proxies.add(HttpBean().apply {\n                                serverAddress = proxy[\"server\"] as String\n                                serverPort = proxy[\"port\"].toString().toInt()\n                                username = proxy[\"username\"]?.toString()\n                                password = proxy[\"password\"]?.toString()\n                                setTLS(proxy[\"tls\"]?.toString() == \"true\")\n                                sni = proxy[\"sni\"]?.toString()\n                                name = proxy[\"name\"]?.toString()\n                                allowInsecure = proxy[\"skip-cert-verify\"]?.toString() == \"true\"\n                            })\n                        }\n\n                        \"ss\" -> {\n                            val ssPlugin = mutableListOf<String>()\n                            if (proxy.contains(\"plugin\")) {\n                                val opts = proxy[\"plugin-opts\"] as Map<String, Any?>\n                                when (proxy[\"plugin\"]) {\n                                    \"obfs\" -> {\n                                        ssPlugin.apply {\n                                            add(\"obfs-local\")\n                                            add(\"obfs=\" + (opts[\"mode\"]?.toString() ?: \"\"))\n                                            add(\"obfs-host=\" + (opts[\"host\"]?.toString() ?: \"\"))\n                                        }\n                                    }\n\n                                    \"v2ray-plugin\" -> {\n                                        ssPlugin.apply {\n                                            add(\"v2ray-plugin\")\n                                            add(\"mode=\" + (opts[\"mode\"]?.toString() ?: \"\"))\n                                            if (opts[\"mode\"]?.toString() == \"true\") add(\"tls\")\n                                            add(\"host=\" + (opts[\"host\"]?.toString() ?: \"\"))\n                                            add(\"path=\" + (opts[\"path\"]?.toString() ?: \"\"))\n                                            if (opts[\"mux\"]?.toString() == \"true\") add(\"mux=8\")\n                                        }\n                                    }\n                                }\n                            }\n                            proxies.add(ShadowsocksBean().apply {\n                                serverAddress = proxy[\"server\"] as String\n                                serverPort = proxy[\"port\"].toString().toInt()\n                                password = proxy[\"password\"]?.toString()\n                                method = clashCipher(proxy[\"cipher\"] as String)\n                                plugin = ssPlugin.joinToString(\";\")\n                                name = proxy[\"name\"]?.toString()\n                            })\n                        }\n\n                        \"vmess\", \"vless\", \"trojan\" -> {\n                            val bean = when (proxy[\"type\"] as String) {\n                                \"vmess\" -> VMessBean()\n                                \"vless\" -> VMessBean().apply {\n                                    alterId = -1 // make it VLESS\n                                    packetEncoding = 2 // clash meta default XUDP\n                                }\n\n                                \"trojan\" -> TrojanBean().apply {\n                                    security = \"tls\"\n                                }\n\n                                else -> error(\"impossible\")\n                            }\n\n                            bean.serverAddress = proxy[\"server\"]?.toString() ?: continue\n                            bean.serverPort = proxy[\"port\"]?.toString()?.toIntOrNull() ?: continue\n\n                            for (opt in proxy) {\n                                when (opt.key) {\n                                    \"name\" -> bean.name = opt.value?.toString()\n                                    \"password\" -> if (bean is TrojanBean) bean.password =\n                                        opt.value?.toString()\n\n                                    \"uuid\" -> if (bean is VMessBean) bean.uuid =\n                                        opt.value?.toString()\n\n                                    \"alterId\" -> if (bean is VMessBean && !bean.isVLESS) bean.alterId =\n                                        opt.value?.toString()?.toIntOrNull()\n\n                                    \"cipher\" -> if (bean is VMessBean && !bean.isVLESS) bean.encryption =\n                                        (opt.value as? String)\n\n                                    \"flow\" -> if (bean is VMessBean && bean.isVLESS) {\n                                        (opt.value as? String)?.let {\n                                            if (it.contains(\"xtls-rprx-vision\")) {\n                                                bean.encryption = \"xtls-rprx-vision\"\n                                            }\n                                        }\n                                    }\n\n                                    \"packet-encoding\" -> if (bean is VMessBean) {\n                                        bean.packetEncoding = when ((opt.value as? String)) {\n                                            \"packetaddr\" -> 1\n                                            \"xudp\" -> 2\n                                            else -> 0\n                                        }\n                                    }\n\n                                    \"tls\" -> if (bean is VMessBean) {\n                                        bean.security =\n                                            if (opt.value as? Boolean == true) \"tls\" else \"\"\n                                    }\n\n                                    \"servername\", \"sni\" -> bean.sni = opt.value?.toString()\n\n                                    \"alpn\" -> bean.alpn =\n                                        (opt.value as? List<Any>)?.joinToString(\"\\n\")\n\n                                    \"skip-cert-verify\" -> bean.allowInsecure =\n                                        opt.value as? Boolean == true\n\n                                    \"client-fingerprint\" -> bean.utlsFingerprint =\n                                        opt.value as String\n\n                                    \"reality-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (realityOpt in it) {\n                                            bean.security = \"tls\"\n\n                                            when (realityOpt.key) {\n                                                \"public-key\" -> bean.realityPubKey =\n                                                    realityOpt.value?.toString()\n\n                                                \"short-id\" -> bean.realityShortId =\n                                                    realityOpt.value?.toString()\n                                            }\n                                        }\n                                    }\n\n                                    \"network\" -> {\n                                        when (opt.value) {\n                                            \"h2\", \"http\" -> bean.type = \"http\"\n                                            \"ws\", \"grpc\" -> bean.type = opt.value as String\n                                        }\n                                    }\n\n                                    \"ws-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (wsOpt in it) {\n                                            when (wsOpt.key) {\n                                                \"headers\" -> (wsOpt.value as? Map<Any, Any?>)?.forEach { (key, value) ->\n                                                    when (key.toString().lowercase()) {\n                                                        \"host\" -> {\n                                                            bean.host = value?.toString()\n                                                        }\n                                                    }\n                                                }\n\n                                                \"path\" -> {\n                                                    bean.path = wsOpt.value?.toString()\n                                                }\n\n                                                \"max-early-data\" -> {\n                                                    bean.wsMaxEarlyData =\n                                                        wsOpt.value?.toString()?.toIntOrNull()\n                                                }\n\n                                                \"early-data-header-name\" -> {\n                                                    bean.earlyDataHeaderName =\n                                                        wsOpt.value?.toString()\n                                                }\n\n                                                \"v2ray-http-upgrade\" -> {\n                                                    if (wsOpt.value as? Boolean == true) {\n                                                        bean.type = \"httpupgrade\"\n                                                    }\n                                                }\n                                            }\n                                        }\n                                    }\n\n                                    \"h2-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (h2Opt in it) {\n                                            when (h2Opt.key) {\n                                                \"host\" -> bean.host =\n                                                    (h2Opt.value as? List<Any>)?.joinToString(\"\\n\")\n\n                                                \"path\" -> bean.path = h2Opt.value?.toString()\n                                            }\n                                        }\n                                    }\n\n                                    \"http-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (httpOpt in it) {\n                                            when (httpOpt.key) {\n                                                \"path\" -> bean.path =\n                                                    (httpOpt.value as? List<Any>)?.joinToString(\"\\n\")\n\n                                                \"headers\" -> {\n                                                    (httpOpt.value as? Map<Any, List<Any>>)?.forEach { (key, value) ->\n                                                        when (key.toString().lowercase()) {\n                                                            \"host\" -> {\n                                                                bean.host = value.joinToString(\"\\n\")\n                                                            }\n                                                        }\n                                                    }\n                                                }\n                                            }\n                                        }\n                                    }\n\n                                    \"grpc-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (grpcOpt in it) {\n                                            when (grpcOpt.key) {\n                                                \"grpc-service-name\" -> bean.path =\n                                                    grpcOpt.value?.toString()\n                                            }\n                                        }\n                                    }\n\n                                    \"smux\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (smuxOpt in it) {\n                                            when (smuxOpt.key) {\n                                                \"enabled\" -> bean.enableMux =\n                                                    smuxOpt.value.toString() == \"true\"\n\n                                                \"max-streams\" -> bean.muxConcurrency =\n                                                    smuxOpt.value.toString().toInt()\n\n                                                \"padding\" -> bean.muxPadding =\n                                                    smuxOpt.value.toString() == \"true\"\n                                            }\n                                        }\n                                    }\n\n                                    \"ech-opts\" -> (opt.value as? Map<String, Any?>)?.also {\n                                        for (echOpt in it) {\n                                            when (echOpt.key) {\n                                                \"enable\" -> bean.enableECH =\n                                                    echOpt.value.toString() == \"true\"\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                            proxies.add(bean)\n                        }\n\n                        \"anytls\" -> {\n                            val bean = AnyTLSBean()\n                            for (opt in proxy) {\n                                if (opt.value == null) continue\n                                when (opt.key.replace(\"_\", \"-\")) {\n                                    \"name\" -> bean.name = opt.value.toString()\n                                    \"server\" -> bean.serverAddress = opt.value as String\n                                    \"port\" -> bean.serverPort = opt.value.toString().toInt()\n                                    \"password\" -> bean.password = opt.value.toString()\n                                    \"client-fingerprint\" -> bean.utlsFingerprint =\n                                        opt.value as String\n\n                                    \"sni\" -> bean.sni = opt.value.toString()\n                                    \"skip-cert-verify\" -> bean.allowInsecure =\n                                        opt.value.toString() == \"true\"\n\n                                    \"alpn\" -> {\n                                        val alpn = (opt.value as? (List<String>))\n                                        bean.alpn = alpn?.joinToString(\"\\n\")\n                                    }\n                                }\n                            }\n                            proxies.add(bean)\n                        }\n\n                        \"hysteria\" -> {\n                            val bean = HysteriaBean()\n                            bean.protocolVersion = 1\n                            var hopPorts = \"\"\n                            for (opt in proxy) {\n                                if (opt.value == null) continue\n                                when (opt.key.replace(\"_\", \"-\")) {\n                                    \"name\" -> bean.name = opt.value.toString()\n                                    \"server\" -> bean.serverAddress = opt.value as String\n                                    \"port\" -> bean.serverPorts = opt.value.toString()\n                                    \"ports\" -> hopPorts = opt.value.toString()\n\n                                    \"obfs\" -> bean.obfuscation = opt.value.toString()\n\n                                    \"auth-str\" -> {\n                                        bean.authPayloadType = HysteriaBean.TYPE_STRING\n                                        bean.authPayload = opt.value.toString()\n                                    }\n\n                                    \"sni\" -> bean.sni = opt.value.toString()\n\n                                    \"skip-cert-verify\" -> bean.allowInsecure =\n                                        opt.value.toString() == \"true\"\n\n                                    \"up\" -> bean.uploadMbps =\n                                        opt.value.toString().substringBefore(\" \").toIntOrNull()\n                                            ?: 100\n\n                                    \"down\" -> bean.downloadMbps =\n                                        opt.value.toString().substringBefore(\" \").toIntOrNull()\n                                            ?: 100\n\n                                    \"recv-window-conn\" -> bean.connectionReceiveWindow =\n                                        opt.value.toString().toIntOrNull() ?: 0\n\n                                    \"recv-window\" -> bean.streamReceiveWindow =\n                                        opt.value.toString().toIntOrNull() ?: 0\n\n                                    \"disable-mtu-discovery\" -> bean.disableMtuDiscovery =\n                                        opt.value.toString() == \"true\" || opt.value.toString() == \"1\"\n\n                                    \"alpn\" -> {\n                                        val alpn = (opt.value as? (List<String>))\n                                        bean.alpn = alpn?.joinToString(\"\\n\") ?: \"h3\"\n                                    }\n                                }\n                            }\n                            if (hopPorts.isNotBlank()) {\n                                bean.serverPorts = hopPorts\n                            }\n                            proxies.add(bean)\n                        }\n\n                        \"hysteria2\" -> {\n                            val bean = HysteriaBean()\n                            bean.protocolVersion = 2\n                            var hopPorts = \"\"\n                            for (opt in proxy) {\n                                if (opt.value == null) continue\n                                when (opt.key.replace(\"_\", \"-\")) {\n                                    \"name\" -> bean.name = opt.value.toString()\n                                    \"server\" -> bean.serverAddress = opt.value as String\n                                    \"port\" -> bean.serverPorts = opt.value.toString()\n                                    \"ports\" -> hopPorts = opt.value.toString()\n\n                                    \"obfs-password\" -> bean.obfuscation = opt.value.toString()\n\n                                    \"password\" -> bean.authPayload = opt.value.toString()\n\n                                    \"sni\" -> bean.sni = opt.value.toString()\n\n                                    \"skip-cert-verify\" -> bean.allowInsecure =\n                                        opt.value.toString() == \"true\"\n\n                                    \"up\" -> bean.uploadMbps =\n                                        opt.value.toString().substringBefore(\" \").toIntOrNull() ?: 0\n\n                                    \"down\" -> bean.downloadMbps =\n                                        opt.value.toString().substringBefore(\" \").toIntOrNull() ?: 0\n                                }\n                            }\n                            if (hopPorts.isNotBlank()) {\n                                bean.serverPorts = hopPorts\n                            }\n                            proxies.add(bean)\n                        }\n\n                        \"tuic\" -> {\n                            val bean = TuicBean()\n                            var ip = \"\"\n                            for (opt in proxy) {\n                                if (opt.value == null) continue\n                                when (opt.key.replace(\"_\", \"-\")) {\n                                    \"name\" -> bean.name = opt.value.toString()\n                                    \"server\" -> bean.serverAddress = opt.value.toString()\n                                    \"ip\" -> ip = opt.value.toString()\n                                    \"port\" -> bean.serverPort = opt.value.toString().toInt()\n\n                                    \"token\" -> {\n                                        bean.protocolVersion = 4\n                                        bean.token = opt.value.toString()\n                                    }\n\n                                    \"uuid\" -> bean.uuid = opt.value.toString()\n\n                                    \"password\" -> bean.token = opt.value.toString()\n\n                                    \"skip-cert-verify\" -> bean.allowInsecure =\n                                        opt.value.toString() == \"true\"\n\n                                    \"disable-sni\" -> bean.disableSNI =\n                                        opt.value.toString() == \"true\"\n\n                                    \"reduce-rtt\" -> bean.reduceRTT =\n                                        opt.value.toString() == \"true\"\n\n                                    \"sni\" -> bean.sni = opt.value.toString()\n\n                                    \"alpn\" -> {\n                                        val alpn = (opt.value as? (List<String>))\n                                        bean.alpn = alpn?.joinToString(\"\\n\")\n                                    }\n\n                                    \"congestion-controller\" -> bean.congestionController =\n                                        opt.value.toString()\n\n                                    \"udp-relay-mode\" -> bean.udpRelayMode = opt.value.toString()\n\n                                }\n                            }\n                            if (ip.isNotBlank()) {\n                                bean.serverAddress = ip\n                                if (bean.sni.isNullOrBlank() && !bean.serverAddress.isNullOrBlank() && !bean.serverAddress.isIpAddress()) {\n                                    bean.sni = bean.serverAddress\n                                }\n                            }\n                            proxies.add(bean)\n                        }\n                    }\n                }\n\n                // Fix ent\n                proxies.forEach {\n                    it.initializeDefaultValues()\n                    if (it is StandardV2RayBean) {\n                        // 1. SNI\n                        if (it.isTLS() && it.sni.isNullOrBlank() && !it.host.isNullOrBlank() && !it.host.isIpAddress()) {\n                            it.sni = it.host\n                        }\n                        // 2. globalClientFingerprint\n                        if (!it.realityPubKey.isNullOrBlank() && it.utlsFingerprint.isNullOrBlank()) {\n                            it.utlsFingerprint = globalClientFingerprint\n                            if (it.utlsFingerprint.isNullOrBlank()) it.utlsFingerprint = \"chrome\"\n                        }\n                    }\n                }\n                return proxies\n            } catch (e: YAMLException) {\n                Logs.w(e)\n            }\n        } else if (text.contains(\"[Interface]\")) {\n            // wireguard\n            try {\n                proxies.addAll(parseWireGuard(text).map {\n                    if (fileName.isNotBlank()) it.name = fileName.removeSuffix(\".conf\")\n                    it\n                })\n                return proxies\n            } catch (e: Exception) {\n                Logs.w(e)\n            }\n        }\n\n        try {\n            val json = JSONTokener(text).nextValue()\n            return parseJSON(json)\n        } catch (ignored: Exception) {\n        }\n\n        try {\n            return parseProxies(text.decodeBase64UrlSafe()).takeIf { it.isNotEmpty() }\n                ?: error(\"Not found\")\n        } catch (e: Exception) {\n            Logs.w(e)\n        }\n\n        try {\n            return parseProxies(text).takeIf { it.isNotEmpty() } ?: error(\"Not found\")\n        } catch (e: SubscriptionFoundException) {\n            throw e\n        } catch (ignored: Exception) {\n        }\n\n        return null\n    }\n\n    fun clashCipher(cipher: String): String {\n        return when (cipher) {\n            \"dummy\" -> \"none\"\n            else -> cipher\n        }\n    }\n\n    fun parseWireGuard(conf: String): List<WireGuardBean> {\n        val ini = Ini(StringReader(conf))\n        val iface = ini[\"Interface\"] ?: error(\"Missing 'Interface' selection\")\n        val bean = WireGuardBean().applyDefaultValues()\n        val localAddresses = iface.getAll(\"Address\")\n        if (localAddresses.isNullOrEmpty()) error(\"Empty address in 'Interface' selection\")\n        bean.localAddress = localAddresses.flatMap { it.split(\",\") }.joinToString(\"\\n\")\n        bean.privateKey = iface[\"PrivateKey\"]\n        bean.mtu = iface[\"MTU\"]?.toIntOrNull()\n        val peers = ini.getAll(\"Peer\")\n        if (peers.isNullOrEmpty()) error(\"Missing 'Peer' selections\")\n        val beans = mutableListOf<WireGuardBean>()\n        for (peer in peers) {\n            val endpoint = peer[\"Endpoint\"]\n            if (endpoint.isNullOrBlank() || !endpoint.contains(\":\")) {\n                continue\n            }\n\n            val peerBean = bean.clone()\n            peerBean.serverAddress = endpoint.substringBeforeLast(\":\")\n            peerBean.serverPort = endpoint.substringAfterLast(\":\").toIntOrNull() ?: continue\n            peerBean.peerPublicKey = peer[\"PublicKey\"] ?: continue\n            peerBean.peerPreSharedKey = peer[\"PresharedKey\"]\n            beans.add(peerBean.applyDefaultValues())\n        }\n        if (beans.isEmpty()) error(\"Empty available peer list\")\n        return beans\n    }\n\n    fun parseJSON(json: Any): List<AbstractBean> {\n        val proxies = ArrayList<AbstractBean>()\n\n        if (json is JSONObject) {\n            when {\n                json.has(\"server\") && (json.has(\"up\") || json.has(\"up_mbps\")) -> {\n                    return listOf(json.parseHysteria1Json())\n                }\n\n                json.has(\"method\") -> {\n                    return listOf(json.parseShadowsocks())\n                }\n\n                json.has(\"remote_addr\") -> {\n                    return listOf(json.parseTrojanGo())\n                }\n\n                json.has(\"outbounds\") -> {\n                    return json.getJSONArray(\"outbounds\")\n                        .filterIsInstance<JSONObject>()\n                        .mapNotNull {\n                            val ty = it.getStr(\"type\")\n                            if (ty == null || ty == \"\" ||\n                                ty == \"dns\" || ty == \"block\" || ty == \"direct\" || ty == \"selector\" || ty == \"urltest\"\n                            ) {\n                                null\n                            } else {\n                                it\n                            }\n                        }.map {\n                            ConfigBean().apply {\n                                applyDefaultValues()\n                                type = 1\n                                config = it.toStringPretty()\n                                name = it.getStr(\"tag\")\n                            }\n                        }\n                }\n\n                json.has(\"server\") && json.has(\"server_port\") -> {\n                    return listOf(ConfigBean().applyDefaultValues().apply {\n                        type = 1\n                        config = json.toStringPretty()\n                    })\n                }\n            }\n        } else {\n            json as JSONArray\n            json.forEach { _, it ->\n                if (isJsonObjectValid(it)) {\n                    proxies.addAll(parseJSON(it))\n                }\n            }\n        }\n\n        proxies.forEach { it.initializeDefaultValues() }\n        return proxies\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Asyncs.kt",
    "content": "@file:Suppress(\"EXPERIMENTAL_API_USAGE\")\n\npackage io.nekohasekai.sagernet.ktx\n\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.lifecycleScope\nimport kotlinx.coroutines.*\n\nfun block(block: suspend CoroutineScope.() -> Unit): suspend CoroutineScope.() -> Unit {\n    return block\n}\n\nfun runOnDefaultDispatcher(block: suspend CoroutineScope.() -> Unit) =\n    GlobalScope.launch(Dispatchers.Default, block = block)\n\nfun Fragment.runOnLifecycleDispatcher(block: suspend CoroutineScope.() -> Unit) =\n    lifecycleScope.launch(Dispatchers.Default, block = block)\n\nsuspend fun <T> onDefaultDispatcher(block: suspend CoroutineScope.() -> T) =\n    withContext(Dispatchers.Default, block = block)\n\nfun runOnIoDispatcher(block: suspend CoroutineScope.() -> Unit) =\n    GlobalScope.launch(Dispatchers.IO, block = block)\n\nsuspend fun <T> onIoDispatcher(block: suspend CoroutineScope.() -> T) =\n    withContext(Dispatchers.IO, block = block)\n\nfun runOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) =\n    GlobalScope.launch(Dispatchers.Main.immediate, block = block)\n\nsuspend fun <T> onMainDispatcher(block: suspend CoroutineScope.() -> T) =\n    withContext(Dispatchers.Main.immediate, block = block)\n\nfun runBlockingOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) {\n    runBlocking {\n        GlobalScope.launch(Dispatchers.Main.immediate, block = block)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Browsers.kt",
    "content": "  package io.nekohasekai.sagernet.ktx\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.browser.customtabs.CustomTabColorSchemeParams\nimport androidx.browser.customtabs.CustomTabsIntent\nimport io.nekohasekai.sagernet.R\n\nfun Context.launchCustomTab(link: String) {\n    CustomTabsIntent.Builder().apply {\n        setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)\n        setColorSchemeParams(\n            CustomTabsIntent.COLOR_SCHEME_LIGHT,\n            CustomTabColorSchemeParams.Builder().apply {\n                setToolbarColor(getColorAttr(R.attr.colorPrimary))\n            }.build()\n        )\n        setColorSchemeParams(\n            CustomTabsIntent.COLOR_SCHEME_DARK,\n            CustomTabColorSchemeParams.Builder().apply {\n                setToolbarColor(getColorAttr(R.attr.colorPrimary))\n            }.build()\n        )\n    }.build().apply {\n        if (intent.resolveActivity(packageManager) != null) {\n            launchUrl(this@launchCustomTab, Uri.parse(link))\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Dialogs.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport android.app.Activity\nimport android.content.Context\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.Fragment\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\n\nfun Context.alert(text: String): AlertDialog {\n    return MaterialAlertDialogBuilder(this).setTitle(R.string.error_title)\n        .setMessage(text)\n        .setPositiveButton(android.R.string.ok, null)\n        .create()\n}\n\nfun Fragment.alert(text: String) = requireContext().alert(text)\n\nfun AlertDialog.tryToShow() {\n    try {\n        val activity = context as Activity\n        if (!activity.isFinishing) {\n            show()\n        }\n    } catch (e: Exception) {\n        Logs.e(e)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Dimens.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport android.content.res.Resources\nimport kotlin.math.ceil\n\nprivate val density = Resources.getSystem().displayMetrics.density\n\nfun dp2pxf(dpValue: Int): Float {\n    return density * dpValue\n}\n\nfun dp2px(dpValue: Int): Int {\n    return ceil(dp2pxf(dpValue)).toInt()\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Formats.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport com.google.gson.JsonParser\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.fmt.Serializable\nimport io.nekohasekai.sagernet.fmt.http.parseHttp\nimport io.nekohasekai.sagernet.fmt.hysteria.parseHysteria1\nimport io.nekohasekai.sagernet.fmt.hysteria.parseHysteria2\nimport io.nekohasekai.sagernet.fmt.naive.parseNaive\nimport io.nekohasekai.sagernet.fmt.parseUniversal\nimport io.nekohasekai.sagernet.fmt.shadowsocks.parseShadowsocks\nimport io.nekohasekai.sagernet.fmt.socks.parseSOCKS\nimport io.nekohasekai.sagernet.fmt.trojan.parseTrojan\nimport io.nekohasekai.sagernet.fmt.tuic.parseTuic\nimport io.nekohasekai.sagernet.fmt.trojan_go.parseTrojanGo\nimport io.nekohasekai.sagernet.fmt.v2ray.parseV2Ray\nimport moe.matsuri.nb4a.proxy.anytls.parseAnytls\nimport moe.matsuri.nb4a.utils.JavaUtil.gson\nimport moe.matsuri.nb4a.utils.Util\nimport okhttp3.HttpUrl\nimport org.json.JSONArray\nimport org.json.JSONException\nimport org.json.JSONObject\n\n// JSON & Base64\n\nfun JSONObject.toStringPretty(): String {\n    return gson.toJson(JsonParser.parseString(this.toString()))\n}\n\ninline fun <reified T : Any> JSONArray.filterIsInstance(): List<T> {\n    val list = mutableListOf<T>()\n    for (i in 0 until this.length()) {\n        if (this[i] is T) list.add(this[i] as T)\n    }\n    return list\n}\n\ninline fun JSONArray.forEach(action: (Int, Any) -> Unit) {\n    for (i in 0 until this.length()) {\n        action(i, this[i])\n    }\n}\n\ninline fun JSONObject.forEach(action: (String, Any) -> Unit) {\n    for (k in this.keys()) {\n        action(k, this.get(k))\n    }\n}\n\nfun isJsonObjectValid(j: Any): Boolean {\n    if (j is JSONObject) return true\n    if (j is JSONArray) return true\n    try {\n        JSONObject(j as String)\n    } catch (ex: JSONException) {\n        try {\n            JSONArray(j)\n        } catch (ex1: JSONException) {\n            return false\n        }\n    }\n    return true\n}\n\n// wtf hutool\nfun JSONObject.getStr(name: String): String? {\n    val obj = this.opt(name) ?: return null\n    if (obj is String) {\n        if (obj.isBlank()) {\n            return null\n        }\n        return obj\n    } else {\n        return null\n    }\n}\n\nfun JSONObject.getBool(name: String): Boolean? {\n    return try {\n        getBoolean(name)\n    } catch (ignored: Exception) {\n        null\n    }\n}\n\n\n// 重名了喵\nfun JSONObject.getIntNya(name: String): Int? {\n    return try {\n        getInt(name)\n    } catch (ignored: Exception) {\n        null\n    }\n}\n\n\nfun String.decodeBase64UrlSafe(): String {\n    return String(Util.b64Decode(this))\n}\n\n// Sub\n\nclass SubscriptionFoundException(val link: String) : RuntimeException()\n\nsuspend fun parseProxies(text: String): List<AbstractBean> {\n    val links = text.split('\\n').flatMap { it.trim().split(' ') }\n    val linksByLine = text.split('\\n').map { it.trim() }\n\n    val entities = ArrayList<AbstractBean>()\n    val entitiesByLine = ArrayList<AbstractBean>()\n\n    fun String.parseLink(entities: ArrayList<AbstractBean>) {\n        if (startsWith(\"clash://install-config?\") || startsWith(\"sn://subscription?\")) {\n            throw SubscriptionFoundException(this)\n        }\n\n        if (startsWith(\"sn://\")) {\n            Logs.d(\"Try parse universal link: $this\")\n            runCatching {\n                entities.add(parseUniversal(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"socks://\") || startsWith(\"socks4://\") || startsWith(\"socks4a://\") || startsWith(\n                \"socks5://\"\n            )\n        ) {\n            Logs.d(\"Try parse socks link: $this\")\n            runCatching {\n                entities.add(parseSOCKS(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (matches(\"(http|https)://.*\".toRegex())) {\n            Logs.d(\"Try parse http link: $this\")\n            runCatching {\n                entities.add(parseHttp(this))\n            }.onFailure {\n                Logs.w(it)\n                val clashUrl = HttpUrl.Builder()\n                    .scheme(\"https\")\n                    .host(\"install-config\")\n                    .addQueryParameter(\"url\", this)\n                    .build()\n                    .toString()\n                    .replaceFirst(\"https://\", \"clash://\")\n                throw (SubscriptionFoundException(clashUrl))\n            }\n        } else if (startsWith(\"vmess://\")) {\n            Logs.d(\"Try parse v2ray link: $this\")\n            runCatching {\n                entities.add(parseV2Ray(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"vless://\")) {\n            Logs.d(\"Try parse vless link: $this\")\n            runCatching {\n                entities.add(parseV2Ray(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"trojan://\")) {\n            Logs.d(\"Try parse trojan link: $this\")\n            runCatching {\n                entities.add(parseTrojan(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"trojan-go://\")) {\n            Logs.d(\"Try parse trojan-go link: $this\")\n            runCatching {\n                entities.add(parseTrojanGo(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"ss://\")) {\n            Logs.d(\"Try parse shadowsocks link: $this\")\n            runCatching {\n                entities.add(parseShadowsocks(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"naive+\")) {\n            Logs.d(\"Try parse naive link: $this\")\n            runCatching {\n                entities.add(parseNaive(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"hysteria://\")) {\n            Logs.d(\"Try parse hysteria1 link: $this\")\n            runCatching {\n                entities.add(parseHysteria1(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"hysteria2://\") || startsWith(\"hy2://\")) {\n            Logs.d(\"Try parse hysteria2 link: $this\")\n            runCatching {\n                entities.add(parseHysteria2(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"tuic://\")) {\n            Logs.d(\"Try parse TUIC link: $this\")\n            runCatching {\n                entities.add(parseTuic(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        } else if (startsWith(\"anytls://\")) {\n            Logs.d(\"Try parse anytls link: $this\")\n            runCatching {\n                entities.add(parseAnytls(this))\n            }.onFailure {\n                Logs.w(it)\n            }\n        }\n    }\n\n    for (link in links) {\n        link.parseLink(entities)\n    }\n    for (link in linksByLine) {\n        link.parseLink(entitiesByLine)\n    }\n//    var isBadLink = false\n    if (entities.onEach { it.initializeDefaultValues() }.size == entitiesByLine.onEach { it.initializeDefaultValues() }.size) run test@{\n        entities.forEachIndexed { index, bean ->\n            val lineBean = entitiesByLine[index]\n            if (bean == lineBean && bean.displayName() != lineBean.displayName()) {\n//                isBadLink = true\n                return@test\n            }\n        }\n    }\n    return if (entities.size > entitiesByLine.size) entities else entitiesByLine\n}\n\nfun <T : Serializable> T.applyDefaultValues(): T {\n    initializeDefaultValues()\n    return this\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Kryos.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport android.os.Parcel\nimport android.os.Parcelable\nimport com.esotericsoftware.kryo.io.ByteBufferInput\nimport com.esotericsoftware.kryo.io.ByteBufferOutput\nimport java.io.InputStream\nimport java.io.OutputStream\n\n\nfun InputStream.byteBuffer() = ByteBufferInput(this)\nfun OutputStream.byteBuffer() = ByteBufferOutput(this)\n\nfun ByteBufferInput.readStringList(): List<String> {\n    return mutableListOf<String>().apply {\n        repeat(readInt()) {\n            add(readString())\n        }\n    }\n}\n\nfun ByteBufferInput.readStringSet(): Set<String> {\n    return linkedSetOf<String>().apply {\n        repeat(readInt()) {\n            add(readString())\n        }\n    }\n}\n\n\nfun ByteBufferOutput.writeStringList(list: List<String>) {\n    writeInt(list.size)\n    for (str in list) writeString(str)\n}\n\nfun ByteBufferOutput.writeStringList(list: Set<String>) {\n    writeInt(list.size)\n    for (str in list) writeString(str)\n}\n\nfun Parcelable.marshall(): ByteArray {\n    val parcel = Parcel.obtain()\n    writeToParcel(parcel, 0)\n    val bytes = parcel.marshall()\n    parcel.recycle()\n    return bytes\n}\n\nfun ByteArray.unmarshall(): Parcel {\n    val parcel = Parcel.obtain()\n    parcel.unmarshall(this, 0, size)\n    parcel.setDataPosition(0) // This is extremely important!\n    return parcel\n}\n\nfun <T> ByteArray.unmarshall(constructor: (Parcel) -> T): T {\n    val parcel = unmarshall()\n    val result = constructor(parcel)\n    parcel.recycle()\n    return result\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Layouts.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport android.graphics.Rect\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ui.MainActivity\n\nclass FixedLinearLayoutManager(val recyclerView: RecyclerView) :\n    LinearLayoutManager(recyclerView.context, RecyclerView.VERTICAL, false) {\n\n    override fun onLayoutChildren(recycler: RecyclerView.Recycler?, state: RecyclerView.State?) {\n        try {\n            super.onLayoutChildren(recycler, state)\n        } catch (ignored: IndexOutOfBoundsException) {\n        }\n    }\n\n    private var listenerDisabled = false\n\n    override fun scrollVerticallyBy(\n        dx: Int, recycler: RecyclerView.Recycler,\n        state: RecyclerView.State\n    ): Int {\n        // Matsuri style\n        if (!DataStore.showBottomBar) return super.scrollVerticallyBy(dx, recycler, state)\n\n        // SagerNet Style\n        val scrollRange = super.scrollVerticallyBy(dx, recycler, state)\n        if (listenerDisabled) return scrollRange\n        val activity = recyclerView.context as? MainActivity\n        if (activity == null) {\n            listenerDisabled = true\n            return scrollRange\n        }\n\n        val overscroll = dx - scrollRange\n        if (overscroll > 0) {\n            val view =\n                (recyclerView.findViewHolderForAdapterPosition(findLastVisibleItemPosition())\n                    ?: return scrollRange).itemView\n            val itemLocation = Rect().also { view.getGlobalVisibleRect(it) }\n            val fabLocation = Rect().also { activity.binding.fab.getGlobalVisibleRect(it) }\n            if (!itemLocation.contains(fabLocation.left, fabLocation.top) && !itemLocation.contains(\n                    fabLocation.right,\n                    fabLocation.bottom\n                )\n            ) {\n                return scrollRange\n            }\n            activity.binding.fab.apply {\n                if (isShown) hide()\n            }\n        } else {\n            /*val screen = Rect().also { activity.window.decorView.getGlobalVisibleRect(it) }\n            val location = Rect().also { activity.stats.getGlobalVisibleRect(it) }\n            if (screen.bottom < location.bottom) {\n                return scrollRange\n            }\n            val height = location.bottom - location.top\n            val mH = activity.stats.measuredHeight\n\n            if (mH > height) {\n                return scrollRange\n            }*/\n\n            activity.binding.fab.apply {\n                if (!isShown) show()\n            }\n        }\n        return scrollRange\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Logs.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport libcore.Libcore\nimport java.io.InputStream\nimport java.io.OutputStream\n\nobject Logs {\n\n    private fun mkTag(): String {\n        val stackTrace = Thread.currentThread().stackTrace\n        return stackTrace[4].className.substringAfterLast(\".\")\n    }\n\n    // level int use logrus.go\n\n    fun d(message: String) {\n        Libcore.nekoLogPrintln(\"[Debug] [${mkTag()}] $message\")\n    }\n\n    fun d(message: String, exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Debug] [${mkTag()}] $message\" + \"\\n\" + exception.stackTraceToString())\n    }\n\n    fun i(message: String) {\n        Libcore.nekoLogPrintln(\"[Info] [${mkTag()}] $message\")\n    }\n\n    fun i(message: String, exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Info] [${mkTag()}] $message\" + \"\\n\" + exception.stackTraceToString())\n    }\n\n    fun w(message: String) {\n        Libcore.nekoLogPrintln(\"[Warning] [${mkTag()}] $message\")\n    }\n\n    fun w(message: String, exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Warning] [${mkTag()}] $message\" + \"\\n\" + exception.stackTraceToString())\n    }\n\n    fun w(exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Warning] [${mkTag()}] \" + exception.stackTraceToString())\n    }\n\n    fun e(message: String) {\n        Libcore.nekoLogPrintln(\"[Error] [${mkTag()}] $message\")\n    }\n\n    fun e(message: String, exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Error] [${mkTag()}] $message\" + \"\\n\" + exception.stackTraceToString())\n    }\n\n    fun e(exception: Throwable) {\n        Libcore.nekoLogPrintln(\"[Error] [${mkTag()}] \" + exception.stackTraceToString())\n    }\n\n}\n\nfun InputStream.use(out: OutputStream) {\n    use { input ->\n        out.use { output ->\n            input.copyTo(output)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Nets.kt",
    "content": "@file:Suppress(\"SpellCheckingInspection\")\n\npackage io.nekohasekai.sagernet.ktx\n\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport moe.matsuri.nb4a.utils.NGUtil\nimport okhttp3.HttpUrl\nimport java.net.InetSocketAddress\nimport java.net.Socket\n\nfun linkBuilder() = HttpUrl.Builder().scheme(\"https\")\n\nfun HttpUrl.Builder.toLink(scheme: String, appendDefaultPort: Boolean = true): String {\n    var url = build()\n    val defaultPort = HttpUrl.defaultPort(url.scheme)\n    var replace = false\n    if (appendDefaultPort && url.port == defaultPort) {\n        url = url.newBuilder().port(14514).build()\n        replace = true\n    }\n    return url.toString().replace(\"${url.scheme}://\", \"$scheme://\").let {\n        if (replace) it.replace(\":14514\", \":$defaultPort\") else it\n    }\n}\n\nfun String.isIpAddress(): Boolean {\n    return NGUtil.isIpv4Address(this) || NGUtil.isIpv6Address(this)\n}\n\nfun String.isIpAddressV6(): Boolean {\n    return NGUtil.isIpv6Address(this)\n}\n\n// [2001:4860:4860::8888] -> 2001:4860:4860::8888\nfun String.unwrapIPV6Host(): String {\n    if (startsWith(\"[\") && endsWith(\"]\")) {\n        return substring(1, length - 1).unwrapIPV6Host()\n    }\n    return this\n}\n\n// [2001:4860:4860::8888] or 2001:4860:4860::8888 -> [2001:4860:4860::8888]\nfun String.wrapIPV6Host(): String {\n    val unwrapped = this.unwrapIPV6Host()\n    if (unwrapped.isIpAddressV6()) {\n        return \"[$unwrapped]\"\n    } else {\n        return this\n    }\n}\n\nfun AbstractBean.wrapUri(): String {\n    return \"${finalAddress.wrapIPV6Host()}:$finalPort\"\n}\n\nfun mkPort(): Int {\n    val socket = Socket()\n    socket.reuseAddress = true\n    socket.bind(InetSocketAddress(0))\n    val port = socket.localPort\n    socket.close()\n    return port\n}\n\nconst val USER_AGENT = \"NekoBox/Android/\" + BuildConfig.VERSION_NAME + \" (Prefer ClashMeta Format)\"\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Preferences.kt",
    "content": "package io.nekohasekai.sagernet.ktx\n\nimport androidx.preference.PreferenceDataStore\nimport kotlin.reflect.KProperty\n\nfun PreferenceDataStore.string(\n    name: String,\n    defaultValue: () -> String = { \"\" },\n) = PreferenceProxy(name, defaultValue, ::getString, ::putString)\n\nfun PreferenceDataStore.boolean(\n    name: String,\n    defaultValue: () -> Boolean = { false },\n) = PreferenceProxy(name, defaultValue, ::getBoolean, ::putBoolean)\n\nfun PreferenceDataStore.int(\n    name: String,\n    defaultValue: () -> Int = { 0 },\n) = PreferenceProxy(name, defaultValue, ::getInt, ::putInt)\n\nfun PreferenceDataStore.stringSet(\n    name: String,\n    defaultValue: () -> Set<String> = { setOf() },\n) = PreferenceProxy(name, defaultValue, ::getStringSet, ::putStringSet)\n\nfun PreferenceDataStore.stringToInt(\n    name: String,\n    defaultValue: () -> Int = { 0 },\n) = PreferenceProxy(name, defaultValue, { key, default ->\n    getString(key, \"$default\")?.toIntOrNull() ?: default\n}, { key, value -> putString(key, \"$value\") })\n\nfun PreferenceDataStore.stringToIntIfExists(\n    name: String,\n    defaultValue: () -> Int = { 0 },\n) = PreferenceProxy(name, defaultValue, { key, default ->\n    getString(key, \"$default\")?.toIntOrNull() ?: default\n}, { key, value -> putString(key, value.takeIf { it > 0 }?.toString() ?: \"\") })\n\nfun PreferenceDataStore.long(\n    name: String,\n    defaultValue: () -> Long = { 0L },\n) = PreferenceProxy(name, defaultValue, ::getLong, ::putLong)\n\nfun PreferenceDataStore.stringToLong(\n    name: String,\n    defaultValue: () -> Long = { 0L },\n) = PreferenceProxy(name, defaultValue, { key, default ->\n    getString(key, \"$default\")?.toLongOrNull() ?: default\n}, { key, value -> putString(key, \"$value\") })\n\nclass PreferenceProxy<T>(\n    val name: String,\n    val defaultValue: () -> T,\n    val getter: (String, T) -> T?,\n    val setter: (String, value: T) -> Unit,\n) {\n\n    operator fun setValue(thisObj: Any?, property: KProperty<*>, value: T) = setter(name, value)\n    operator fun getValue(thisObj: Any?, property: KProperty<*>) = getter(name, defaultValue())!!\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ktx/Utils.kt",
    "content": "@file:SuppressLint(\"SoonBlockedPrivateApi\")\n\npackage io.nekohasekai.sagernet.ktx\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.annotation.SuppressLint\nimport android.content.ActivityNotFoundException\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.res.Resources\nimport android.os.Build\nimport android.system.Os\nimport android.system.OsConstants\nimport android.util.TypedValue\nimport android.view.View\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.preference.Preference\nimport androidx.recyclerview.widget.LinearSmoothScroller\nimport androidx.recyclerview.widget.RecyclerView\nimport com.jakewharton.processphoenix.ProcessPhoenix\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ui.MainActivity\nimport io.nekohasekai.sagernet.ui.ThemedActivity\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport moe.matsuri.nb4a.utils.NGUtil\nimport java.io.FileDescriptor\nimport java.net.HttpURLConnection\nimport java.net.InetAddress\nimport java.net.Socket\nimport java.net.URLEncoder\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.concurrent.atomic.AtomicLong\nimport java.util.concurrent.atomic.AtomicReference\nimport kotlin.coroutines.Continuation\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.reflect.KMutableProperty0\nimport kotlin.reflect.KProperty\nimport kotlin.reflect.KProperty0\n\nfun String?.blankAsNull(): String? = if (isNullOrBlank()) null else this\n\ninline fun <T> Iterable<T>.forEachTry(action: (T) -> Unit) {\n    var result: Exception? = null\n    for (element in this) try {\n        action(element)\n    } catch (e: Exception) {\n        if (result == null) result = e else result.addSuppressed(e)\n    }\n    if (result != null) {\n        throw result\n    }\n}\n\nval Throwable.readableMessage\n    get() = localizedMessage.takeIf { !it.isNullOrBlank() } ?: javaClass.simpleName\n\n/**\n * https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466\n */\n\nprivate val socketGetFileDescriptor = Socket::class.java.getDeclaredMethod(\"getFileDescriptor\\$\")\nval Socket.fileDescriptor get() = socketGetFileDescriptor.invoke(this) as FileDescriptor\n\nprivate val getInt = FileDescriptor::class.java.getDeclaredMethod(\"getInt$\")\nval FileDescriptor.int get() = getInt.invoke(this) as Int\n\nsuspend fun <T> HttpURLConnection.useCancellable(block: suspend HttpURLConnection.() -> T): T {\n    return suspendCancellableCoroutine { cont ->\n        cont.invokeOnCancellation {\n            if (Build.VERSION.SDK_INT >= 26) disconnect() else GlobalScope.launch(Dispatchers.IO) { disconnect() }\n        }\n        GlobalScope.launch(Dispatchers.IO) {\n            try {\n                cont.resume(block())\n            } catch (e: Throwable) {\n                cont.resumeWithException(e)\n            }\n        }\n    }\n}\n\nfun parsePort(str: String?, default: Int, min: Int = 1025): Int {\n    val value = str?.toIntOrNull() ?: default\n    return if (value < min || value > 65535) default else value\n}\n\nfun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver =\n    object : BroadcastReceiver() {\n        override fun onReceive(context: Context, intent: Intent) = callback(context, intent)\n    }\n\nfun Context.listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit) =\n    object : BroadcastReceiver() {\n        override fun onReceive(context: Context, intent: Intent) {\n            callback()\n            if (onetime) context.unregisterReceiver(this)\n        }\n    }.apply {\n        registerReceiver(this, IntentFilter().apply {\n            addAction(Intent.ACTION_PACKAGE_ADDED)\n            addAction(Intent.ACTION_PACKAGE_REMOVED)\n            addDataScheme(\"package\")\n        })\n    }\n\n/**\n * Based on: https://stackoverflow.com/a/26348729/2245107\n */\nfun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {\n    val typedValue = TypedValue()\n    if (!resolveAttribute(resId, typedValue, true)) throw Resources.NotFoundException()\n    return typedValue.resourceId\n}\n\nfun Preference.remove() = parent!!.removePreference(this)\n\n/**\n * A slightly more performant variant of parseNumericAddress.\n *\n * Bug in Android 9.0 and lower: https://issuetracker.google.com/issues/123456213\n */\n\nprivate val parseNumericAddress by lazy {\n    InetAddress::class.java.getDeclaredMethod(\"parseNumericAddress\", String::class.java).apply {\n        isAccessible = true\n    }\n}\n\nfun String?.parseNumericAddress(): InetAddress? =\n    Os.inet_pton(OsConstants.AF_INET, this) ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let {\n        if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke(\n            null, this\n        ) as InetAddress\n    }\n\n@JvmOverloads\nfun DialogFragment.showAllowingStateLoss(fragmentManager: FragmentManager, tag: String? = null) {\n    if (!fragmentManager.isStateSaved) show(fragmentManager, tag)\n}\n\nfun String.pathSafe(): String {\n    // \" \" encoded as +\n    return URLEncoder.encode(this, \"UTF-8\")\n}\n\nfun String.urlSafe(): String {\n    return URLEncoder.encode(this, \"UTF-8\").replace(\"+\", \"%20\")\n}\n\nfun String.unUrlSafe(): String {\n    return NGUtil.urlDecode(this)\n}\n\nfun RecyclerView.scrollTo(index: Int, force: Boolean = false) {\n    if (force) post {\n        scrollToPosition(index)\n    }\n    postDelayed({\n        try {\n            layoutManager?.startSmoothScroll(object : LinearSmoothScroller(context) {\n                init {\n                    targetPosition = index\n                }\n\n                override fun getVerticalSnapPreference(): Int {\n                    return SNAP_TO_START\n                }\n            })\n        } catch (ignored: IllegalArgumentException) {\n        }\n    }, 300L)\n}\n\nval app get() = SagerNet.application\n\nval shortAnimTime by lazy {\n    app.resources.getInteger(android.R.integer.config_shortAnimTime).toLong()\n}\n\nfun View.crossFadeFrom(other: View) {\n    clearAnimation()\n    other.clearAnimation()\n    if (isVisible && other.isGone) return\n    alpha = 0F\n    visibility = View.VISIBLE\n    animate().alpha(1F).duration = shortAnimTime\n    other.animate().alpha(0F).setListener(object : AnimatorListenerAdapter() {\n        override fun onAnimationEnd(animation: Animator) {\n            other.visibility = View.GONE\n        }\n    }).duration = shortAnimTime\n}\n\n\nfun Fragment.snackbar(textId: Int) = (requireActivity() as MainActivity).snackbar(textId)\nfun Fragment.snackbar(text: CharSequence) = (requireActivity() as MainActivity).snackbar(text)\n\nfun ThemedActivity.startFilesForResult(\n    launcher: ActivityResultLauncher<String>, input: String\n) {\n    try {\n        return launcher.launch(input)\n    } catch (_: ActivityNotFoundException) {\n    } catch (_: SecurityException) {\n    }\n    snackbar(getString(R.string.file_manager_missing)).show()\n}\n\nfun Fragment.startFilesForResult(\n    launcher: ActivityResultLauncher<String>, input: String\n) {\n    try {\n        return launcher.launch(input)\n    } catch (_: ActivityNotFoundException) {\n    } catch (_: SecurityException) {\n    }\n    (requireActivity() as ThemedActivity).snackbar(getString(R.string.file_manager_missing)).show()\n}\n\nfun Fragment.needReload() {\n    if (DataStore.serviceState.started) {\n        snackbar(getString(R.string.need_reload)).setAction(R.string.apply) {\n            SagerNet.reloadService()\n        }.show()\n    }\n}\n\nfun Fragment.needRestart() {\n    snackbar(R.string.need_restart).setAction(R.string.apply) {\n        triggerFullRestart(requireContext())\n    }.show()\n}\n\nfun triggerFullRestart(ctx: Context) {\n    runOnDefaultDispatcher {\n        SagerNet.stopService()\n        delay(500)\n        SagerConnection.restartingApp = true\n        val connection = SagerConnection(SagerConnection.CONNECTION_ID_RESTART_BG)\n        connection.connect(ctx, RestartCallback {\n            ProcessPhoenix.triggerRebirth(ctx, Intent(ctx, MainActivity::class.java))\n        })\n    }\n}\n\nprivate class RestartCallback(val callback: () -> Unit) : SagerConnection.Callback {\n    override fun stateChanged(\n        state: BaseService.State,\n        profileName: String?,\n        msg: String?\n    ) {\n    }\n\n    override fun onServiceConnected(service: ISagerNetService) {\n        callback()\n    }\n}\n\nfun Context.getColour(@ColorRes colorRes: Int): Int {\n    return ContextCompat.getColor(this, colorRes)\n}\n\nfun Context.getColorAttr(@AttrRes resId: Int): Int {\n    return ContextCompat.getColor(this, TypedValue().also {\n        theme.resolveAttribute(resId, it, true)\n    }.resourceId)\n}\n\nval isExpert: Boolean by lazy { BuildConfig.DEBUG || DataStore.isExpert }\nconst val isOss = BuildConfig.FLAVOR == \"oss\"\nconst val isPlay = BuildConfig.FLAVOR == \"play\"\nconst val isPreview = BuildConfig.FLAVOR == \"preview\"\n\nfun <T> Continuation<T>.tryResume(value: T) {\n    try {\n        resumeWith(Result.success(value))\n    } catch (ignored: IllegalStateException) {\n    }\n}\n\nfun <T> Continuation<T>.tryResumeWithException(exception: Throwable) {\n    try {\n        resumeWith(Result.failure(exception))\n    } catch (ignored: IllegalStateException) {\n    }\n}\n\noperator fun <F> KProperty0<F>.getValue(thisRef: Any?, property: KProperty<*>): F = get()\noperator fun <F> KMutableProperty0<F>.setValue(\n    thisRef: Any?, property: KProperty<*>, value: F\n) = set(value)\n\noperator fun AtomicBoolean.getValue(thisRef: Any?, property: KProperty<*>): Boolean = get()\noperator fun AtomicBoolean.setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) =\n    set(value)\n\noperator fun AtomicInteger.getValue(thisRef: Any?, property: KProperty<*>): Int = get()\noperator fun AtomicInteger.setValue(thisRef: Any?, property: KProperty<*>, value: Int) = set(value)\n\noperator fun AtomicLong.getValue(thisRef: Any?, property: KProperty<*>): Long = get()\noperator fun AtomicLong.setValue(thisRef: Any?, property: KProperty<*>, value: Long) = set(value)\n\noperator fun <T> AtomicReference<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get()\noperator fun <T> AtomicReference<T>.setValue(thisRef: Any?, property: KProperty<*>, value: T) =\n    set(value)\n\noperator fun <K, V> Map<K, V>.getValue(thisRef: K, property: KProperty<*>) = get(thisRef)\noperator fun <K, V> MutableMap<K, V>.setValue(thisRef: K, property: KProperty<*>, value: V?) {\n\n    if (value != null) {\n\n        put(thisRef, value)\n\n    } else {\n\n        remove(thisRef)\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/plugin/PluginManager.kt",
    "content": "package io.nekohasekai.sagernet.plugin\n\nimport android.content.pm.ComponentInfo\nimport android.content.pm.ProviderInfo\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.ktx.Logs\nimport moe.matsuri.nb4a.plugin.Plugins\nimport java.io.File\nimport java.io.FileNotFoundException\n\nobject PluginManager {\n\n    class PluginNotFoundException(val plugin: String) : FileNotFoundException(plugin),\n        BaseService.ExpectedException {\n        override fun getLocalizedMessage() =\n            SagerNet.application.getString(R.string.plugin_unknown, plugin)\n    }\n\n    data class InitResult(\n        val path: String,\n        val info: ProviderInfo,\n    )\n\n    @Throws(Throwable::class)\n    fun init(pluginId: String): InitResult? {\n        if (pluginId.isEmpty()) return null\n        var throwable: Throwable? = null\n\n        try {\n            val result = initNative(pluginId)\n            if (result != null) return result\n        } catch (t: Throwable) {\n            throwable = t\n            Logs.w(t)\n        }\n\n        throw throwable ?: PluginNotFoundException(pluginId)\n    }\n\n    private fun initNative(pluginId: String): InitResult? {\n        val info = Plugins.getPlugin(pluginId) ?: return null\n\n        // internal so\n        if (info.applicationInfo == null) {\n            try {\n                initNativeInternal(pluginId)?.let { return InitResult(it, info) }\n            } catch (t: Throwable) {\n                Logs.w(\"initNativeInternal failed\", t)\n            }\n            return null\n        }\n\n        try {\n            initNativeFaster(info)?.let { return InitResult(it, info) }\n        } catch (t: Throwable) {\n            Logs.w(\"initNativeFaster failed\", t)\n        }\n\n        Logs.w(\"Init native returns empty result\")\n        return null\n    }\n\n    private fun initNativeInternal(pluginId: String): String? {\n        fun soIfExist(soName: String): String? {\n            val f = File(SagerNet.application.applicationInfo.nativeLibraryDir, soName)\n            if (f.canExecute()) {\n                return f.absolutePath\n            }\n            return null\n        }\n        return when (pluginId) {\n            \"hysteria-plugin\" -> soIfExist(\"libhysteria.so\")\n            \"hysteria2-plugin\" -> soIfExist(\"libhysteria2.so\")\n            else -> null\n        }\n    }\n\n    private fun initNativeFaster(provider: ProviderInfo): String? {\n        return provider.loadString(Plugins.METADATA_KEY_EXECUTABLE_PATH)\n            ?.let { relativePath ->\n                File(provider.applicationInfo.nativeLibraryDir).resolve(relativePath).apply {\n                    check(canExecute())\n                }.absolutePath\n            }\n    }\n\n    fun ComponentInfo.loadString(key: String) = when (val value = metaData.get(key)) {\n        is String -> value\n        is Int -> SagerNet.application.packageManager.getResourcesForApplication(applicationInfo)\n            .getString(value)\n\n        null -> null\n        else -> error(\"meta-data $key has invalid type ${value.javaClass}\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/AboutFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.app.Activity\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.PowerManager\nimport android.provider.Settings\nimport android.text.util.Linkify\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.result.component1\nimport androidx.activity.result.component2\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.RecyclerView\nimport com.danielstone.materialaboutlibrary.MaterialAboutFragment\nimport com.danielstone.materialaboutlibrary.items.MaterialAboutActionItem\nimport com.danielstone.materialaboutlibrary.model.MaterialAboutCard\nimport com.danielstone.materialaboutlibrary.model.MaterialAboutList\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.databinding.LayoutAboutBinding\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.plugin.PluginManager.loadString\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport io.nekohasekai.sagernet.widget.ListListener\nimport libcore.Libcore\nimport moe.matsuri.nb4a.plugin.Plugins\nimport androidx.core.net.toUri\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport moe.matsuri.nb4a.utils.Util\nimport org.json.JSONObject\n\nclass AboutFragment : ToolbarFragment(R.layout.layout_about) {\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val binding = LayoutAboutBinding.bind(view)\n\n        ViewCompat.setOnApplyWindowInsetsListener(view, ListListener)\n        toolbar.setTitle(R.string.menu_about)\n\n        parentFragmentManager.beginTransaction()\n            .replace(R.id.about_fragment_holder, AboutContent())\n            .commitAllowingStateLoss()\n\n        runOnDefaultDispatcher {\n            val license = view.context.assets.open(\"LICENSE\").bufferedReader().readText()\n            onMainDispatcher {\n                binding.license.text = license\n                Linkify.addLinks(binding.license, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)\n            }\n        }\n    }\n\n    class AboutContent : MaterialAboutFragment() {\n\n        val requestIgnoreBatteryOptimizations = registerForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { (resultCode, _) ->\n            if (resultCode == Activity.RESULT_OK) {\n                parentFragmentManager.beginTransaction()\n                    .replace(R.id.about_fragment_holder, AboutContent())\n                    .commitAllowingStateLoss()\n            }\n        }\n\n        override fun getMaterialAboutList(activityContext: Context): MaterialAboutList {\n            return MaterialAboutList.Builder()\n                .addCard(\n                    MaterialAboutCard.Builder()\n                        .outline(false)\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .icon(R.drawable.ic_baseline_update_24)\n                                .text(R.string.app_version)\n                                .subText(SagerNet.appVersionNameForDisplay)\n                                .setOnClickAction {\n                                    requireContext().launchCustomTab(\n                                        \"https://github.com/MatsuriDayo/NekoBoxForAndroid/releases\"\n                                    )\n                                }\n                                .build())\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .text(R.string.check_update_release)\n                                .setOnClickAction {\n                                    checkUpdate(false)\n                                }\n                                .build())\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .text(R.string.check_update_preview)\n                                .setOnClickAction {\n                                    checkUpdate(true)\n                                }\n                                .build())\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .icon(R.drawable.ic_baseline_layers_24)\n                                .text(getString(R.string.version_x, \"sing-box\"))\n                                .subText(Libcore.versionBox())\n                                .setOnClickAction { }\n                                .build())\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .icon(R.drawable.ic_baseline_card_giftcard_24)\n                                .text(R.string.donate)\n                                .subText(R.string.donate_info)\n                                .setOnClickAction {\n                                    requireContext().launchCustomTab(\n                                        \"https://matsuridayo.github.io/index_docs/#donate\"\n                                    )\n                                }\n                                .build())\n                        .apply {\n                            PackageCache.awaitLoadSync()\n                            for ((_, pkg) in PackageCache.installedPluginPackages) {\n                                try {\n                                    val pluginId =\n                                        pkg.providers?.get(0)?.loadString(Plugins.METADATA_KEY_ID)\n                                    if (pluginId.isNullOrBlank()) continue\n                                    addItem(\n                                        MaterialAboutActionItem.Builder()\n                                            .icon(R.drawable.ic_baseline_nfc_24)\n                                            .text(\n                                                getString(\n                                                    R.string.version_x,\n                                                    pluginId\n                                                ) + \" (${Plugins.displayExeProvider(pkg.packageName)})\"\n                                            )\n                                            .subText(\"v\" + pkg.versionName)\n                                            .setOnClickAction {\n                                                startActivity(Intent().apply {\n                                                    action =\n                                                        Settings.ACTION_APPLICATION_DETAILS_SETTINGS\n                                                    data = Uri.fromParts(\n                                                        \"package\", pkg.packageName, null\n                                                    )\n                                                })\n                                            }\n                                            .build())\n                                } catch (e: Exception) {\n                                    Logs.w(e)\n                                }\n                            }\n                        }\n                        .apply {\n                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                                val pm = app.getSystemService(Context.POWER_SERVICE) as PowerManager\n                                if (!pm.isIgnoringBatteryOptimizations(app.packageName)) {\n                                    addItem(\n                                        MaterialAboutActionItem.Builder()\n                                            .icon(R.drawable.ic_baseline_running_with_errors_24)\n                                            .text(R.string.ignore_battery_optimizations)\n                                            .subText(R.string.ignore_battery_optimizations_sum)\n                                            .setOnClickAction {\n                                                requestIgnoreBatteryOptimizations.launch(\n                                                    Intent(\n                                                        Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,\n                                                        \"package:${app.packageName}\".toUri()\n                                                    )\n                                                )\n                                            }\n                                            .build())\n                                }\n                            }\n                        }\n                        .build())\n                .addCard(\n                    MaterialAboutCard.Builder()\n                        .outline(false)\n                        .title(R.string.project)\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .icon(R.drawable.ic_baseline_sanitizer_24)\n                                .text(R.string.github)\n                                .setOnClickAction {\n                                    requireContext().launchCustomTab(\n                                        \"https://github.com/MatsuriDayo/NekoBoxForAndroid\"\n\n                                    )\n                                }\n                                .build())\n                        .addItem(\n                            MaterialAboutActionItem.Builder()\n                                .icon(R.drawable.ic_qu_shadowsocks_foreground)\n                                .text(R.string.telegram)\n                                .setOnClickAction {\n                                    requireContext().launchCustomTab(\n                                        \"https://t.me/MatsuriDayo\"\n                                    )\n                                }\n                                .build())\n                        .build())\n                .build()\n\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n\n            view.findViewById<RecyclerView>(R.id.mal_recyclerview).apply {\n                overScrollMode = RecyclerView.OVER_SCROLL_NEVER\n            }\n        }\n\n        fun checkUpdate(checkPreview: Boolean) {\n            runOnIoDispatcher {\n                try {\n                    val client = Libcore.newHttpClient().apply {\n                        modernTLS()\n                        trySocks5(DataStore.mixedPort)\n                    }\n                    val response = client.newRequest().apply {\n                        if (checkPreview) {\n                            setURL(\"https://api.github.com/repos/MatsuriDayo/NekoBoxForAndroid/releases/tags/preview\")\n                        } else {\n                            setURL(\"https://api.github.com/repos/MatsuriDayo/NekoBoxForAndroid/releases/latest\")\n                        }\n                    }.execute()\n                    val release = JSONObject(Util.getStringBox(response.contentString))\n                    val releaseName = release.getString(\"name\")\n                    val releaseUrl = release.getString(\"html_url\")\n                    var haveUpdate = releaseName.isNotBlank()\n                    haveUpdate = if (isPreview) {\n                        if (checkPreview) {\n                            haveUpdate && releaseName != BuildConfig.PRE_VERSION_NAME\n                        } else {\n                            // User: 1.3.9 pre-1.4.0 Stable: 1.3.9 -> No update\n                            haveUpdate && releaseName != BuildConfig.VERSION_NAME\n                        }\n                    } else {\n                        // User: 1.4.0 Preview: pre-1.4.0 -> No update\n                        // User: 1.4.0 Preview: pre-1.4.1 -> Update\n                        // User: 1.4.0 Stable: 1.4.0 -> No update\n                        // User: 1.4.0 Stable: 1.4.1 -> Update\n                        haveUpdate && !releaseName.contains(BuildConfig.VERSION_NAME)\n                    }\n                    runOnMainDispatcher {\n                        if (haveUpdate) {\n                            val context = requireContext()\n                            MaterialAlertDialogBuilder(context)\n                                .setTitle(R.string.update_dialog_title)\n                                .setMessage(\n                                    context.getString(\n                                        R.string.update_dialog_message,\n                                        SagerNet.appVersionNameForDisplay,\n                                        releaseName\n                                    )\n                                )\n                                .setPositiveButton(R.string.yes) { _, _ ->\n                                    val intent = Intent(Intent.ACTION_VIEW, releaseUrl.toUri())\n                                    context.startActivity(intent)\n                                }\n                                .setNegativeButton(R.string.no, null)\n                                .show()\n                        } else {\n                            Toast.makeText(app, R.string.check_update_no, Toast.LENGTH_SHORT).show()\n                        }\n                    }\n                } catch (e: Exception) {\n                    Logs.w(e)\n                    runOnMainDispatcher {\n                        Toast.makeText(app, e.readableMessage, Toast.LENGTH_SHORT).show()\n                    }\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.util.SparseBooleanArray\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Filter\nimport android.widget.Filterable\nimport androidx.annotation.UiThread\nimport androidx.core.util.contains\nimport androidx.core.util.set\nimport androidx.core.view.ViewCompat\nimport androidx.core.widget.addTextChangedListener\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.DefaultItemAnimator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.snackbar.Snackbar\nimport com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.databinding.LayoutAppListBinding\nimport io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding\nimport io.nekohasekai.sagernet.ktx.crossFadeFrom\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport io.nekohasekai.sagernet.widget.ListListener\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.withContext\nimport kotlin.coroutines.coroutineContext\n\nclass AppListActivity : ThemedActivity() {\n    companion object {\n        private const val SWITCH = \"switch\"\n\n        private val cachedApps\n            get() = PackageCache.installedPackages.toMutableMap().apply {\n                remove(BuildConfig.APPLICATION_ID)\n            }\n    }\n\n    private class ProxiedApp(\n        private val pm: PackageManager, private val appInfo: ApplicationInfo,\n        val packageName: String,\n    ) {\n        val name: CharSequence = appInfo.loadLabel(pm)    // cached for sorting\n        val icon: Drawable get() = appInfo.loadIcon(pm)\n        val uid get() = appInfo.uid\n        val sys get() = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0\n    }\n\n    private inner class AppViewHolder(val binding: LayoutAppsItemBinding) : RecyclerView.ViewHolder(\n        binding.root\n    ),\n        View.OnClickListener {\n        private lateinit var item: ProxiedApp\n\n        init {\n            binding.root.setOnClickListener(this)\n        }\n\n        fun bind(app: ProxiedApp) {\n            item = app\n            binding.itemicon.setImageDrawable(app.icon)\n            binding.title.text = app.name\n            binding.desc.text = \"${app.packageName} (${app.uid})\"\n            handlePayload(listOf(SWITCH))\n        }\n\n        fun handlePayload(payloads: List<String>) {\n            if (payloads.contains(SWITCH)) {\n                val selected = isProxiedApp(item)\n                binding.itemcheck.isChecked = selected\n            }\n        }\n\n        override fun onClick(v: View?) {\n            if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true\n            DataStore.routePackages = apps.filter { isProxiedApp(it) }\n                .joinToString(\"\\n\") { it.packageName }\n            appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH)\n        }\n    }\n\n    private inner class AppsAdapter : RecyclerView.Adapter<AppViewHolder>(),\n        Filterable,\n        FastScrollRecyclerView.SectionedAdapter {\n        var filteredApps = apps\n\n        suspend fun reload() {\n            PackageCache.reload()\n            apps = cachedApps.mapNotNull { (packageName, packageInfo) ->\n                coroutineContext[Job]!!.ensureActive()\n                packageInfo.applicationInfo?.let { ProxiedApp(packageManager, it, packageName) }\n            }.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n        }\n\n        override fun onBindViewHolder(holder: AppViewHolder, position: Int) =\n            holder.bind(filteredApps[position])\n\n        override fun onBindViewHolder(holder: AppViewHolder, position: Int, payloads: List<Any>) {\n            if (payloads.isNotEmpty()) {\n                @Suppress(\"UNCHECKED_CAST\") holder.handlePayload(payloads as List<String>)\n                return\n            }\n\n            onBindViewHolder(holder, position)\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder =\n            AppViewHolder(LayoutAppsItemBinding.inflate(layoutInflater, parent, false))\n\n        override fun getItemCount(): Int = filteredApps.size\n\n        private val filterImpl = object : Filter() {\n            override fun performFiltering(constraint: CharSequence) = FilterResults().apply {\n                var filteredApps = if (constraint.isEmpty()) apps else apps.filter {\n                    it.name.contains(constraint, true) || it.packageName.contains(\n                        constraint, true\n                    ) || it.uid.toString().contains(constraint)\n                }\n                if (!sysApps) filteredApps = filteredApps.filter { !it.sys }\n                count = filteredApps.size\n                values = filteredApps\n            }\n\n            override fun publishResults(constraint: CharSequence, results: FilterResults) {\n                @Suppress(\"UNCHECKED_CAST\")\n                filteredApps = results.values as List<ProxiedApp>\n                notifyDataSetChanged()\n            }\n        }\n\n        override fun getFilter(): Filter = filterImpl\n\n        override fun getSectionName(position: Int): String {\n            return filteredApps[position].name.firstOrNull()?.toString() ?: \"\"\n        }\n\n    }\n\n    private val loading by lazy { findViewById<View>(R.id.loading) }\n\n    private lateinit var binding: LayoutAppListBinding\n    private val proxiedUids = SparseBooleanArray()\n    private var loader: Job? = null\n    private var apps = emptyList<ProxiedApp>()\n    private val appsAdapter = AppsAdapter()\n\n    private fun initProxiedUids(str: String = DataStore.routePackages) {\n        proxiedUids.clear()\n        val apps = cachedApps\n        for (line in str.lineSequence()) {\n            val app = (apps[line] ?: continue)\n            val uid = app.applicationInfo?.uid ?: continue\n            proxiedUids[uid] = true\n        }\n    }\n\n    private fun isProxiedApp(app: ProxiedApp) = proxiedUids[app.uid]\n\n    @UiThread\n    private fun loadApps() {\n        loader?.cancel()\n        loader = lifecycleScope.launchWhenCreated {\n            loading.crossFadeFrom(binding.list)\n            val adapter = binding.list.adapter as AppsAdapter\n            withContext(Dispatchers.IO) { adapter.reload() }\n            adapter.filter.filter(binding.search.text?.toString() ?: \"\")\n            if (apps.isEmpty()) {\n                binding.list.visibility = View.GONE\n                binding.appPlaceholder.root.crossFadeFrom(loading)\n            } else {\n                binding.list.crossFadeFrom(loading)\n            }\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = LayoutAppListBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.appPlaceholder.openSettings.setOnClickListener {\n            val intent =\n                Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\n                    data = android.net.Uri.fromParts(\"package\", packageName, null)\n                }\n            startActivity(intent)\n        }\n\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.apply {\n            setTitle(R.string.select_apps)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        initProxiedUids()\n        binding.list.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)\n        binding.list.itemAnimator = DefaultItemAnimator()\n        binding.list.adapter = appsAdapter\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener)\n\n        binding.search.addTextChangedListener {\n            appsAdapter.filter.filter(it?.toString() ?: \"\")\n        }\n\n        binding.showSystemApps.isChecked = sysApps\n        binding.showSystemApps.setOnCheckedChangeListener { _, isChecked ->\n            sysApps = isChecked\n            appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n        }\n\n        loadApps()\n    }\n\n    private var sysApps = false\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.app_list_menu, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_invert_selections -> {\n                runOnDefaultDispatcher {\n                    for (app in apps) {\n                        if (proxiedUids.contains(app.uid)) {\n                            proxiedUids.delete(app.uid)\n                        } else {\n                            proxiedUids[app.uid] = true\n                        }\n                    }\n                    DataStore.routePackages = apps.filter { isProxiedApp(it) }\n                        .joinToString(\"\\n\") { it.packageName }\n                    apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n                    onMainDispatcher {\n                        appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n                    }\n                }\n\n                return true\n            }\n\n            R.id.action_clear_selections -> {\n                runOnDefaultDispatcher {\n                    proxiedUids.clear()\n                    DataStore.routePackages = \"\"\n                    apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n                    onMainDispatcher {\n                        appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n                    }\n                }\n            }\n\n            R.id.action_export_clipboard -> {\n                val success = SagerNet.trySetPrimaryClip(\"false\\n${DataStore.routePackages}\")\n                Snackbar.make(\n                    binding.list,\n                    if (success) R.string.action_export_msg else R.string.action_export_err,\n                    Snackbar.LENGTH_LONG\n                ).show()\n                return true\n            }\n\n            R.id.action_import_clipboard -> {\n                val proxiedAppString =\n                    SagerNet.clipboard.primaryClip?.getItemAt(0)?.text?.toString()\n                if (!proxiedAppString.isNullOrEmpty()) {\n                    val i = proxiedAppString.indexOf('\\n')\n                    try {\n                        val apps = if (i < 0) \"\" else proxiedAppString.substring(i + 1)\n                        DataStore.routePackages = apps\n                        Snackbar.make(\n                            binding.list, R.string.action_import_msg, Snackbar.LENGTH_LONG\n                        ).show()\n                        initProxiedUids(apps)\n                        appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH)\n                        return true\n                    } catch (_: IllegalArgumentException) {\n                    }\n                }\n                Snackbar.make(binding.list, R.string.action_import_err, Snackbar.LENGTH_LONG).show()\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        if (!super.onSupportNavigateUp()) finish()\n        return true\n    }\n\n    override fun supportNavigateUpTo(upIntent: Intent) =\n        super.supportNavigateUpTo(upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))\n\n    override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU) {\n        if (binding.toolbar.isOverflowMenuShowing) binding.toolbar.hideOverflowMenu() else binding.toolbar.showOverflowMenu()\n    } else super.onKeyUp(keyCode, event)\n\n    override fun onDestroy() {\n        loader?.cancel()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.text.TextUtils\nimport android.util.SparseBooleanArray\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.Filter\nimport android.widget.Filterable\nimport androidx.annotation.UiThread\nimport androidx.core.util.contains\nimport androidx.core.util.set\nimport androidx.core.view.ViewCompat\nimport androidx.core.widget.addTextChangedListener\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.DefaultItemAnimator\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.snackbar.Snackbar\nimport com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.databinding.LayoutAppsBinding\nimport io.nekohasekai.sagernet.databinding.LayoutAppsItemBinding\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.crossFadeFrom\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport io.nekohasekai.sagernet.widget.ListListener\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.withContext\nimport moe.matsuri.nb4a.utils.NGUtil\nimport kotlin.coroutines.coroutineContext\n\nclass AppManagerActivity : ThemedActivity() {\n    companion object {\n        @SuppressLint(\"StaticFieldLeak\")\n        private var instance: AppManagerActivity? = null\n        private const val SWITCH = \"switch\"\n\n        private val cachedApps\n            get() = PackageCache.installedPackages.toMutableMap().apply {\n                remove(BuildConfig.APPLICATION_ID)\n            }\n    }\n\n    private class ProxiedApp(\n        private val pm: PackageManager, private val appInfo: ApplicationInfo,\n        val packageName: String,\n    ) {\n        val name: CharSequence = appInfo.loadLabel(pm)    // cached for sorting\n        val icon: Drawable get() = appInfo.loadIcon(pm)\n        val uid get() = appInfo.uid\n        val sys get() = (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0\n    }\n\n    private inner class AppViewHolder(val binding: LayoutAppsItemBinding) : RecyclerView.ViewHolder(\n        binding.root\n    ),\n        View.OnClickListener {\n        private lateinit var item: ProxiedApp\n\n        init {\n            binding.root.setOnClickListener(this)\n        }\n\n        fun bind(app: ProxiedApp) {\n            item = app\n            binding.itemicon.setImageDrawable(app.icon)\n            binding.title.text = app.name\n            binding.desc.text = \"${app.packageName} (${app.uid})\"\n            binding.itemcheck.isChecked = isProxiedApp(app)\n        }\n\n        fun handlePayload(payloads: List<String>) {\n            if (payloads.contains(SWITCH)) binding.itemcheck.isChecked = isProxiedApp(item)\n        }\n\n        override fun onClick(v: View?) {\n            if (isProxiedApp(item)) proxiedUids.delete(item.uid) else proxiedUids[item.uid] = true\n            DataStore.individual = apps.filter { isProxiedApp(it) }\n                .joinToString(\"\\n\") { it.packageName }\n\n            appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH)\n        }\n    }\n\n    private inner class AppsAdapter : RecyclerView.Adapter<AppViewHolder>(),\n        Filterable,\n        FastScrollRecyclerView.SectionedAdapter {\n        var filteredApps = apps\n\n        suspend fun reload() {\n            PackageCache.reload()\n            apps = cachedApps.mapNotNull { (packageName, packageInfo) ->\n                coroutineContext[Job]!!.ensureActive()\n                packageInfo.applicationInfo?.let { ProxiedApp(packageManager, it, packageName) }\n            }.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n        }\n\n        override fun onBindViewHolder(holder: AppViewHolder, position: Int) =\n            holder.bind(filteredApps[position])\n\n        override fun onBindViewHolder(holder: AppViewHolder, position: Int, payloads: List<Any>) {\n            if (payloads.isNotEmpty()) {\n                @Suppress(\"UNCHECKED_CAST\") holder.handlePayload(payloads as List<String>)\n                return\n            }\n\n            onBindViewHolder(holder, position)\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder =\n            AppViewHolder(LayoutAppsItemBinding.inflate(layoutInflater, parent, false))\n\n        override fun getItemCount(): Int = filteredApps.size\n\n        private val filterImpl = object : Filter() {\n            override fun performFiltering(constraint: CharSequence) = FilterResults().apply {\n                var filteredApps = if (constraint.isEmpty()) apps else apps.filter {\n                    it.name.contains(constraint, true) || it.packageName.contains(\n                        constraint, true\n                    ) || it.uid.toString().contains(constraint)\n                }\n                if (!sysApps) filteredApps = filteredApps.filter { !it.sys }\n                count = filteredApps.size\n                values = filteredApps\n            }\n\n            override fun publishResults(constraint: CharSequence, results: FilterResults) {\n                @Suppress(\"UNCHECKED_CAST\")\n                filteredApps = results.values as List<ProxiedApp>\n                notifyDataSetChanged()\n            }\n        }\n\n        override fun getFilter(): Filter = filterImpl\n\n        override fun getSectionName(position: Int): String {\n            return filteredApps[position].name.firstOrNull()?.toString() ?: \"\"\n        }\n\n    }\n\n    private val loading by lazy { findViewById<View>(R.id.loading) }\n\n    private lateinit var binding: LayoutAppsBinding\n    private val proxiedUids = SparseBooleanArray()\n    private var loader: Job? = null\n    private var apps = emptyList<ProxiedApp>()\n    private val appsAdapter = AppsAdapter()\n\n    private fun initProxiedUids(str: String = DataStore.individual) {\n        proxiedUids.clear()\n        val apps = cachedApps\n        for (line in str.lineSequence()) {\n            val app = (apps[line] ?: continue)\n            val uid = app.applicationInfo?.uid ?: continue\n            proxiedUids[uid] = true\n        }\n    }\n\n    private fun isProxiedApp(app: ProxiedApp) = proxiedUids[app.uid]\n\n    @UiThread\n    private fun loadApps() {\n        loader?.cancel()\n        loader = lifecycleScope.launchWhenCreated {\n            loading.crossFadeFrom(binding.list)\n            val adapter = binding.list.adapter as AppsAdapter\n            withContext(Dispatchers.IO) { adapter.reload() }\n            adapter.filter.filter(binding.search.text?.toString() ?: \"\")\n            if (apps.isEmpty()) {\n                binding.list.visibility = View.GONE\n                binding.appPlaceholder.root.crossFadeFrom(loading)\n            } else {\n                binding.list.crossFadeFrom(loading)\n            }\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = LayoutAppsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        binding.appPlaceholder.openSettings.setOnClickListener {\n            val intent =\n                Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {\n                    data = android.net.Uri.fromParts(\"package\", packageName, null)\n                }\n            startActivity(intent)\n        }\n\n        setSupportActionBar(binding.toolbar)\n        supportActionBar?.apply {\n            setTitle(R.string.proxied_apps)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        if (!DataStore.proxyApps) {\n            DataStore.proxyApps = true\n        }\n\n        binding.bypassGroup.check(if (DataStore.bypass) R.id.appProxyModeBypass else R.id.appProxyModeOn)\n        binding.bypassGroup.setOnCheckedChangeListener { _, checkedId ->\n            when (checkedId) {\n                R.id.appProxyModeDisable -> {\n                    DataStore.proxyApps = false\n                    finish()\n                }\n\n                R.id.appProxyModeOn -> DataStore.bypass = false\n                R.id.appProxyModeBypass -> DataStore.bypass = true\n            }\n        }\n        binding.autoSelectProxyApps.setOnClickListener { selectProxyApp() }\n\n        initProxiedUids()\n        binding.list.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)\n        binding.list.itemAnimator = DefaultItemAnimator()\n        binding.list.adapter = appsAdapter\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener)\n\n        binding.search.addTextChangedListener {\n            appsAdapter.filter.filter(it?.toString() ?: \"\")\n        }\n\n        binding.showSystemApps.isChecked = sysApps\n        binding.showSystemApps.setOnCheckedChangeListener { _, isChecked ->\n            sysApps = isChecked\n            appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n        }\n\n        instance = this\n        loadApps()\n    }\n\n    private var sysApps = true\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.per_app_proxy_menu, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_invert_selections -> {\n                runOnDefaultDispatcher {\n                    val proxiedUidsOld = proxiedUids.clone()\n                    for (app in apps) {\n                        if (proxiedUidsOld.contains(app.uid)) {\n                            proxiedUids.delete(app.uid)\n                        } else {\n                            proxiedUids[app.uid] = true\n                        }\n                    }\n                    DataStore.individual = apps.filter { isProxiedApp(it) }\n                        .joinToString(\"\\n\") { it.packageName }\n                    apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n                    onMainDispatcher {\n                        appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n                    }\n                }\n\n                return true\n            }\n\n            R.id.action_clear_selections -> {\n                runOnDefaultDispatcher {\n                    proxiedUids.clear()\n                    DataStore.individual = \"\"\n                    apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n                    onMainDispatcher {\n                        appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n                    }\n                }\n            }\n\n            R.id.action_export_clipboard -> {\n                val success =\n                    SagerNet.trySetPrimaryClip(\"${DataStore.bypass}\\n${DataStore.individual}\")\n                Snackbar.make(\n                    binding.list,\n                    if (success) R.string.action_export_msg else R.string.action_export_err,\n                    Snackbar.LENGTH_LONG\n                ).show()\n                return true\n            }\n\n            R.id.action_import_clipboard -> {\n                val proxiedAppString =\n                    SagerNet.clipboard.primaryClip?.getItemAt(0)?.text?.toString()\n                if (!proxiedAppString.isNullOrEmpty()) {\n                    val i = proxiedAppString.indexOf('\\n')\n                    try {\n                        val (enabled, apps) = if (i < 0) {\n                            proxiedAppString to \"\"\n                        } else proxiedAppString.substring(\n                            0, i\n                        ) to proxiedAppString.substring(i + 1)\n                        binding.bypassGroup.check(if (enabled.toBoolean()) R.id.appProxyModeBypass else R.id.appProxyModeOn)\n                        DataStore.individual = apps\n                        Snackbar.make(\n                            binding.list, R.string.action_import_msg, Snackbar.LENGTH_LONG\n                        ).show()\n                        initProxiedUids(apps)\n                        appsAdapter.notifyItemRangeChanged(0, appsAdapter.itemCount, SWITCH)\n                        return true\n                    } catch (_: IllegalArgumentException) {\n                    }\n                }\n                Snackbar.make(binding.list, R.string.action_import_err, Snackbar.LENGTH_LONG).show()\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n\n    private fun selectProxyApp() {\n        MaterialAlertDialogBuilder(this).setTitle(R.string.confirm)\n            .setMessage(R.string.auto_select_proxy_apps_message)\n            .setPositiveButton(R.string.yes) { _, _ ->\n                try {\n                    val needProxyAppsList = getAutoProxyApps(\"\")\n                    val bypass = DataStore.bypass\n                    proxiedUids.clear()\n                    for (app in cachedApps) {\n                        val needProxy =\n                            needProxyAppsList.contains(app.key) || (app.value.applicationInfo?.uid\n                                ?: 0) == 1000\n                        if (needProxy) {\n                            if (!bypass) {\n                                app.value.applicationInfo?.apply {\n                                    proxiedUids[uid] = true\n                                }\n                            }\n                        } else {\n                            if (bypass) {\n                                app.value.applicationInfo?.apply {\n                                    proxiedUids[uid] = true\n                                }\n                            }\n                        }\n                    }\n                    DataStore.individual =\n                        apps.filter { isProxiedApp(it) }.joinToString(\"\\n\") { it.packageName }\n                    apps = apps.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))\n                    appsAdapter.filter.filter(binding.search.text?.toString() ?: \"\")\n                } catch (e: Exception) {\n                    Logs.e(e)\n                }\n            }\n            .setNegativeButton(R.string.no, null)\n            .show()\n    }\n\n    private fun getAutoProxyApps(content: String): List<String> {\n        var list = listOf<String>()\n        try {\n            val proxyApps = if (TextUtils.isEmpty(content)) {\n                NGUtil.readTextFromAssets(app, \"proxy_packagename.txt\")\n            } else {\n                content\n            }\n            if (!TextUtils.isEmpty(proxyApps)) {\n                list = proxyApps.split(\"\\n\")\n            }\n        } catch (_: Exception) {\n        }\n        return list\n    }\n\n    override fun supportNavigateUpTo(upIntent: Intent) =\n        super.supportNavigateUpTo(upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP))\n\n    override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU) {\n        if (binding.toolbar.isOverflowMenuShowing) binding.toolbar.hideOverflowMenu() else binding.toolbar.showOverflowMenu()\n    } else super.onKeyUp(keyCode, event)\n\n    override fun onDestroy() {\n        instance = null\n        loader?.cancel()\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/AssetsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport android.provider.OpenableColumns\nimport android.text.format.DateFormat\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.ViewGroup\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.view.isInvisible\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.snackbar.Snackbar\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.databinding.LayoutAssetItemBinding\nimport io.nekohasekai.sagernet.databinding.LayoutAssetsBinding\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.widget.UndoSnackbarManager\nimport libcore.Libcore\nimport moe.matsuri.nb4a.utils.Util\nimport org.json.JSONObject\nimport java.io.File\nimport java.io.FileWriter\nimport java.util.*\nimport java.util.concurrent.atomic.AtomicInteger\n\nclass AssetsActivity : ThemedActivity() {\n\n    lateinit var adapter: AssetAdapter\n    lateinit var layout: LayoutAssetsBinding\n    lateinit var undoManager: UndoSnackbarManager<File>\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val binding = LayoutAssetsBinding.inflate(layoutInflater)\n        layout = binding\n        setContentView(binding.root)\n\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.route_assets)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        binding.recyclerView.layoutManager = FixedLinearLayoutManager(binding.recyclerView)\n        adapter = AssetAdapter()\n        binding.recyclerView.adapter = adapter\n\n        binding.refreshLayout.setOnRefreshListener {\n            adapter.reloadAssets()\n            binding.refreshLayout.isRefreshing = false\n        }\n        binding.refreshLayout.setColorSchemeColors(getColorAttr(R.attr.primaryOrTextPrimary))\n\n        undoManager = UndoSnackbarManager(this, adapter)\n\n        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(\n            0, ItemTouchHelper.START\n        ) {\n\n            override fun getSwipeDirs(\n                recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder\n            ): Int {\n                val index = viewHolder.bindingAdapterPosition\n                if (index < 2) return 0\n                return super.getSwipeDirs(recyclerView, viewHolder)\n            }\n\n            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n                val index = viewHolder.bindingAdapterPosition\n                adapter.remove(index)\n                undoManager.remove(index to (viewHolder as AssetHolder).file)\n            }\n\n            override fun onMove(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n                target: RecyclerView.ViewHolder\n            ) = false\n\n        }).attachToRecyclerView(binding.recyclerView)\n    }\n\n    override fun snackbarInternal(text: CharSequence): Snackbar {\n        return Snackbar.make(layout.coordinator, text, Snackbar.LENGTH_LONG)\n    }\n\n    val assetNames = arrayOf(\"geoip.db\", \"geosite.db\")\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.import_asset_menu, menu)\n        return true\n    }\n\n    val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file ->\n        if (file != null) {\n            val fileName = contentResolver.query(file, null, null, null, null)?.use { cursor ->\n                cursor.moveToFirst()\n                cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString)\n            }?.takeIf { it.isNotBlank() } ?: file.pathSegments.last()\n                .substringAfterLast('/')\n                .substringAfter(':')\n\n            if (!fileName.endsWith(\".db\")) {\n                alert(getString(R.string.route_not_asset, fileName)).show()\n                return@registerForActivityResult\n            }\n            val filesDir = getExternalFilesDir(null) ?: filesDir\n\n            runOnDefaultDispatcher {\n                val outFile = File(filesDir, fileName).apply {\n                    parentFile?.mkdirs()\n                }\n\n                contentResolver.openInputStream(file)?.use(outFile.outputStream())\n\n                File(outFile.parentFile, outFile.nameWithoutExtension + \".version.txt\").apply {\n                    if (isFile) delete()\n                    createNewFile()\n                    val fw = FileWriter(this)\n                    fw.write(\"Custom\")\n                    fw.close()\n                }\n\n                adapter.reloadAssets()\n            }\n\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_import_file -> {\n                startFilesForResult(importFile, \"*/*\")\n                return true\n            }\n        }\n        return false\n    }\n\n    inner class AssetAdapter : RecyclerView.Adapter<AssetHolder>(),\n        UndoSnackbarManager.Interface<File> {\n\n        val assets = ArrayList<File>()\n\n        init {\n            reloadAssets()\n        }\n\n        fun reloadAssets() {\n            val filesDir = getExternalFilesDir(null) ?: filesDir\n            val files = filesDir.listFiles()\n                ?.filter { it.isFile && it.name.endsWith(\".db\") && it.name !in assetNames }\n            assets.clear()\n            assets.add(File(filesDir, \"geoip.db\"))\n            assets.add(File(filesDir, \"geosite.db\"))\n            if (files != null) assets.addAll(files)\n\n            layout.refreshLayout.post {\n                notifyDataSetChanged()\n            }\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AssetHolder {\n            return AssetHolder(LayoutAssetItemBinding.inflate(layoutInflater, parent, false))\n        }\n\n        override fun onBindViewHolder(holder: AssetHolder, position: Int) {\n            holder.bind(assets[position])\n        }\n\n        override fun getItemCount(): Int {\n            return assets.size\n        }\n\n        fun remove(index: Int) {\n            assets.removeAt(index)\n            notifyItemRemoved(index)\n        }\n\n        override fun undo(actions: List<Pair<Int, File>>) {\n            for ((index, item) in actions) {\n                assets.add(index, item)\n                notifyItemInserted(index)\n            }\n        }\n\n        override fun commit(actions: List<Pair<Int, File>>) {\n            val groups = actions.map { it.second }.toTypedArray()\n            runOnDefaultDispatcher {\n                groups.forEach { it.deleteRecursively() }\n            }\n        }\n\n    }\n\n    val updating = AtomicInteger()\n\n    inner class AssetHolder(val binding: LayoutAssetItemBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        lateinit var file: File\n\n        fun bind(file: File) {\n            this.file = file\n\n            binding.assetName.text = file.name\n            val versionFile = File(file.parentFile, \"${file.nameWithoutExtension}.version.txt\")\n\n            val localVersion = if (file.isFile) {\n                if (versionFile.isFile) {\n                    try {\n                        versionFile.readText().trim()\n                    } catch (e: Throwable) {\n                        snackbar(e.readableMessage)\n                        \"<unknown>\"\n                    }\n                } else {\n                    \"Unknown-\" + DateFormat.getDateFormat(app).format(Date(file.lastModified()))\n                }\n            } else {\n                \"<unknown>\"\n            }\n\n            binding.assetStatus.text = getString(R.string.route_asset_status, localVersion)\n\n            binding.rulesUpdate.isInvisible = file.name !in assetNames\n            binding.rulesUpdate.setOnClickListener {\n                updating.incrementAndGet()\n                layout.refreshLayout.isEnabled = false\n                binding.subscriptionUpdateProgress.isInvisible = false\n                binding.rulesUpdate.isInvisible = true\n                runOnDefaultDispatcher {\n                    runCatching {\n                        updateAsset(file, versionFile, localVersion)\n                    }.onFailure {\n                        onMainDispatcher {\n                            alert(it.readableMessage).tryToShow()\n                        }\n                    }\n\n                    onMainDispatcher {\n                        binding.rulesUpdate.isInvisible = false\n                        binding.subscriptionUpdateProgress.isInvisible = true\n                        if (updating.decrementAndGet() == 0) {\n                            layout.refreshLayout.isEnabled = true\n                        }\n                    }\n                }\n            }\n\n        }\n\n    }\n\n    private val rulesProviders = listOf(\n        RuleAssetsProvider(\n            \"SagerNet/sing-geoip\",\n            \"SagerNet/sing-geosite\",\n        ),\n        RuleAssetsProvider(\n            \"soffchen/sing-geoip\",\n            \"soffchen/sing-geosite\",\n        ),\n        RuleAssetsProvider(\n            \"Chocolate4U/Iran-sing-box-rules\"\n        ),\n        RuleAssetsProvider(\n            \"L11R/antizapret-sing-box-geo\"\n        ),\n    )\n\n    suspend fun updateAsset(file: File, versionFile: File, localVersion: String) {\n        var fileName = file.name\n\n        val ruleProvider = rulesProviders[DataStore.rulesProvider]\n        val repo = ruleProvider.repoByFileName[fileName]\n\n        val client = Libcore.newHttpClient().apply {\n            modernTLS()\n            keepAlive()\n            trySocks5(DataStore.mixedPort)\n        }\n\n        try {\n            var response = client.newRequest().apply {\n                setURL(\"https://api.github.com/repos/$repo/releases/latest\")\n            }.execute()\n\n            val release = JSONObject(Util.getStringBox(response.contentString))\n            val tagName = release.optString(\"tag_name\")\n\n            if (tagName == localVersion) {\n                onMainDispatcher {\n                    snackbar(R.string.route_asset_no_update).show()\n                }\n                return\n            }\n\n            val releaseAssets = release.getJSONArray(\"assets\").filterIsInstance<JSONObject>()\n            val assetToDownload = releaseAssets.find { it.getStr(\"name\") == fileName }\n                ?: error(\"File $fileName not found in release ${release[\"url\"]}\")\n            val browserDownloadUrl = assetToDownload.getStr(\"browser_download_url\")\n\n            response = client.newRequest().apply {\n                setURL(browserDownloadUrl)\n            }.execute()\n\n            val cacheFile = File(file.parentFile, file.name + \".tmp\")\n            cacheFile.parentFile?.mkdirs()\n\n            response.writeTo(cacheFile.canonicalPath)\n\n            if (fileName.endsWith(\".xz\")) {\n                Libcore.unxz(cacheFile.absolutePath, file.absolutePath)\n                cacheFile.delete()\n            } else {\n                cacheFile.renameTo(file)\n            }\n\n            versionFile.writeText(tagName)\n\n            adapter.reloadAssets()\n\n            onMainDispatcher {\n                snackbar(R.string.route_asset_updated).show()\n            }\n        } finally {\n            client.close()\n        }\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        finish()\n        return true\n    }\n\n    override fun onBackPressed() {\n        finish()\n    }\n\n    override fun onResume() {\n        super.onResume()\n\n        if (::adapter.isInitialized) {\n            adapter.reloadAssets()\n        }\n    }\n\n    private data class RuleAssetsProvider(\n        val repoByFileName: Map<String, String>\n    ) {\n        constructor(\n            geoipRepo: String,\n            geositeRepo: String = geoipRepo,\n        ) : this(\n            mapOf(\n                \"geoip.db\" to geoipRepo,\n                \"geosite.db\" to geositeRepo,\n            )\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/BackupFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Parcel\nimport android.os.Parcelable\nimport android.provider.OpenableColumns\nimport android.view.View\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.content.FileProvider\nimport androidx.core.view.isVisible\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.jakewharton.processphoenix.ProcessPhoenix\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.bg.Executable\nimport io.nekohasekai.sagernet.database.*\nimport io.nekohasekai.sagernet.database.preference.KeyValuePair\nimport io.nekohasekai.sagernet.database.preference.PublicDatabase\nimport io.nekohasekai.sagernet.databinding.LayoutBackupBinding\nimport io.nekohasekai.sagernet.databinding.LayoutImportBinding\nimport io.nekohasekai.sagernet.databinding.LayoutProgressBinding\nimport io.nekohasekai.sagernet.ktx.*\nimport kotlinx.coroutines.delay\nimport moe.matsuri.nb4a.utils.Util\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport java.io.File\nimport java.util.*\n\nclass BackupFragment : NamedFragment(R.layout.layout_backup) {\n\n    override fun name0() = app.getString(R.string.backup)\n\n    var content = \"\"\n    private val exportSettings =\n        registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->\n            if (data != null) {\n                runOnDefaultDispatcher {\n                    try {\n                        requireActivity().contentResolver.openOutputStream(\n                            data\n                        )!!.bufferedWriter().use {\n                            it.write(content)\n                        }\n                        onMainDispatcher {\n                            snackbar(getString(R.string.action_export_msg)).show()\n                        }\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                        onMainDispatcher {\n                            snackbar(e.readableMessage).show()\n                        }\n                    }\n                }\n            }\n        }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val binding = LayoutBackupBinding.bind(view)\n\n        binding.resetSettings.setOnClickListener {\n            MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)\n                .setMessage(R.string.reset_settings_message)\n                .setNegativeButton(R.string.no, null)\n                .setPositiveButton(R.string.yes) { _, _ ->\n                    DataStore.configurationStore.reset()\n                    triggerFullRestart(requireContext())\n                }\n                .show()\n        }\n\n        binding.actionExport.setOnClickListener {\n            runOnDefaultDispatcher {\n                content = doBackup(\n                    binding.backupConfigurations.isChecked,\n                    binding.backupRules.isChecked,\n                    binding.backupSettings.isChecked\n                )\n                onMainDispatcher {\n                    startFilesForResult(\n                        exportSettings, \"nekobox_backup_${Date().toLocaleString()}.json\"\n                    )\n                }\n            }\n        }\n\n        binding.actionShare.setOnClickListener {\n            runOnDefaultDispatcher {\n                content = doBackup(\n                    binding.backupConfigurations.isChecked,\n                    binding.backupRules.isChecked,\n                    binding.backupSettings.isChecked\n                )\n                app.cacheDir.mkdirs()\n                val cacheFile = File(\n                    app.cacheDir, \"nekobox_backup_${Date().toLocaleString()}.json\"\n                )\n                cacheFile.writeText(content)\n                onMainDispatcher {\n                    startActivity(\n                        Intent.createChooser(\n                            Intent(Intent.ACTION_SEND).setType(\"application/json\")\n                                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                                .putExtra(\n                                    Intent.EXTRA_STREAM, FileProvider.getUriForFile(\n                                        app, BuildConfig.APPLICATION_ID + \".cache\", cacheFile\n                                    )\n                                ), app.getString(R.string.abc_shareactionprovider_share_with)\n                        )\n                    )\n                }\n\n            }\n        }\n\n        binding.actionImportFile.setOnClickListener {\n            startFilesForResult(importFile, \"*/*\")\n        }\n    }\n\n    fun Parcelable.toBase64Str(): String {\n        val parcel = Parcel.obtain()\n        writeToParcel(parcel, 0)\n        try {\n            return Util.b64EncodeUrlSafe(parcel.marshall())\n        } finally {\n            parcel.recycle()\n        }\n    }\n\n    fun doBackup(profile: Boolean, rule: Boolean, setting: Boolean): String {\n        val out = JSONObject().apply {\n            put(\"version\", 1)\n            if (profile) {\n                put(\"profiles\", JSONArray().apply {\n                    SagerDatabase.proxyDao.getAll().forEach {\n                        put(it.toBase64Str())\n                    }\n                })\n\n                put(\"groups\", JSONArray().apply {\n                    SagerDatabase.groupDao.allGroups().forEach {\n                        put(it.toBase64Str())\n                    }\n                })\n            }\n            if (rule) {\n                put(\"rules\", JSONArray().apply {\n                    SagerDatabase.rulesDao.allRules().forEach {\n                        put(it.toBase64Str())\n                    }\n                })\n            }\n            if (setting) {\n                put(\"settings\", JSONArray().apply {\n                    PublicDatabase.kvPairDao.all().forEach {\n                        put(it.toBase64Str())\n                    }\n                })\n            }\n        }\n        return out.toStringPretty()\n    }\n\n    val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file ->\n        if (file != null) {\n            runOnDefaultDispatcher {\n                startImport(file)\n            }\n        }\n    }\n\n    suspend fun startImport(file: Uri) {\n        val fileName = requireContext().contentResolver.query(file, null, null, null, null)\n            ?.use { cursor ->\n                cursor.moveToFirst()\n                cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME).let(cursor::getString)\n            }\n            ?.takeIf { it.isNotBlank() } ?: file.pathSegments.last()\n            .substringAfterLast('/')\n            .substringAfter(':')\n\n        if (!fileName.endsWith(\".json\")) {\n            onMainDispatcher {\n                snackbar(getString(R.string.backup_not_file, fileName)).show()\n            }\n            return\n        }\n\n        suspend fun invalid() = onMainDispatcher {\n            onMainDispatcher {\n                snackbar(getString(R.string.invalid_backup_file)).show()\n            }\n        }\n\n        val content = try {\n            JSONObject((requireContext().contentResolver.openInputStream(file) ?: return).use {\n                it.bufferedReader().readText()\n            })\n        } catch (e: Exception) {\n            Logs.w(e)\n            invalid()\n            return\n        }\n        val version = content.optInt(\"version\", 0)\n        if (version < 1 || version > 1) {\n            invalid()\n            return\n        }\n\n        onMainDispatcher {\n            val import = LayoutImportBinding.inflate(layoutInflater)\n            if (!content.has(\"profiles\")) {\n                import.backupConfigurations.isVisible = false\n            }\n            if (!content.has(\"rules\")) {\n                import.backupRules.isVisible = false\n            }\n            if (!content.has(\"settings\")) {\n                import.backupSettings.isVisible = false\n            }\n            MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.backup_import)\n                .setView(import.root)\n                .setPositiveButton(R.string.backup_import) { _, _ ->\n                    SagerNet.stopService()\n\n                    val binding = LayoutProgressBinding.inflate(layoutInflater)\n                    binding.content.text = getString(R.string.backup_importing)\n                    val dialog = AlertDialog.Builder(requireContext())\n                        .setView(binding.root)\n                        .setCancelable(false)\n                        .show()\n                    runOnDefaultDispatcher {\n                        runCatching {\n                            finishImport(\n                                content,\n                                import.backupConfigurations.isChecked,\n                                import.backupRules.isChecked,\n                                import.backupSettings.isChecked\n                            )\n                            triggerFullRestart(requireContext())\n                        }.onFailure {\n                            Logs.w(it)\n                            onMainDispatcher {\n                                alert(it.readableMessage).tryToShow()\n                            }\n                        }\n\n                        onMainDispatcher {\n                            dialog.dismiss()\n                        }\n                    }\n                }\n                .setNegativeButton(android.R.string.cancel, null)\n                .show()\n        }\n    }\n\n    fun finishImport(\n        content: JSONObject, profile: Boolean, rule: Boolean, setting: Boolean\n    ) {\n        if (profile && content.has(\"profiles\")) {\n            val profiles = mutableListOf<ProxyEntity>()\n            val jsonProfiles = content.getJSONArray(\"profiles\")\n            for (i in 0 until jsonProfiles.length()) {\n                val data = Util.b64Decode(jsonProfiles[i] as String)\n                val parcel = Parcel.obtain()\n                parcel.unmarshall(data, 0, data.size)\n                parcel.setDataPosition(0)\n                profiles.add(ProxyEntity.CREATOR.createFromParcel(parcel))\n                parcel.recycle()\n            }\n            SagerDatabase.proxyDao.reset()\n            SagerDatabase.proxyDao.insert(profiles)\n\n            val groups = mutableListOf<ProxyGroup>()\n            val jsonGroups = content.getJSONArray(\"groups\")\n            for (i in 0 until jsonGroups.length()) {\n                val data = Util.b64Decode(jsonGroups[i] as String)\n                val parcel = Parcel.obtain()\n                parcel.unmarshall(data, 0, data.size)\n                parcel.setDataPosition(0)\n                groups.add(ProxyGroup.CREATOR.createFromParcel(parcel))\n                parcel.recycle()\n            }\n            SagerDatabase.groupDao.reset()\n            SagerDatabase.groupDao.insert(groups)\n        }\n        if (rule && content.has(\"rules\")) {\n            val rules = mutableListOf<RuleEntity>()\n            val jsonRules = content.getJSONArray(\"rules\")\n            for (i in 0 until jsonRules.length()) {\n                val data = Util.b64Decode(jsonRules[i] as String)\n                val parcel = Parcel.obtain()\n                parcel.unmarshall(data, 0, data.size)\n                parcel.setDataPosition(0)\n                rules.add(ParcelizeBridge.createRule(parcel))\n                parcel.recycle()\n            }\n            SagerDatabase.rulesDao.reset()\n            SagerDatabase.rulesDao.insert(rules)\n        }\n        if (setting && content.has(\"settings\")) {\n            val settings = mutableListOf<KeyValuePair>()\n            val jsonSettings = content.getJSONArray(\"settings\")\n            for (i in 0 until jsonSettings.length()) {\n                val data = Util.b64Decode(jsonSettings[i] as String)\n                val parcel = Parcel.obtain()\n                parcel.unmarshall(data, 0, data.size)\n                parcel.setDataPosition(0)\n                settings.add(KeyValuePair.CREATOR.createFromParcel(parcel))\n                parcel.recycle()\n            }\n            PublicDatabase.kvPairDao.reset()\n            PublicDatabase.kvPairDao.insert(settings)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/BlankActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport moe.matsuri.nb4a.utils.SendLog\n\nclass BlankActivity : AppCompatActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        // process crash log\n        intent?.getStringExtra(\"sendLog\")?.apply {\n            SendLog.sendLog(this@BlankActivity, this)\n        }\n\n        finish()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.os.SystemClock\nimport android.provider.OpenableColumns\nimport android.text.SpannableStringBuilder\nimport android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\nimport android.text.format.Formatter\nimport android.text.style.ForegroundColorSpan\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.net.toUri\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.size\nimport androidx.fragment.app.Fragment\nimport androidx.preference.PreferenceDataStore\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport androidx.viewpager2.widget.ViewPager2\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.tabs.TabLayout\nimport com.google.android.material.tabs.TabLayoutMediator\nimport io.nekohasekai.sagernet.GroupOrder\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.TrafficData\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.proto.UrlTest\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.GroupManager\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.database.ProxyGroup\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.databinding.LayoutProfileListBinding\nimport io.nekohasekai.sagernet.databinding.LayoutProgressListBinding\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.fmt.toUniversalLink\nimport io.nekohasekai.sagernet.group.GroupUpdater\nimport io.nekohasekai.sagernet.group.RawUpdater\nimport io.nekohasekai.sagernet.ktx.FixedLinearLayoutManager\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.SubscriptionFoundException\nimport io.nekohasekai.sagernet.ktx.alert\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.dp2px\nimport io.nekohasekai.sagernet.ktx.getColorAttr\nimport io.nekohasekai.sagernet.ktx.getColour\nimport io.nekohasekai.sagernet.ktx.isIpAddress\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnLifecycleDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\nimport io.nekohasekai.sagernet.ktx.scrollTo\nimport io.nekohasekai.sagernet.ktx.showAllowingStateLoss\nimport io.nekohasekai.sagernet.ktx.snackbar\nimport io.nekohasekai.sagernet.ktx.startFilesForResult\nimport io.nekohasekai.sagernet.ktx.tryToShow\nimport io.nekohasekai.sagernet.plugin.PluginManager\nimport io.nekohasekai.sagernet.ui.profile.ChainSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.HttpSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.HysteriaSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.MieruSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.NaiveSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.SSHSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.ShadowsocksSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.SocksSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.TrojanGoSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.TrojanSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.TuicSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.VMessSettingsActivity\nimport io.nekohasekai.sagernet.ui.profile.WireGuardSettingsActivity\nimport io.nekohasekai.sagernet.widget.QRCodeDialog\nimport io.nekohasekai.sagernet.widget.UndoSnackbarManager\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.joinAll\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport moe.matsuri.nb4a.Protocols\nimport moe.matsuri.nb4a.Protocols.getProtocolColor\nimport moe.matsuri.nb4a.proxy.anytls.AnyTLSSettingsActivity\nimport moe.matsuri.nb4a.proxy.config.ConfigSettingActivity\nimport moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSSettingsActivity\nimport moe.matsuri.nb4a.ui.ConnectionTestNotification\nimport okhttp3.internal.closeQuietly\nimport java.net.InetSocketAddress\nimport java.net.Socket\nimport java.net.UnknownHostException\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.ConcurrentLinkedQueue\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.zip.ZipInputStream\n\nclass ConfigurationFragment @JvmOverloads constructor(\n    val select: Boolean = false, val selectedItem: ProxyEntity? = null, val titleRes: Int = 0\n) : ToolbarFragment(R.layout.layout_group_list),\n    PopupMenu.OnMenuItemClickListener,\n    Toolbar.OnMenuItemClickListener,\n    SearchView.OnQueryTextListener,\n    OnPreferenceDataStoreChangeListener {\n\n    interface SelectCallback {\n        fun returnProfile(profileId: Long)\n    }\n\n    lateinit var adapter: GroupPagerAdapter\n    lateinit var tabLayout: TabLayout\n    lateinit var groupPager: ViewPager2\n\n    val alwaysShowAddress by lazy { DataStore.alwaysShowAddress }\n\n    fun getCurrentGroupFragment(): GroupFragment? {\n        return try {\n            childFragmentManager.findFragmentByTag(\"f\" + DataStore.selectedGroup) as GroupFragment?\n        } catch (e: Exception) {\n            Logs.e(e)\n            null\n        }\n    }\n\n    val updateSelectedCallback = object : ViewPager2.OnPageChangeCallback() {\n        override fun onPageScrolled(\n            position: Int, positionOffset: Float, positionOffsetPixels: Int\n        ) {\n            if (adapter.groupList.size > position) {\n                DataStore.selectedGroup = adapter.groupList[position].id\n            }\n        }\n    }\n\n    override fun onQueryTextChange(query: String): Boolean {\n        getCurrentGroupFragment()?.adapter?.filter(query)\n        return false\n    }\n\n    override fun onQueryTextSubmit(query: String): Boolean = false\n\n    @SuppressLint(\"DetachAndAttachSameFragment\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        if (savedInstanceState != null) {\n            parentFragmentManager.beginTransaction()\n                .setReorderingAllowed(false)\n                .detach(this)\n                .attach(this)\n                .commit()\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        if (!select) {\n            toolbar.inflateMenu(R.menu.add_profile_menu)\n            toolbar.setOnMenuItemClickListener(this)\n        } else {\n            toolbar.setTitle(titleRes)\n            toolbar.setNavigationIcon(R.drawable.ic_navigation_close)\n            toolbar.setNavigationOnClickListener {\n                requireActivity().finish()\n            }\n        }\n\n        val searchView = toolbar.findViewById<SearchView>(R.id.action_search)\n        if (searchView != null) {\n            searchView.setOnQueryTextListener(this)\n            searchView.maxWidth = Int.MAX_VALUE\n\n            searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->\n                if (!hasFocus) {\n                    cancelSearch(searchView)\n                }\n            }\n        }\n\n        groupPager = view.findViewById(R.id.group_pager)\n        tabLayout = view.findViewById(R.id.group_tab)\n        adapter = GroupPagerAdapter()\n        ProfileManager.addListener(adapter)\n        GroupManager.addListener(adapter)\n\n        groupPager.adapter = adapter\n        groupPager.offscreenPageLimit = 2\n\n        TabLayoutMediator(tabLayout, groupPager) { tab, position ->\n            if (adapter.groupList.size > position) {\n                tab.text = adapter.groupList[position].displayName()\n            }\n            tab.view.setOnLongClickListener { // clear toast\n                true\n            }\n        }.attach()\n\n        toolbar.setOnClickListener {\n            val fragment = getCurrentGroupFragment()\n\n            if (fragment != null) {\n                val selectedProxy = selectedItem?.id ?: DataStore.selectedProxy\n                val selectedProfileIndex =\n                    fragment.adapter!!.configurationIdList.indexOf(selectedProxy)\n                if (selectedProfileIndex != -1) {\n                    val layoutManager = fragment.layoutManager\n                    val first = layoutManager.findFirstVisibleItemPosition()\n                    val last = layoutManager.findLastVisibleItemPosition()\n\n                    if (selectedProfileIndex !in first..last) {\n                        fragment.configurationListView.scrollTo(selectedProfileIndex, true)\n                        return@setOnClickListener\n                    }\n\n                }\n\n                fragment.configurationListView.scrollTo(0)\n            }\n\n        }\n\n        DataStore.profileCacheStore.registerChangeListener(this)\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        runOnMainDispatcher {\n            // editingGroup\n            if (key == Key.PROFILE_GROUP) {\n                val targetId = DataStore.editingGroup\n                if (targetId > 0 && targetId != DataStore.selectedGroup) {\n                    DataStore.selectedGroup = targetId\n                    val targetIndex = adapter.groupList.indexOfFirst { it.id == targetId }\n                    if (targetIndex >= 0) {\n                        groupPager.setCurrentItem(targetIndex, false)\n                    } else {\n                        adapter.reload()\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        DataStore.profileCacheStore.unregisterChangeListener(this)\n\n        if (::adapter.isInitialized) {\n            GroupManager.removeListener(adapter)\n            ProfileManager.removeListener(adapter)\n        }\n\n        super.onDestroy()\n    }\n\n    override fun onKeyDown(ketCode: Int, event: KeyEvent): Boolean {\n        val fragment = getCurrentGroupFragment()\n        fragment?.configurationListView?.apply {\n            if (!hasFocus()) requestFocus()\n        }\n        return super.onKeyDown(ketCode, event)\n    }\n\n    private val importFile =\n        registerForActivityResult(ActivityResultContracts.GetContent()) { file ->\n            if (file != null) runOnDefaultDispatcher {\n                try {\n                    val fileName =\n                        requireContext().contentResolver.query(file, null, null, null, null)\n                            ?.use { cursor ->\n                                cursor.moveToFirst()\n                                cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)\n                                    .let(cursor::getString)\n                            }\n                    val proxies = mutableListOf<AbstractBean>()\n                    if (fileName != null && fileName.endsWith(\".zip\")) {\n                        // try parse wireguard zip\n                        val zip =\n                            ZipInputStream(requireContext().contentResolver.openInputStream(file)!!)\n                        while (true) {\n                            val entry = zip.nextEntry ?: break\n                            if (entry.isDirectory) continue\n                            val fileText = zip.bufferedReader().readText()\n                            RawUpdater.parseRaw(fileText, entry.name)\n                                ?.let { pl -> proxies.addAll(pl) }\n                            zip.closeEntry()\n                        }\n                        zip.closeQuietly()\n                    } else {\n                        val fileText =\n                            requireContext().contentResolver.openInputStream(file)!!.use {\n                                it.bufferedReader().readText()\n                            }\n                        RawUpdater.parseRaw(fileText, fileName ?: \"\")\n                            ?.let { pl -> proxies.addAll(pl) }\n                    }\n                    if (proxies.isEmpty()) onMainDispatcher {\n                        snackbar(getString(R.string.no_proxies_found_in_file)).show()\n                    } else import(proxies)\n                } catch (e: SubscriptionFoundException) {\n                    (requireActivity() as MainActivity).importSubscription(e.link.toUri())\n                } catch (e: Exception) {\n                    Logs.w(e)\n                    onMainDispatcher {\n                        snackbar(e.readableMessage).show()\n                    }\n                }\n            }\n        }\n\n    suspend fun import(proxies: List<AbstractBean>) {\n        val targetId = DataStore.selectedGroupForImport()\n        for (proxy in proxies) {\n            ProfileManager.createProfile(targetId, proxy)\n        }\n        onMainDispatcher {\n            DataStore.editingGroup = targetId\n            snackbar(\n                requireContext().resources.getQuantityString(\n                    R.plurals.added, proxies.size, proxies.size\n                )\n            ).show()\n        }\n\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_scan_qr_code -> {\n                startActivity(Intent(context, ScannerActivity::class.java))\n            }\n\n            R.id.action_import_clipboard -> {\n                val text = SagerNet.getClipboardText()\n                if (text.isBlank()) {\n                    snackbar(getString(R.string.clipboard_empty)).show()\n                } else runOnDefaultDispatcher {\n                    try {\n                        val proxies = RawUpdater.parseRaw(text)\n                        if (proxies.isNullOrEmpty()) onMainDispatcher {\n                            snackbar(getString(R.string.no_proxies_found_in_clipboard)).show()\n                        } else import(proxies)\n                    } catch (e: SubscriptionFoundException) {\n                        (requireActivity() as MainActivity).importSubscription(e.link.toUri())\n                    } catch (e: Exception) {\n                        Logs.w(e)\n\n                        onMainDispatcher {\n                            snackbar(e.readableMessage).show()\n                        }\n                    }\n                }\n            }\n\n            R.id.action_import_file -> {\n                startFilesForResult(importFile, \"*/*\")\n            }\n\n            R.id.action_new_socks -> {\n                startActivity(Intent(requireActivity(), SocksSettingsActivity::class.java))\n            }\n\n            R.id.action_new_http -> {\n                startActivity(Intent(requireActivity(), HttpSettingsActivity::class.java))\n            }\n\n            R.id.action_new_ss -> {\n                startActivity(Intent(requireActivity(), ShadowsocksSettingsActivity::class.java))\n            }\n\n            R.id.action_new_vmess -> {\n                startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java))\n            }\n\n            R.id.action_new_vless -> {\n                startActivity(Intent(requireActivity(), VMessSettingsActivity::class.java).apply {\n                    putExtra(\"vless\", true)\n                })\n            }\n\n            R.id.action_new_trojan -> {\n                startActivity(Intent(requireActivity(), TrojanSettingsActivity::class.java))\n            }\n\n            R.id.action_new_trojan_go -> {\n                startActivity(Intent(requireActivity(), TrojanGoSettingsActivity::class.java))\n            }\n\n            R.id.action_new_mieru -> {\n                startActivity(Intent(requireActivity(), MieruSettingsActivity::class.java))\n            }\n\n            R.id.action_new_naive -> {\n                startActivity(Intent(requireActivity(), NaiveSettingsActivity::class.java))\n            }\n\n            R.id.action_new_hysteria -> {\n                startActivity(Intent(requireActivity(), HysteriaSettingsActivity::class.java))\n            }\n\n            R.id.action_new_tuic -> {\n                startActivity(Intent(requireActivity(), TuicSettingsActivity::class.java))\n            }\n\n            R.id.action_new_ssh -> {\n                startActivity(Intent(requireActivity(), SSHSettingsActivity::class.java))\n            }\n\n            R.id.action_new_wg -> {\n                startActivity(Intent(requireActivity(), WireGuardSettingsActivity::class.java))\n            }\n\n            R.id.action_new_shadowtls -> {\n                startActivity(Intent(requireActivity(), ShadowTLSSettingsActivity::class.java))\n            }\n\n            R.id.action_new_anytls -> {\n                startActivity(Intent(requireActivity(), AnyTLSSettingsActivity::class.java))\n            }\n\n            R.id.action_new_config -> {\n                startActivity(Intent(requireActivity(), ConfigSettingActivity::class.java))\n            }\n\n            R.id.action_new_chain -> {\n                startActivity(Intent(requireActivity(), ChainSettingsActivity::class.java))\n            }\n\n            R.id.action_update_subscription -> {\n                val group = DataStore.currentGroup()\n                if (group.type != GroupType.SUBSCRIPTION) {\n                    snackbar(R.string.group_not_subscription).show()\n                    Logs.e(\"onMenuItemClick: Group(${group.displayName()}) is not subscription\")\n                } else {\n                    runOnLifecycleDispatcher {\n                        GroupUpdater.startUpdate(group, true)\n                    }\n                }\n            }\n\n            R.id.action_clear_traffic_statistics -> {\n                runOnDefaultDispatcher {\n                    val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())\n                    val toClear = mutableListOf<ProxyEntity>()\n                    if (profiles.isNotEmpty()) for (profile in profiles) {\n                        if (profile.tx != 0L || profile.rx != 0L) {\n                            profile.tx = 0\n                            profile.rx = 0\n                            toClear.add(profile)\n                        }\n                    }\n                    if (toClear.isNotEmpty()) {\n                        ProfileManager.updateProfile(toClear)\n                    }\n                }\n            }\n\n            R.id.action_connection_test_clear_results -> {\n                runOnDefaultDispatcher {\n                    val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())\n                    val toClear = mutableListOf<ProxyEntity>()\n                    if (profiles.isNotEmpty()) for (profile in profiles) {\n                        if (profile.status != 0) {\n                            profile.status = 0\n                            profile.ping = 0\n                            profile.error = null\n                            toClear.add(profile)\n                        }\n                    }\n                    if (toClear.isNotEmpty()) {\n                        ProfileManager.updateProfile(toClear)\n                    }\n                }\n            }\n\n            R.id.action_connection_test_delete_unavailable -> {\n                runOnDefaultDispatcher {\n                    val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())\n                    val toClear = mutableListOf<ProxyEntity>()\n                    if (profiles.isNotEmpty()) for (profile in profiles) {\n                        if (profile.status != 0 && profile.status != 1) {\n                            toClear.add(profile)\n                        }\n                    }\n                    if (toClear.isNotEmpty()) {\n                        onMainDispatcher {\n                            MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)\n                                .setMessage(R.string.delete_confirm_prompt)\n                                .setPositiveButton(R.string.yes) { _, _ ->\n                                    for (profile in toClear) {\n                                        adapter.groupFragments[DataStore.selectedGroup]?.adapter?.apply {\n                                            val index = configurationIdList.indexOf(profile.id)\n                                            if (index >= 0) {\n                                                configurationIdList.removeAt(index)\n                                                configurationList.remove(profile.id)\n                                                notifyItemRemoved(index)\n                                            }\n                                        }\n                                    }\n                                    runOnDefaultDispatcher {\n                                        for (profile in toClear) {\n                                            ProfileManager.deleteProfile2(\n                                                profile.groupId, profile.id\n                                            )\n                                        }\n                                    }\n                                }\n                                .setNegativeButton(R.string.no, null)\n                                .show()\n                        }\n                    }\n                }\n            }\n\n            R.id.action_remove_duplicate -> {\n                runOnDefaultDispatcher {\n                    val profiles = SagerDatabase.proxyDao.getByGroup(DataStore.currentGroupId())\n                    val toClear = mutableListOf<ProxyEntity>()\n                    val uniqueProxies = LinkedHashSet<Protocols.Deduplication>()\n                    for (pf in profiles) {\n                        val proxy = Protocols.Deduplication(pf.requireBean(), pf.displayType())\n                        if (!uniqueProxies.add(proxy)) {\n                            toClear += pf\n                        }\n                    }\n                    if (toClear.isNotEmpty()) {\n                        onMainDispatcher {\n                            MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)\n                                .setMessage(\n                                    getString(R.string.delete_confirm_prompt) + \"\\n\" +\n                                            toClear.mapIndexedNotNull { index, proxyEntity ->\n                                                if (index < 20) {\n                                                    proxyEntity.displayName()\n                                                } else if (index == 20) {\n                                                    \"......\"\n                                                } else {\n                                                    null\n                                                }\n                                            }.joinToString(\"\\n\")\n                                )\n                                .setPositiveButton(R.string.yes) { _, _ ->\n                                    for (profile in toClear) {\n                                        adapter.groupFragments[DataStore.selectedGroup]?.adapter?.apply {\n                                            val index = configurationIdList.indexOf(profile.id)\n                                            if (index >= 0) {\n                                                configurationIdList.removeAt(index)\n                                                configurationList.remove(profile.id)\n                                                notifyItemRemoved(index)\n                                            }\n                                        }\n                                    }\n                                    runOnDefaultDispatcher {\n                                        for (profile in toClear) {\n                                            ProfileManager.deleteProfile2(\n                                                profile.groupId, profile.id\n                                            )\n                                        }\n                                    }\n                                }\n                                .setNegativeButton(R.string.no, null)\n                                .show()\n                        }\n                    }\n                }\n            }\n\n            R.id.action_connection_tcp_ping -> {\n                pingTest(false)\n            }\n\n            R.id.action_connection_url_test -> {\n                urlTest()\n            }\n        }\n        return true\n    }\n\n    inner class TestDialog {\n        val binding = LayoutProgressListBinding.inflate(layoutInflater)\n        val builder = MaterialAlertDialogBuilder(requireContext()).setView(binding.root)\n            .setPositiveButton(R.string.minimize) { _, _ ->\n                minimize()\n            }\n            .setNegativeButton(android.R.string.cancel) { _, _ ->\n                cancel()\n            }\n            .setCancelable(false)\n\n        lateinit var cancel: () -> Unit\n        lateinit var minimize: () -> Unit\n\n        val dialogStatus = AtomicInteger(0) // 1: hidden 2: cancelled\n        var notification: ConnectionTestNotification? = null\n\n        val results: MutableSet<ProxyEntity> = ConcurrentHashMap.newKeySet()\n        var proxyN = 0\n        val finishedN = AtomicInteger(0)\n\n        fun update(profile: ProxyEntity) {\n            if (dialogStatus.get() != 2) {\n                results.add(profile)\n            }\n            runOnMainDispatcher {\n                val context = context ?: return@runOnMainDispatcher\n                val progress = finishedN.addAndGet(1)\n                val status = dialogStatus.get()\n                notification?.updateNotification(\n                    progress,\n                    proxyN,\n                    progress >= proxyN || status == 2\n                )\n                if (status >= 1) return@runOnMainDispatcher\n                if (!isAdded) return@runOnMainDispatcher\n\n                // refresh dialog\n\n                var profileStatusText: String? = null\n                var profileStatusColor = 0\n\n                when (profile.status) {\n                    -1 -> {\n                        profileStatusText = profile.error\n                        profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary)\n                    }\n\n                    0 -> {\n                        profileStatusText = getString(R.string.connection_test_testing)\n                        profileStatusColor = context.getColorAttr(android.R.attr.textColorSecondary)\n                    }\n\n                    1 -> {\n                        profileStatusText = getString(R.string.available, profile.ping)\n                        profileStatusColor = context.getColour(R.color.material_green_500)\n                    }\n\n                    2 -> {\n                        profileStatusText = profile.error\n                        profileStatusColor = context.getColour(R.color.material_red_500)\n                    }\n\n                    3 -> {\n                        val err = profile.error ?: \"\"\n                        val msg = Protocols.genFriendlyMsg(err)\n                        profileStatusText = if (msg != err) msg else getString(R.string.unavailable)\n                        profileStatusColor = context.getColour(R.color.material_red_500)\n                    }\n                }\n\n                val text = SpannableStringBuilder().apply {\n                    append(\"\\n\" + profile.displayName())\n                    append(\"\\n\")\n                    append(\n                        profile.displayType(),\n                        ForegroundColorSpan(context.getProtocolColor(profile.type)),\n                        SPAN_EXCLUSIVE_EXCLUSIVE\n                    )\n                    append(\" \")\n                    append(\n                        profileStatusText,\n                        ForegroundColorSpan(profileStatusColor),\n                        SPAN_EXCLUSIVE_EXCLUSIVE\n                    )\n                    append(\"\\n\")\n                }\n\n                binding.nowTesting.text = text\n                binding.progress.text = \"$progress / $proxyN\"\n            }\n        }\n\n    }\n\n    @OptIn(DelicateCoroutinesApi::class)\n    @Suppress(\"EXPERIMENTAL_API_USAGE\")\n    fun pingTest(icmpPing: Boolean) {\n        if (DataStore.runningTest) return else DataStore.runningTest = true\n        val test = TestDialog()\n        val dialog = test.builder.show()\n        val testJobs = mutableListOf<Job>()\n        val group = DataStore.currentGroup()\n\n        val mainJob = runOnDefaultDispatcher {\n            val profilesList = SagerDatabase.proxyDao.getByGroup(group.id).filter {\n                if (icmpPing) {\n                    if (it.requireBean().canICMPing()) {\n                        return@filter true\n                    }\n                } else {\n                    if (it.requireBean().canTCPing()) {\n                        return@filter true\n                    }\n                }\n                return@filter false\n            }\n            test.proxyN = profilesList.size\n            val profiles = ConcurrentLinkedQueue(profilesList)\n            repeat(DataStore.connectionTestConcurrent) {\n                testJobs.add(launch(Dispatchers.IO) {\n                    while (isActive) {\n                        val profile = profiles.poll() ?: break\n\n                        profile.status = 0\n                        var address = profile.requireBean().serverAddress\n                        if (!address.isIpAddress()) {\n                            try {\n                                SagerNet.underlyingNetwork!!.getAllByName(address).apply {\n                                    if (isNotEmpty()) {\n                                        address = this[0].hostAddress\n                                    }\n                                }\n                            } catch (ignored: UnknownHostException) {\n                            }\n                        }\n                        if (!isActive) break\n                        if (!address.isIpAddress()) {\n                            profile.status = 2\n                            profile.error = app.getString(R.string.connection_test_domain_not_found)\n                            test.update(profile)\n                            continue\n                        }\n                        try {\n                            if (icmpPing) {\n                                // removed\n                            } else {\n                                val socket =\n                                    SagerNet.underlyingNetwork?.socketFactory?.createSocket()\n                                        ?: Socket()\n                                try {\n                                    socket.soTimeout = 3000\n                                    socket.bind(InetSocketAddress(0))\n                                    val start = SystemClock.elapsedRealtime()\n                                    socket.connect(\n                                        InetSocketAddress(\n                                            address, profile.requireBean().serverPort\n                                        ), 3000\n                                    )\n                                    if (!isActive) break\n                                    profile.status = 1\n                                    profile.ping = (SystemClock.elapsedRealtime() - start).toInt()\n                                    test.update(profile)\n                                } finally {\n                                    socket.closeQuietly()\n                                }\n                            }\n                        } catch (e: Exception) {\n                            if (!isActive) break\n                            val message = e.readableMessage\n\n                            if (icmpPing) {\n                                profile.status = 2\n                                profile.error = getString(R.string.connection_test_unreachable)\n                            } else {\n                                profile.status = 2\n                                when {\n                                    !message.contains(\"failed:\") -> profile.error =\n                                        getString(R.string.connection_test_timeout)\n\n                                    else -> when {\n                                        message.contains(\"ECONNREFUSED\") -> {\n                                            profile.error =\n                                                getString(R.string.connection_test_refused)\n                                        }\n\n                                        message.contains(\"ENETUNREACH\") -> {\n                                            profile.error =\n                                                getString(R.string.connection_test_unreachable)\n                                        }\n\n                                        else -> {\n                                            profile.status = 3\n                                            profile.error = message\n                                        }\n                                    }\n                                }\n                            }\n                            test.update(profile)\n                        }\n                    }\n                })\n            }\n\n            testJobs.joinAll()\n\n            runOnMainDispatcher {\n                test.cancel()\n            }\n        }\n        test.cancel = {\n            test.dialogStatus.set(2)\n            dialog.dismiss()\n            runOnDefaultDispatcher {\n                mainJob.cancel()\n                testJobs.forEach { it.cancel() }\n                test.results.forEach {\n                    try {\n                        ProfileManager.updateProfile(it)\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                    }\n                }\n                GroupManager.postReload(DataStore.currentGroupId())\n                DataStore.runningTest = false\n            }\n        }\n        test.minimize = {\n            test.dialogStatus.set(1)\n            test.notification = ConnectionTestNotification(\n                dialog.context,\n                \"[${group.displayName()}] ${getString(R.string.connection_test)}\"\n            )\n            dialog.hide()\n        }\n    }\n\n    @OptIn(DelicateCoroutinesApi::class)\n    fun urlTest() {\n        if (DataStore.runningTest) return else DataStore.runningTest = true\n        val test = TestDialog()\n        val dialog = test.builder.show()\n        val testJobs = mutableListOf<Job>()\n        val group = DataStore.currentGroup()\n\n        val mainJob = runOnDefaultDispatcher {\n            val profilesList = SagerDatabase.proxyDao.getByGroup(group.id)\n            test.proxyN = profilesList.size\n            val profiles = ConcurrentLinkedQueue(profilesList)\n            repeat(DataStore.connectionTestConcurrent) {\n                testJobs.add(launch(Dispatchers.IO) {\n                    val urlTest = UrlTest() // note: this is NOT in bg process\n                    while (isActive) {\n                        val profile = profiles.poll() ?: break\n                        profile.status = 0\n\n                        try {\n                            val result = urlTest.doTest(profile)\n                            profile.status = 1\n                            profile.ping = result\n                        } catch (e: PluginManager.PluginNotFoundException) {\n                            profile.status = 2\n                            profile.error = e.readableMessage\n                        } catch (e: Exception) {\n                            profile.status = 3\n                            profile.error = e.readableMessage\n                        }\n\n                        test.update(profile)\n                    }\n                })\n            }\n\n            testJobs.joinAll()\n\n            runOnMainDispatcher {\n                test.cancel()\n            }\n        }\n        test.cancel = {\n            test.dialogStatus.set(2)\n            dialog.dismiss()\n            runOnDefaultDispatcher {\n                mainJob.cancel()\n                testJobs.forEach { it.cancel() }\n                test.results.forEach {\n                    try {\n                        ProfileManager.updateProfile(it)\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                    }\n                }\n                GroupManager.postReload(DataStore.currentGroupId())\n                DataStore.runningTest = false\n            }\n        }\n        test.minimize = {\n            test.dialogStatus.set(1)\n            test.notification = ConnectionTestNotification(\n                dialog.context,\n                \"[${group.displayName()}] ${getString(R.string.connection_test)}\"\n            )\n            dialog.hide()\n        }\n    }\n\n    inner class GroupPagerAdapter : FragmentStateAdapter(this),\n        ProfileManager.Listener,\n        GroupManager.Listener {\n\n        var selectedGroupIndex = 0\n        var groupList: ArrayList<ProxyGroup> = ArrayList()\n        var groupFragments: HashMap<Long, GroupFragment> = HashMap()\n\n        fun reload(now: Boolean = false) {\n\n            if (!select) {\n                groupPager.unregisterOnPageChangeCallback(updateSelectedCallback)\n            }\n\n            runOnDefaultDispatcher {\n                var newGroupList = ArrayList(SagerDatabase.groupDao.allGroups())\n                if (newGroupList.isEmpty()) {\n                    SagerDatabase.groupDao.createGroup(ProxyGroup(ungrouped = true))\n                    newGroupList = ArrayList(SagerDatabase.groupDao.allGroups())\n                }\n                newGroupList.find { it.ungrouped }?.let {\n                    if (SagerDatabase.proxyDao.countByGroup(it.id) == 0L) {\n                        newGroupList.remove(it)\n                    }\n                }\n\n                var selectedGroup = selectedItem?.groupId ?: DataStore.currentGroupId()\n                var set = false\n                if (selectedGroup > 0L) {\n                    selectedGroupIndex = newGroupList.indexOfFirst { it.id == selectedGroup }\n                    set = true\n                } else if (groupList.size == 1) {\n                    selectedGroup = groupList[0].id\n                    if (DataStore.selectedGroup != selectedGroup) {\n                        DataStore.selectedGroup = selectedGroup\n                    }\n                }\n\n                val runFunc = if (now) activity?.let { it::runOnUiThread } else groupPager::post\n                if (runFunc != null) {\n                    runFunc {\n                        groupList = newGroupList\n                        notifyDataSetChanged()\n                        if (set) groupPager.setCurrentItem(selectedGroupIndex, false)\n                        val hideTab = groupList.size < 2\n                        tabLayout.isGone = hideTab\n                        toolbar.elevation = if (hideTab) 0F else dp2px(4).toFloat()\n                        if (!select) {\n                            groupPager.registerOnPageChangeCallback(updateSelectedCallback)\n                        }\n                    }\n                }\n            }\n        }\n\n        init {\n            reload(true)\n        }\n\n        override fun getItemCount(): Int {\n            return groupList.size\n        }\n\n        override fun createFragment(position: Int): Fragment {\n            return GroupFragment().apply {\n                proxyGroup = groupList[position]\n                groupFragments[proxyGroup.id] = this\n                if (position == selectedGroupIndex) {\n                    selected = true\n                }\n            }\n        }\n\n        override fun getItemId(position: Int): Long {\n            return groupList[position].id\n        }\n\n        override fun containsItem(itemId: Long): Boolean {\n            return groupList.any { it.id == itemId }\n        }\n\n        override suspend fun groupAdd(group: ProxyGroup) {\n            tabLayout.post {\n                groupList.add(group)\n\n                if (groupList.any { !it.ungrouped }) tabLayout.post {\n                    tabLayout.visibility = View.VISIBLE\n                }\n\n                notifyItemInserted(groupList.size - 1)\n                tabLayout.getTabAt(groupList.size - 1)?.select()\n            }\n        }\n\n        override suspend fun groupRemoved(groupId: Long) {\n            val index = groupList.indexOfFirst { it.id == groupId }\n            if (index == -1) return\n\n            tabLayout.post {\n                groupList.removeAt(index)\n                notifyItemRemoved(index)\n            }\n        }\n\n        override suspend fun groupUpdated(group: ProxyGroup) {\n            val index = groupList.indexOfFirst { it.id == group.id }\n            if (index == -1) return\n\n            tabLayout.post {\n                tabLayout.getTabAt(index)?.text = group.displayName()\n            }\n        }\n\n        override suspend fun groupUpdated(groupId: Long) = Unit\n\n        override suspend fun onAdd(profile: ProxyEntity) {\n            if (groupList.find { it.id == profile.groupId } == null) {\n                DataStore.selectedGroup = profile.groupId\n                reload()\n            }\n        }\n\n        override suspend fun onUpdated(data: TrafficData) = Unit\n\n        override suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean) = Unit\n\n        override suspend fun onRemoved(groupId: Long, profileId: Long) {\n            val group = groupList.find { it.id == groupId } ?: return\n            if (group.ungrouped && SagerDatabase.proxyDao.countByGroup(groupId) == 0L) {\n                reload()\n            }\n        }\n    }\n\n    class GroupFragment : Fragment() {\n\n        lateinit var proxyGroup: ProxyGroup\n        var selected = false\n\n        override fun onCreateView(\n            inflater: LayoutInflater,\n            container: ViewGroup?,\n            savedInstanceState: Bundle?,\n        ): View {\n            return LayoutProfileListBinding.inflate(inflater).root\n        }\n\n        lateinit var undoManager: UndoSnackbarManager<ProxyEntity>\n        var adapter: ConfigurationAdapter? = null\n\n        override fun onSaveInstanceState(outState: Bundle) {\n            super.onSaveInstanceState(outState)\n\n            if (::proxyGroup.isInitialized) {\n                outState.putParcelable(\"proxyGroup\", proxyGroup)\n            }\n        }\n\n        override fun onViewStateRestored(savedInstanceState: Bundle?) {\n            super.onViewStateRestored(savedInstanceState)\n\n            savedInstanceState?.getParcelable<ProxyGroup>(\"proxyGroup\")?.also {\n                proxyGroup = it\n                onViewCreated(requireView(), null)\n            }\n        }\n\n        private val isEnabled: Boolean\n            get() {\n                return DataStore.serviceState.let { it.canStop || it == BaseService.State.Stopped }\n            }\n\n        lateinit var layoutManager: LinearLayoutManager\n        lateinit var configurationListView: RecyclerView\n\n        val select by lazy {\n            try {\n                (parentFragment as ConfigurationFragment).select\n            } catch (e: Exception) {\n                Logs.e(e)\n                false\n            }\n        }\n        val selectedItem by lazy {\n            try {\n                (parentFragment as ConfigurationFragment).selectedItem\n            } catch (e: Exception) {\n                Logs.e(e)\n                null\n            }\n        }\n\n        override fun onResume() {\n            super.onResume()\n\n            if (::configurationListView.isInitialized && configurationListView.size == 0) {\n                configurationListView.adapter = adapter\n                runOnDefaultDispatcher {\n                    adapter?.reloadProfiles()\n                }\n            } else if (!::configurationListView.isInitialized) {\n                onViewCreated(requireView(), null)\n            }\n            checkOrderMenu()\n            configurationListView.requestFocus()\n        }\n\n        fun checkOrderMenu() {\n            if (select) return\n\n            val pf = requireParentFragment() as? ToolbarFragment ?: return\n            val menu = pf.toolbar.menu\n            val origin = menu.findItem(R.id.action_order_origin)\n            val byName = menu.findItem(R.id.action_order_by_name)\n            val byDelay = menu.findItem(R.id.action_order_by_delay)\n            when (proxyGroup.order) {\n                GroupOrder.ORIGIN -> {\n                    origin.isChecked = true\n                }\n\n                GroupOrder.BY_NAME -> {\n                    byName.isChecked = true\n                }\n\n                GroupOrder.BY_DELAY -> {\n                    byDelay.isChecked = true\n                }\n            }\n\n            fun updateTo(order: Int) {\n                if (proxyGroup.order == order) return\n                runOnDefaultDispatcher {\n                    proxyGroup.order = order\n                    GroupManager.updateGroup(proxyGroup)\n                }\n            }\n\n            origin.setOnMenuItemClickListener {\n                it.isChecked = true\n                updateTo(GroupOrder.ORIGIN)\n                true\n            }\n            byName.setOnMenuItemClickListener {\n                it.isChecked = true\n                updateTo(GroupOrder.BY_NAME)\n                true\n            }\n            byDelay.setOnMenuItemClickListener {\n                it.isChecked = true\n                updateTo(GroupOrder.BY_DELAY)\n                true\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            if (!::proxyGroup.isInitialized) return\n\n            configurationListView = view.findViewById(R.id.configuration_list)\n            layoutManager = FixedLinearLayoutManager(configurationListView)\n            configurationListView.layoutManager = layoutManager\n            adapter = ConfigurationAdapter()\n            ProfileManager.addListener(adapter!!)\n            GroupManager.addListener(adapter!!)\n            configurationListView.adapter = adapter\n            configurationListView.setItemViewCacheSize(20)\n\n            if (!select) {\n\n                undoManager = UndoSnackbarManager(activity as MainActivity, adapter!!)\n\n                ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(\n                    ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START\n                ) {\n                    override fun getSwipeDirs(\n                        recyclerView: RecyclerView,\n                        viewHolder: RecyclerView.ViewHolder,\n                    ): Int {\n                        return 0\n                    }\n\n                    override fun getDragDirs(\n                        recyclerView: RecyclerView,\n                        viewHolder: RecyclerView.ViewHolder,\n                    ) = if (isEnabled) super.getDragDirs(recyclerView, viewHolder) else 0\n\n                    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n                    }\n\n                    override fun onMove(\n                        recyclerView: RecyclerView,\n                        viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,\n                    ): Boolean {\n                        adapter?.move(\n                            viewHolder.bindingAdapterPosition, target.bindingAdapterPosition\n                        )\n                        return true\n                    }\n\n                    override fun clearView(\n                        recyclerView: RecyclerView,\n                        viewHolder: RecyclerView.ViewHolder,\n                    ) {\n                        super.clearView(recyclerView, viewHolder)\n                        adapter?.commitMove()\n                    }\n                }).attachToRecyclerView(configurationListView)\n\n            }\n\n        }\n\n        override fun onDestroy() {\n            adapter?.let {\n                ProfileManager.removeListener(it)\n                GroupManager.removeListener(it)\n            }\n\n            super.onDestroy()\n\n            if (!::undoManager.isInitialized) return\n            undoManager.flush()\n        }\n\n        inner class ConfigurationAdapter : RecyclerView.Adapter<ConfigurationHolder>(),\n            ProfileManager.Listener,\n            GroupManager.Listener,\n            UndoSnackbarManager.Interface<ProxyEntity> {\n\n            init {\n                setHasStableIds(true)\n            }\n\n            var configurationIdList: MutableList<Long> = mutableListOf()\n            val configurationList = HashMap<Long, ProxyEntity>()\n\n            private fun getItem(profileId: Long): ProxyEntity {\n                var profile = configurationList[profileId]\n                if (profile == null) {\n                    profile = ProfileManager.getProfile(profileId)\n                    if (profile != null) {\n                        configurationList[profileId] = profile\n                    }\n                }\n                return profile!!\n            }\n\n            private fun getItemAt(index: Int) = getItem(configurationIdList[index])\n\n            override fun onCreateViewHolder(\n                parent: ViewGroup,\n                viewType: Int,\n            ): ConfigurationHolder {\n                return ConfigurationHolder(\n                    LayoutInflater.from(parent.context)\n                        .inflate(R.layout.layout_profile, parent, false)\n                )\n            }\n\n            override fun getItemId(position: Int): Long {\n                return configurationIdList[position]\n            }\n\n            override fun onBindViewHolder(holder: ConfigurationHolder, position: Int) {\n                try {\n                    holder.bind(getItemAt(position))\n                } catch (ignored: NullPointerException) { // when group deleted\n                }\n            }\n\n            override fun getItemCount(): Int {\n                return configurationIdList.size\n            }\n\n            private val updated = HashSet<ProxyEntity>()\n\n            fun filter(name: String) {\n                if (name.isEmpty()) {\n                    reloadProfiles()\n                    return\n                }\n                configurationIdList.clear()\n                val lower = name.lowercase()\n                configurationIdList.addAll(configurationList.filter {\n                    it.value.displayName().lowercase().contains(lower) ||\n                            it.value.displayType().lowercase().contains(lower) ||\n                            it.value.displayAddress().lowercase().contains(lower)\n                }.keys)\n                notifyDataSetChanged()\n            }\n\n            fun move(from: Int, to: Int) {\n                val first = getItemAt(from)\n                var previousOrder = first.userOrder\n                val (step, range) = if (from < to) Pair(1, from until to) else Pair(\n                    -1, to + 1 downTo from\n                )\n                for (i in range) {\n                    val next = getItemAt(i + step)\n                    val order = next.userOrder\n                    next.userOrder = previousOrder\n                    previousOrder = order\n                    configurationIdList[i] = next.id\n                    updated.add(next)\n                }\n                first.userOrder = previousOrder\n                configurationIdList[to] = first.id\n                updated.add(first)\n                notifyItemMoved(from, to)\n            }\n\n            fun commitMove() = runOnDefaultDispatcher {\n                updated.forEach { SagerDatabase.proxyDao.updateProxy(it) }\n                updated.clear()\n            }\n\n            fun remove(pos: Int) {\n                if (pos < 0) return\n                configurationIdList.removeAt(pos)\n                notifyItemRemoved(pos)\n            }\n\n            override fun undo(actions: List<Pair<Int, ProxyEntity>>) {\n                for ((index, item) in actions) {\n                    configurationListView.post {\n                        configurationList[item.id] = item\n                        configurationIdList.add(index, item.id)\n                        notifyItemInserted(index)\n                    }\n                }\n            }\n\n            override fun commit(actions: List<Pair<Int, ProxyEntity>>) {\n                val profiles = actions.map { it.second }\n                runOnDefaultDispatcher {\n                    for (entity in profiles) {\n                        ProfileManager.deleteProfile(entity.groupId, entity.id)\n                    }\n                }\n            }\n\n            override suspend fun onAdd(profile: ProxyEntity) {\n                if (profile.groupId != proxyGroup.id) return\n\n                configurationListView.post {\n                    if (::undoManager.isInitialized) {\n                        undoManager.flush()\n                    }\n                    val pos = itemCount\n                    configurationList[profile.id] = profile\n                    configurationIdList.add(profile.id)\n                    notifyItemInserted(pos)\n                }\n            }\n\n            override suspend fun onUpdated(profile: ProxyEntity, noTraffic: Boolean) {\n                if (profile.groupId != proxyGroup.id) return\n                val index = configurationIdList.indexOf(profile.id)\n                if (index < 0) return\n                configurationListView.post {\n                    if (::undoManager.isInitialized) {\n                        undoManager.flush()\n                    }\n                    configurationList[profile.id] = profile\n                    notifyItemChanged(index)\n                    //\n                    val oldProfile = configurationList[profile.id]\n                    if (noTraffic && oldProfile != null) {\n                        runOnDefaultDispatcher {\n                            onUpdated(\n                                TrafficData(\n                                    id = profile.id,\n                                    rx = oldProfile.rx,\n                                    tx = oldProfile.tx\n                                )\n                            )\n                        }\n                    }\n                }\n            }\n\n            override suspend fun onUpdated(data: TrafficData) {\n                try {\n                    val index = configurationIdList.indexOf(data.id)\n                    if (index != -1) {\n                        val holder = layoutManager.findViewByPosition(index)\n                            ?.let { configurationListView.getChildViewHolder(it) } as ConfigurationHolder?\n                        if (holder != null) {\n                            onMainDispatcher {\n                                holder.bind(holder.entity, data)\n                            }\n                        }\n                    }\n                } catch (e: Exception) {\n                    Logs.w(e)\n                }\n            }\n\n            override suspend fun onRemoved(groupId: Long, profileId: Long) {\n                if (groupId != proxyGroup.id) return\n                val index = configurationIdList.indexOf(profileId)\n                if (index < 0) return\n\n                configurationListView.post {\n                    configurationIdList.removeAt(index)\n                    configurationList.remove(profileId)\n                    notifyItemRemoved(index)\n                }\n            }\n\n            override suspend fun groupAdd(group: ProxyGroup) = Unit\n            override suspend fun groupRemoved(groupId: Long) = Unit\n\n            override suspend fun groupUpdated(group: ProxyGroup) {\n                if (group.id != proxyGroup.id) return\n                proxyGroup = group\n                reloadProfiles()\n            }\n\n            override suspend fun groupUpdated(groupId: Long) {\n                if (groupId != proxyGroup.id) return\n                proxyGroup = SagerDatabase.groupDao.getById(groupId)!!\n                reloadProfiles()\n            }\n\n            fun reloadProfiles() {\n                var newProfiles = SagerDatabase.proxyDao.getByGroup(proxyGroup.id)\n                when (proxyGroup.order) {\n                    GroupOrder.BY_NAME -> {\n                        newProfiles = newProfiles.sortedBy { it.displayName() }\n\n                    }\n\n                    GroupOrder.BY_DELAY -> {\n                        newProfiles =\n                            newProfiles.sortedBy { if (it.status == 1) it.ping else 114514 }\n                    }\n                }\n\n                configurationList.clear()\n                configurationList.putAll(newProfiles.associateBy { it.id })\n                val newProfileIds = newProfiles.map { it.id }\n\n                var selectedProfileIndex = -1\n\n                if (selected) {\n                    val selectedProxy = selectedItem?.id ?: DataStore.selectedProxy\n                    selectedProfileIndex = newProfileIds.indexOf(selectedProxy)\n                }\n\n                configurationListView.post {\n                    configurationIdList.clear()\n                    configurationIdList.addAll(newProfileIds)\n                    notifyDataSetChanged()\n\n                    if (selectedProfileIndex != -1) {\n                        configurationListView.scrollTo(selectedProfileIndex, true)\n                    } else if (newProfiles.isNotEmpty()) {\n                        configurationListView.scrollTo(0, true)\n                    }\n\n                }\n            }\n\n        }\n\n        val profileAccess = Mutex()\n        val reloadAccess = Mutex()\n\n        inner class ConfigurationHolder(val view: View) : RecyclerView.ViewHolder(view),\n            PopupMenu.OnMenuItemClickListener {\n\n            lateinit var entity: ProxyEntity\n\n            val profileName: TextView = view.findViewById(R.id.profile_name)\n            val profileType: TextView = view.findViewById(R.id.profile_type)\n            val profileAddress: TextView = view.findViewById(R.id.profile_address)\n            val profileStatus: TextView = view.findViewById(R.id.profile_status)\n\n            val trafficText: TextView = view.findViewById(R.id.traffic_text)\n            val selectedView: LinearLayout = view.findViewById(R.id.selected_view)\n            val editButton: ImageView = view.findViewById(R.id.edit)\n            val shareLayout: LinearLayout = view.findViewById(R.id.share)\n            val shareLayer: LinearLayout = view.findViewById(R.id.share_layer)\n            val shareButton: ImageView = view.findViewById(R.id.shareIcon)\n            val removeButton: ImageView = view.findViewById(R.id.remove)\n\n            fun bind(proxyEntity: ProxyEntity, trafficData: TrafficData? = null) {\n                val pf = parentFragment as? ConfigurationFragment ?: return\n\n                entity = proxyEntity\n\n                if (select) {\n                    view.setOnClickListener {\n                        (requireActivity() as SelectCallback).returnProfile(proxyEntity.id)\n                    }\n                } else {\n                    view.setOnClickListener {\n                        runOnDefaultDispatcher {\n                            var update: Boolean\n                            var lastSelected: Long\n                            profileAccess.withLock {\n                                update = DataStore.selectedProxy != proxyEntity.id\n                                lastSelected = DataStore.selectedProxy\n                                DataStore.selectedProxy = proxyEntity.id\n                                onMainDispatcher {\n                                    selectedView.visibility = View.VISIBLE\n                                }\n                            }\n\n                            if (update) {\n                                ProfileManager.postUpdate(lastSelected)\n                                if (DataStore.serviceState.canStop && reloadAccess.tryLock()) {\n                                    SagerNet.reloadService()\n                                    reloadAccess.unlock()\n                                }\n                            } else if (SagerNet.isTv) {\n                                if (DataStore.serviceState.started) {\n                                    SagerNet.stopService()\n                                } else {\n                                    SagerNet.startService()\n                                }\n                            }\n                        }\n\n                    }\n                }\n\n                profileName.text = proxyEntity.displayName()\n                profileType.text = proxyEntity.displayType()\n                profileType.setTextColor(requireContext().getProtocolColor(proxyEntity.type))\n\n                var rx = proxyEntity.rx\n                var tx = proxyEntity.tx\n                if (trafficData != null) {\n                    // use new data\n                    tx = trafficData.tx\n                    rx = trafficData.rx\n                }\n\n                val showTraffic = rx + tx != 0L\n                trafficText.isVisible = showTraffic\n                if (showTraffic) {\n                    trafficText.text = view.context.getString(\n                        R.string.traffic,\n                        Formatter.formatFileSize(view.context, tx),\n                        Formatter.formatFileSize(view.context, rx)\n                    )\n                }\n\n                var address = proxyEntity.displayAddress()\n                if (showTraffic && address.length >= 30) {\n                    address = address.substring(0, 27) + \"...\"\n                }\n\n                if (proxyEntity.requireBean().name.isBlank() || !pf.alwaysShowAddress) {\n                    address = \"\"\n                }\n\n                profileAddress.text = address\n                (trafficText.parent as View).isGone =\n                    (!showTraffic || proxyEntity.status <= 0) && address.isBlank()\n\n                if (proxyEntity.status <= 0) {\n                    if (showTraffic) {\n                        profileStatus.text = trafficText.text\n                        profileStatus.setTextColor(requireContext().getColorAttr(android.R.attr.textColorSecondary))\n                        trafficText.text = \"\"\n                    } else {\n                        profileStatus.text = \"\"\n                    }\n                } else if (proxyEntity.status == 1) {\n                    profileStatus.text = getString(R.string.available, proxyEntity.ping)\n                    profileStatus.setTextColor(requireContext().getColour(R.color.material_green_500))\n                } else {\n                    profileStatus.setTextColor(requireContext().getColour(R.color.material_red_500))\n                    if (proxyEntity.status == 2) {\n                        profileStatus.text = proxyEntity.error\n                    }\n                }\n\n                if (proxyEntity.status == 3) {\n                    val err = proxyEntity.error ?: \"<?>\"\n                    val msg = Protocols.genFriendlyMsg(err)\n                    profileStatus.text = if (msg != err) msg else getString(R.string.unavailable)\n                    profileStatus.setOnClickListener {\n                        alert(err).tryToShow()\n                    }\n                } else {\n                    profileStatus.setOnClickListener(null)\n                }\n\n                editButton.setOnClickListener {\n                    it.context.startActivity(\n                        proxyEntity.settingIntent(\n                            it.context, proxyGroup.type == GroupType.SUBSCRIPTION\n                        )\n                    )\n                }\n\n                removeButton.setOnClickListener {\n                    adapter?.let {\n                        val index = it.configurationIdList.indexOf(proxyEntity.id)\n                        it.remove(index)\n                        undoManager.remove(index to proxyEntity)\n                    }\n                }\n\n                val selectOrChain = select || proxyEntity.type == ProxyEntity.TYPE_CHAIN\n                shareLayout.isGone = selectOrChain\n                editButton.isGone = select\n                removeButton.isGone = select\n\n                proxyEntity.nekoBean?.apply {\n                    shareLayout.isGone = true\n                }\n\n                runOnDefaultDispatcher {\n                    val selected = (selectedItem?.id ?: DataStore.selectedProxy) == proxyEntity.id\n                    val started =\n                        selected && DataStore.serviceState.started && DataStore.currentProfile == proxyEntity.id\n                    onMainDispatcher {\n                        editButton.isEnabled = !started\n                        removeButton.isEnabled = !started\n                        selectedView.visibility = if (selected) View.VISIBLE else View.INVISIBLE\n                    }\n\n                    fun showShare(anchor: View) {\n                        val popup = PopupMenu(requireContext(), anchor)\n                        popup.menuInflater.inflate(R.menu.profile_share_menu, popup.menu)\n\n                        when {\n                            !proxyEntity.haveStandardLink() -> {\n                                popup.menu.findItem(R.id.action_group_qr).subMenu?.removeItem(R.id.action_standard_qr)\n                                popup.menu.findItem(R.id.action_group_clipboard).subMenu?.removeItem(\n                                    R.id.action_standard_clipboard\n                                )\n                            }\n\n                            !proxyEntity.haveLink() -> {\n                                popup.menu.removeItem(R.id.action_group_qr)\n                                popup.menu.removeItem(R.id.action_group_clipboard)\n                            }\n                        }\n\n                        if (proxyEntity.nekoBean != null) {\n                            popup.menu.removeItem(R.id.action_group_configuration)\n                        }\n\n                        popup.setOnMenuItemClickListener(this@ConfigurationHolder)\n                        popup.show()\n                    }\n\n                    if (!(select || proxyEntity.type == ProxyEntity.TYPE_CHAIN)) {\n                        onMainDispatcher {\n                            shareLayer.setBackgroundColor(Color.TRANSPARENT)\n                            shareButton.setImageResource(R.drawable.ic_social_share)\n                            shareButton.setColorFilter(Color.GRAY)\n                            shareButton.isVisible = true\n\n                            shareLayout.setOnClickListener {\n                                showShare(it)\n                            }\n                        }\n                    }\n                }\n\n            }\n\n            var currentName = \"\"\n            fun showCode(link: String) {\n                QRCodeDialog(link, currentName).showAllowingStateLoss(parentFragmentManager)\n            }\n\n            fun export(link: String) {\n                val success = SagerNet.trySetPrimaryClip(link)\n                (activity as MainActivity).snackbar(if (success) R.string.action_export_msg else R.string.action_export_err)\n                    .show()\n            }\n\n            override fun onMenuItemClick(item: MenuItem): Boolean {\n                try {\n                    currentName = entity.displayName()!!\n                    when (item.itemId) {\n                        R.id.action_standard_qr -> showCode(entity.toStdLink())\n                        R.id.action_standard_clipboard -> export(entity.toStdLink())\n                        R.id.action_universal_qr -> showCode(entity.requireBean().toUniversalLink())\n                        R.id.action_universal_clipboard -> export(\n                            entity.requireBean().toUniversalLink()\n                        )\n\n                        R.id.action_config_export_clipboard -> export(entity.exportConfig().first)\n                        R.id.action_config_export_file -> {\n                            val cfg = entity.exportConfig()\n                            DataStore.serverConfig = cfg.first\n                            startFilesForResult(\n                                (parentFragment as ConfigurationFragment).exportConfig, cfg.second\n                            )\n                        }\n                    }\n                } catch (e: Exception) {\n                    Logs.w(e)\n                    (activity as MainActivity).snackbar(e.readableMessage).show()\n                    return true\n                }\n                return true\n            }\n        }\n\n    }\n\n    private val exportConfig =\n        registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->\n            if (data != null) {\n                runOnDefaultDispatcher {\n                    try {\n                        (requireActivity() as MainActivity).contentResolver.openOutputStream(data)!!\n                            .bufferedWriter()\n                            .use {\n                                it.write(DataStore.serverConfig)\n                            }\n                        onMainDispatcher {\n                            snackbar(getString(R.string.action_export_msg)).show()\n                        }\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                        onMainDispatcher {\n                            snackbar(e.readableMessage).show()\n                        }\n                    }\n\n                }\n            }\n        }\n\n    private fun cancelSearch(searchView: SearchView) {\n        searchView.onActionViewCollapsed()\n        searchView.clearFocus()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/GroupFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.format.Formatter\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.*\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.*\nimport io.nekohasekai.sagernet.databinding.LayoutGroupItemBinding\nimport io.nekohasekai.sagernet.fmt.toUniversalLink\nimport io.nekohasekai.sagernet.group.GroupUpdater\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.widget.ListListener\nimport io.nekohasekai.sagernet.widget.QRCodeDialog\nimport io.nekohasekai.sagernet.widget.UndoSnackbarManager\nimport kotlinx.coroutines.delay\nimport moe.matsuri.nb4a.utils.Util\nimport moe.matsuri.nb4a.utils.toBytesString\nimport java.lang.NumberFormatException\nimport java.util.*\n\nclass GroupFragment : ToolbarFragment(R.layout.layout_group),\n    Toolbar.OnMenuItemClickListener {\n\n    lateinit var activity: MainActivity\n    lateinit var groupListView: RecyclerView\n    lateinit var layoutManager: LinearLayoutManager\n    lateinit var groupAdapter: GroupAdapter\n    lateinit var undoManager: UndoSnackbarManager<ProxyGroup>\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity = requireActivity() as MainActivity\n\n        ViewCompat.setOnApplyWindowInsetsListener(view, ListListener)\n        toolbar.setTitle(R.string.menu_group)\n        toolbar.inflateMenu(R.menu.add_group_menu)\n        toolbar.setOnMenuItemClickListener(this)\n\n        groupListView = view.findViewById(R.id.group_list)\n        layoutManager = FixedLinearLayoutManager(groupListView)\n        groupListView.layoutManager = layoutManager\n        groupAdapter = GroupAdapter()\n        GroupManager.addListener(groupAdapter)\n        groupListView.adapter = groupAdapter\n\n        undoManager = UndoSnackbarManager(activity, groupAdapter)\n\n        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(\n            ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START\n        ) {\n            override fun getSwipeDirs(\n                recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder\n            ): Int {\n                val proxyGroup = (viewHolder as GroupHolder).proxyGroup\n                if (proxyGroup.ungrouped || proxyGroup.id in GroupUpdater.updating) {\n                    return 0\n                }\n                return super.getSwipeDirs(recyclerView, viewHolder)\n            }\n\n            override fun getDragDirs(\n                recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder\n            ): Int {\n                val proxyGroup = (viewHolder as GroupHolder).proxyGroup\n                if (proxyGroup.ungrouped || proxyGroup.id in GroupUpdater.updating) {\n                    return 0\n                }\n                return super.getDragDirs(recyclerView, viewHolder)\n            }\n\n            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n                val index = viewHolder.bindingAdapterPosition\n                groupAdapter.remove(index)\n                undoManager.remove(index to (viewHolder as GroupHolder).proxyGroup)\n            }\n\n            override fun onMove(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,\n            ): Boolean {\n                groupAdapter.move(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)\n                return true\n            }\n\n            override fun clearView(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) {\n                super.clearView(recyclerView, viewHolder)\n                groupAdapter.commitMove()\n            }\n        }).attachToRecyclerView(groupListView)\n\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_new_group -> {\n                startActivity(Intent(context, GroupSettingsActivity::class.java))\n            }\n\n            R.id.action_update_all -> {\n                MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)\n                    .setMessage(R.string.update_all_subscription)\n                    .setPositiveButton(R.string.yes) { _, _ ->\n                        SagerDatabase.groupDao.allGroups()\n                            .filter { it.type == GroupType.SUBSCRIPTION }\n                            .forEach {\n                                GroupUpdater.startUpdate(it, true)\n                            }\n                    }\n                    .setNegativeButton(R.string.no, null)\n                    .show()\n            }\n        }\n        return true\n    }\n\n    private lateinit var selectedGroup: ProxyGroup\n\n    private val exportProfiles =\n        registerForActivityResult(ActivityResultContracts.CreateDocument()) { data ->\n            if (data != null) {\n                runOnDefaultDispatcher {\n                    val profiles = SagerDatabase.proxyDao.getByGroup(selectedGroup.id)\n                    val links = profiles.joinToString(\"\\n\") { it.toStdLink(compact = true) }\n                    try {\n                        (requireActivity() as MainActivity).contentResolver.openOutputStream(\n                            data\n                        )!!.bufferedWriter().use {\n                            it.write(links)\n                        }\n                        onMainDispatcher {\n                            snackbar(getString(R.string.action_export_msg)).show()\n                        }\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                        onMainDispatcher {\n                            snackbar(e.readableMessage).show()\n                        }\n                    }\n\n                }\n            }\n        }\n\n    inner class GroupAdapter : RecyclerView.Adapter<GroupHolder>(),\n        GroupManager.Listener,\n        UndoSnackbarManager.Interface<ProxyGroup> {\n\n        val groupList = ArrayList<ProxyGroup>()\n\n        suspend fun reload() {\n            val groups = SagerDatabase.groupDao.allGroups().toMutableList()\n            if (groups.size > 1 && SagerDatabase.proxyDao.countByGroup(groups.find { it.ungrouped }!!.id) == 0L) groups.removeAll { it.ungrouped }\n            groupList.clear()\n            groupList.addAll(groups)\n            groupListView.post {\n                notifyDataSetChanged()\n            }\n        }\n\n        init {\n            setHasStableIds(true)\n\n            runOnDefaultDispatcher {\n                reload()\n            }\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupHolder {\n            return GroupHolder(LayoutGroupItemBinding.inflate(layoutInflater, parent, false))\n        }\n\n        override fun onBindViewHolder(holder: GroupHolder, position: Int) {\n            holder.bind(groupList[position])\n        }\n\n        override fun getItemCount(): Int {\n            return groupList.size\n        }\n\n        override fun getItemId(position: Int): Long {\n            return groupList[position].id\n        }\n\n        private val updated = HashSet<ProxyGroup>()\n\n        fun move(from: Int, to: Int) {\n            val first = groupList[from]\n            var previousOrder = first.userOrder\n            val (step, range) = if (from < to) Pair(1, from until to) else Pair(\n                -1, to + 1 downTo from\n            )\n            for (i in range) {\n                val next = groupList[i + step]\n                val order = next.userOrder\n                next.userOrder = previousOrder\n                previousOrder = order\n                groupList[i] = next\n                updated.add(next)\n            }\n            first.userOrder = previousOrder\n            groupList[to] = first\n            updated.add(first)\n            notifyItemMoved(from, to)\n        }\n\n        fun commitMove() = runOnDefaultDispatcher {\n            updated.forEach { SagerDatabase.groupDao.updateGroup(it) }\n            updated.clear()\n        }\n\n        fun remove(index: Int) {\n            groupList.removeAt(index)\n            notifyItemRemoved(index)\n        }\n\n        override fun undo(actions: List<Pair<Int, ProxyGroup>>) {\n            for ((index, item) in actions) {\n                groupList.add(index, item)\n                notifyItemInserted(index)\n            }\n        }\n\n        override fun commit(actions: List<Pair<Int, ProxyGroup>>) {\n            val groups = actions.map { it.second }\n            runOnDefaultDispatcher {\n                GroupManager.deleteGroup(groups)\n                reload()\n            }\n        }\n\n        override suspend fun groupAdd(group: ProxyGroup) {\n            groupList.add(group)\n            delay(300L)\n\n            onMainDispatcher {\n                undoManager.flush()\n                notifyItemInserted(groupList.size - 1)\n\n                if (group.type == GroupType.SUBSCRIPTION) {\n                    GroupUpdater.startUpdate(group, true)\n                }\n            }\n        }\n\n        override suspend fun groupRemoved(groupId: Long) {\n            val index = groupList.indexOfFirst { it.id == groupId }\n            if (index == -1) return\n            onMainDispatcher {\n                undoManager.flush()\n                if (SagerDatabase.groupDao.allGroups().size <= 2) {\n                    runOnDefaultDispatcher {\n                        reload()\n                    }\n                } else {\n                    groupList.removeAt(index)\n                    notifyItemRemoved(index)\n                }\n            }\n        }\n\n        override suspend fun groupUpdated(group: ProxyGroup) {\n            val index = groupList.indexOfFirst { it.id == group.id }\n            if (index == -1) {\n                reload()\n                return\n            }\n            groupList[index] = group\n            onMainDispatcher {\n                undoManager.flush()\n\n                notifyItemChanged(index)\n            }\n        }\n\n        override suspend fun groupUpdated(groupId: Long) {\n            val index = groupList.indexOfFirst { it.id == groupId }\n            if (index == -1) {\n                reload()\n                return\n            }\n            onMainDispatcher {\n                notifyItemChanged(index)\n            }\n        }\n\n    }\n\n    override fun onDestroy() {\n        if (::groupAdapter.isInitialized) {\n            GroupManager.removeListener(groupAdapter)\n        }\n\n        super.onDestroy()\n\n        if (!::undoManager.isInitialized) return\n        undoManager.flush()\n    }\n\n    inner class GroupHolder(binding: LayoutGroupItemBinding) :\n        RecyclerView.ViewHolder(binding.root),\n        PopupMenu.OnMenuItemClickListener {\n\n        lateinit var proxyGroup: ProxyGroup\n        val groupName = binding.groupName\n        val groupStatus = binding.groupStatus\n        val groupTraffic = binding.groupTraffic\n        val groupUser = binding.groupUser\n        val editButton = binding.edit\n        val optionsButton = binding.options\n        val updateButton = binding.groupUpdate\n        val subscriptionUpdateProgress = binding.subscriptionUpdateProgress\n\n        override fun onMenuItemClick(item: MenuItem): Boolean {\n\n            fun export(link: String) {\n                val success = SagerNet.trySetPrimaryClip(link)\n                activity.snackbar(if (success) R.string.action_export_msg else R.string.action_export_err)\n                    .show()\n            }\n\n            when (item.itemId) {\n                R.id.action_universal_qr -> {\n                    QRCodeDialog(\n                        proxyGroup.toUniversalLink(), proxyGroup.displayName()\n                    ).showAllowingStateLoss(parentFragmentManager)\n                }\n\n                R.id.action_universal_clipboard -> {\n                    export(proxyGroup.toUniversalLink())\n                }\n\n                R.id.action_export_clipboard -> {\n                    runOnDefaultDispatcher {\n                        val profiles = SagerDatabase.proxyDao.getByGroup(selectedGroup.id)\n                        val links = profiles.joinToString(\"\\n\") { it.toStdLink(compact = true) }\n                        onMainDispatcher {\n                            SagerNet.trySetPrimaryClip(links)\n                            snackbar(getString(R.string.copy_toast_msg)).show()\n                        }\n                    }\n                }\n\n                R.id.action_export_file -> {\n                    startFilesForResult(exportProfiles, \"profiles_${proxyGroup.displayName()}.txt\")\n                }\n\n                R.id.action_clear -> {\n                    MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.confirm)\n                        .setMessage(R.string.clear_profiles_message)\n                        .setPositiveButton(R.string.yes) { _, _ ->\n                            runOnDefaultDispatcher {\n                                GroupManager.clearGroup(proxyGroup.id)\n                            }\n                        }\n                        .setNegativeButton(android.R.string.cancel, null)\n                        .show()\n                }\n            }\n\n            return true\n        }\n\n\n        fun bind(group: ProxyGroup) {\n            proxyGroup = group\n\n            itemView.setOnClickListener { }\n\n            editButton.isGone = proxyGroup.ungrouped\n            updateButton.isInvisible = proxyGroup.type != GroupType.SUBSCRIPTION\n            groupName.text = proxyGroup.displayName()\n\n            editButton.setOnClickListener {\n                startActivity(Intent(it.context, GroupSettingsActivity::class.java).apply {\n                    putExtra(GroupSettingsActivity.EXTRA_GROUP_ID, group.id)\n                })\n            }\n\n            updateButton.setOnClickListener {\n                GroupUpdater.startUpdate(proxyGroup, true)\n            }\n\n            optionsButton.setOnClickListener {\n                selectedGroup = proxyGroup\n\n                val popup = PopupMenu(requireContext(), it)\n                popup.menuInflater.inflate(R.menu.group_action_menu, popup.menu)\n\n                if (proxyGroup.type != GroupType.SUBSCRIPTION) {\n                    popup.menu.removeItem(R.id.action_share_subscription)\n                }\n                popup.setOnMenuItemClickListener(this)\n                popup.show()\n            }\n\n            if (proxyGroup.id in GroupUpdater.updating) {\n                (groupName.parent as LinearLayout).apply {\n                    setPadding(paddingLeft, dp2px(11), paddingRight, paddingBottom)\n                }\n\n                subscriptionUpdateProgress.isVisible = true\n\n                if (!GroupUpdater.progress.containsKey(proxyGroup.id)) {\n                    subscriptionUpdateProgress.isIndeterminate = true\n                } else {\n                    subscriptionUpdateProgress.isIndeterminate = false\n                    GroupUpdater.progress[proxyGroup.id]?.let {\n                        subscriptionUpdateProgress.max = it.max\n                        subscriptionUpdateProgress.progress = it.progress\n                    }\n                }\n\n                updateButton.isInvisible = true\n                editButton.isGone = true\n            } else {\n                (groupName.parent as LinearLayout).apply {\n                    setPadding(paddingLeft, dp2px(15), paddingRight, paddingBottom)\n                }\n\n                subscriptionUpdateProgress.isVisible = false\n                updateButton.isInvisible = proxyGroup.type != GroupType.SUBSCRIPTION\n                editButton.isGone = proxyGroup.ungrouped\n            }\n\n            val subscription = proxyGroup.subscription\n            if (subscription != null && subscription.bytesUsed > 0L) { // SIP008 & Open Online Config\n                groupTraffic.isVisible = true\n                groupTraffic.text = if (subscription.bytesRemaining > 0L) {\n                    app.getString(\n                        R.string.subscription_traffic, Formatter.formatFileSize(\n                            app, subscription.bytesUsed\n                        ), Formatter.formatFileSize(\n                            app, subscription.bytesRemaining\n                        )\n                    )\n                } else {\n                    app.getString(\n                        R.string.subscription_used, Formatter.formatFileSize(\n                            app, subscription.bytesUsed\n                        )\n                    )\n                }\n                groupStatus.setPadding(0)\n            } else if (subscription != null && !subscription.subscriptionUserinfo.isNullOrBlank()) { // Raw\n                var text = \"\"\n\n                fun get(regex: String): String? {\n                    return regex.toRegex().findAll(subscription.subscriptionUserinfo).mapNotNull {\n                        if (it.groupValues.size > 1) it.groupValues[1] else null\n                    }.firstOrNull()\n                }\n\n                try {\n                    var used: Long = 0\n                    get(\"upload=([0-9]+)\")?.apply {\n                        used += toLong()\n                    }\n                    get(\"download=([0-9]+)\")?.apply {\n                        used += toLong()\n                    }\n                    val total = get(\"total=([0-9]+)\")?.toLong() ?: 0\n                    val remain = total - used\n                    if (used > 0 || total > 0) {\n                        text += if (remain > 0) {\n                            getString(\n                                R.string.subscription_traffic,\n                                used.toBytesString(),\n                                remain.toBytesString()\n                            )\n                        } else {\n                            getString(R.string.subscription_used, used.toBytesString())\n                        }\n                    }\n                    get(\"expire=([0-9]+)\")?.apply {\n                        text += \"\\n\"\n                        text += getString(\n                            R.string.subscription_expire,\n                            Util.timeStamp2Text(this.toLong() * 1000)\n                        )\n                    }\n                } catch (_: NumberFormatException) {\n                    // ignore\n                }\n\n                if (text.isNotEmpty()) {\n                    groupTraffic.isVisible = true\n                    groupTraffic.text = text\n                    groupStatus.setPadding(0)\n                }\n            } else {\n                groupTraffic.isVisible = false\n                groupStatus.setPadding(0, 0, 0, dp2px(4))\n            }\n\n            groupUser.text = subscription?.username ?: \"\"\n\n            runOnDefaultDispatcher {\n                val size = SagerDatabase.proxyDao.countByGroup(group.id)\n                onMainDispatcher {\n                    @Suppress(\"DEPRECATION\") when (group.type) {\n                        GroupType.BASIC -> {\n                            if (size == 0L) {\n                                groupStatus.setText(R.string.group_status_empty)\n                            } else {\n                                groupStatus.text = getString(R.string.group_status_proxies, size)\n                            }\n                        }\n\n                        GroupType.SUBSCRIPTION -> {\n                            groupStatus.text = if (size == 0L) {\n                                getString(R.string.group_status_empty_subscription)\n                            } else {\n                                val date = Date(group.subscription!!.lastUpdated * 1000L)\n                                getString(\n                                    R.string.group_status_proxies_subscription,\n                                    size,\n                                    \"${date.month + 1} - ${date.date}\"\n                                )\n                            }\n\n                        }\n                    }\n                }\n\n            }\n\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/GroupSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.ViewCompat\nimport androidx.preference.*\nimport com.github.shadowsocks.plugin.Empty\nimport com.github.shadowsocks.plugin.fragment.AlertDialogFragment\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.*\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.widget.ListListener\nimport io.nekohasekai.sagernet.widget.OutboundPreference\nimport kotlinx.parcelize.Parcelize\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\n@Suppress(\"UNCHECKED_CAST\")\nclass GroupSettingsActivity(\n    @LayoutRes resId: Int = R.layout.layout_config_settings,\n) : ThemedActivity(resId),\n    OnPreferenceDataStoreChangeListener {\n\n    private lateinit var frontProxyPreference: OutboundPreference\n    private lateinit var landingProxyPreference: OutboundPreference\n\n    fun ProxyGroup.init() {\n        DataStore.groupName = name ?: \"\"\n        DataStore.groupType = type\n        DataStore.groupOrder = order\n        DataStore.groupIsSelector = isSelector\n\n        DataStore.frontProxy = frontProxy\n        DataStore.landingProxy = landingProxy\n        DataStore.frontProxyTmp = if (frontProxy >= 0) 3 else 0\n        DataStore.landingProxyTmp = if (landingProxy >= 0) 3 else 0\n\n        val subscription = subscription ?: SubscriptionBean().applyDefaultValues()\n        DataStore.subscriptionLink = subscription.link\n        DataStore.subscriptionForceResolve = subscription.forceResolve\n        DataStore.subscriptionDeduplication = subscription.deduplication\n        DataStore.subscriptionUpdateWhenConnectedOnly = subscription.updateWhenConnectedOnly\n        DataStore.subscriptionUserAgent = subscription.customUserAgent\n        DataStore.subscriptionAutoUpdate = subscription.autoUpdate\n        DataStore.subscriptionAutoUpdateDelay = subscription.autoUpdateDelay\n    }\n\n    fun ProxyGroup.serialize() {\n        name = DataStore.groupName.takeIf { it.isNotBlank() } ?: \"My group\"\n        type = DataStore.groupType\n        order = DataStore.groupOrder\n        isSelector = DataStore.groupIsSelector\n\n        frontProxy = if (DataStore.frontProxyTmp == 3) DataStore.frontProxy else -1\n        landingProxy = if (DataStore.landingProxyTmp == 3) DataStore.landingProxy else -1\n\n        val isSubscription = type == GroupType.SUBSCRIPTION\n        if (isSubscription) {\n            subscription = (subscription ?: SubscriptionBean().applyDefaultValues()).apply {\n                link = DataStore.subscriptionLink\n                forceResolve = DataStore.subscriptionForceResolve\n                deduplication = DataStore.subscriptionDeduplication\n                updateWhenConnectedOnly = DataStore.subscriptionUpdateWhenConnectedOnly\n                customUserAgent = DataStore.subscriptionUserAgent\n                autoUpdate = DataStore.subscriptionAutoUpdate\n                autoUpdateDelay = DataStore.subscriptionAutoUpdateDelay\n            }\n        }\n    }\n\n    fun needSave(): Boolean {\n        if (!DataStore.dirty) return false\n        return true\n    }\n\n    fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.group_preferences)\n\n        frontProxyPreference = findPreference(Key.GROUP_FRONT_PROXY)!!\n        frontProxyPreference.apply {\n            setEntries(R.array.front_proxy_entry)\n            setEntryValues(R.array.front_proxy_value)\n            setOnPreferenceChangeListener { _, newValue ->\n                if (newValue.toString() == \"3\") {\n                    selectProfileForAddFront.launch(\n                        Intent(this@GroupSettingsActivity, ProfileSelectActivity::class.java)\n                    )\n                    false\n                } else {\n                    true\n                }\n            }\n        }\n        landingProxyPreference = findPreference(Key.GROUP_LANDING_PROXY)!!\n        landingProxyPreference.apply {\n            setEntries(R.array.front_proxy_entry)\n            setEntryValues(R.array.front_proxy_value)\n            setOnPreferenceChangeListener { _, newValue ->\n                if (newValue.toString() == \"3\") {\n                    selectProfileForAddLanding.launch(\n                        Intent(this@GroupSettingsActivity, ProfileSelectActivity::class.java)\n                    )\n                    false\n                } else {\n                    true\n                }\n            }\n        }\n\n        val groupType = findPreference<SimpleMenuPreference>(Key.GROUP_TYPE)!!\n        val groupSubscription = findPreference<PreferenceCategory>(Key.GROUP_SUBSCRIPTION)!!\n        val subscriptionUpdate = findPreference<PreferenceCategory>(Key.SUBSCRIPTION_UPDATE)!!\n\n        fun updateGroupType(groupType: Int = DataStore.groupType) {\n            val isSubscription = groupType == GroupType.SUBSCRIPTION\n            groupSubscription.isVisible = isSubscription\n            subscriptionUpdate.isVisible = isSubscription\n        }\n        updateGroupType()\n        groupType.setOnPreferenceChangeListener { _, newValue ->\n            updateGroupType((newValue as String).toInt())\n            true\n        }\n\n        val subscriptionAutoUpdate =\n            findPreference<SwitchPreference>(Key.SUBSCRIPTION_AUTO_UPDATE)!!\n        val subscriptionAutoUpdateDelay =\n            findPreference<EditTextPreference>(Key.SUBSCRIPTION_AUTO_UPDATE_DELAY)!!\n\n        subscriptionAutoUpdateDelay.isEnabled = subscriptionAutoUpdate.isChecked\n        subscriptionAutoUpdateDelay.setOnPreferenceChangeListener { _, newValue ->\n            val delay = (newValue as String).toIntOrNull()\n            if (delay == null) {\n                false\n            } else {\n                delay >= 15\n            }\n        }\n        subscriptionAutoUpdate.setOnPreferenceChangeListener { _, newValue ->\n            subscriptionAutoUpdateDelay.isEnabled = (newValue as Boolean)\n            true\n        }\n    }\n\n    class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.unsaved_changes_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    (requireActivity() as GroupSettingsActivity).saveAndExit()\n                }\n            }\n            setNegativeButton(R.string.no) { _, _ ->\n                requireActivity().finish()\n            }\n            setNeutralButton(android.R.string.cancel, null)\n        }\n    }\n\n    @Parcelize\n    data class GroupIdArg(val groupId: Long) : Parcelable\n    class DeleteConfirmationDialogFragment : AlertDialogFragment<GroupIdArg, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.delete_group_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    GroupManager.deleteGroup(arg.groupId)\n                }\n                requireActivity().finish()\n            }\n            setNegativeButton(R.string.no, null)\n        }\n    }\n\n    companion object {\n        const val EXTRA_GROUP_ID = \"id\"\n    }\n\n    @SuppressLint(\"CommitTransaction\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.group_settings)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        if (savedInstanceState == null) {\n            val editingId = intent.getLongExtra(EXTRA_GROUP_ID, 0L)\n            DataStore.editingId = editingId\n            runOnDefaultDispatcher {\n                if (editingId == 0L) {\n                    ProxyGroup().init()\n                } else {\n                    val entity = SagerDatabase.groupDao.getById(editingId)\n                    if (entity == null) {\n                        onMainDispatcher {\n                            finish()\n                        }\n                        return@runOnDefaultDispatcher\n                    }\n                    entity.init()\n                }\n\n                onMainDispatcher {\n                    supportFragmentManager.beginTransaction()\n                        .replace(R.id.settings, MyPreferenceFragmentCompat())\n                        .commit()\n\n                    DataStore.dirty = false\n                    DataStore.profileCacheStore.registerChangeListener(this@GroupSettingsActivity)\n                }\n            }\n\n        }\n\n    }\n\n    suspend fun saveAndExit() {\n\n        val editingId = DataStore.editingId\n        if (editingId == 0L) {\n            GroupManager.createGroup(ProxyGroup().apply { serialize() })\n        } else if (needSave()) {\n            val entity = SagerDatabase.groupDao.getById(DataStore.editingId)\n            if (entity == null) {\n                finish()\n                return\n            }\n            val keepUserInfo = (entity.type == GroupType.SUBSCRIPTION &&\n                    DataStore.groupType == GroupType.SUBSCRIPTION &&\n                    entity.subscription?.link == DataStore.subscriptionLink)\n            if (!keepUserInfo) {\n                entity.subscription?.subscriptionUserinfo = \"\";\n            }\n            GroupManager.updateGroup(entity.apply { serialize() })\n        }\n\n        finish()\n\n    }\n\n    val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.profile_config_menu, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)\n\n    override fun onBackPressed() {\n        if (needSave()) {\n            UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)\n        } else super.onBackPressed()\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        if (!super.onSupportNavigateUp()) finish()\n        return true\n    }\n\n    override fun onDestroy() {\n        DataStore.profileCacheStore.unregisterChangeListener(this)\n        super.onDestroy()\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        if (key != Key.PROFILE_DIRTY) {\n            DataStore.dirty = true\n        }\n    }\n\n    class MyPreferenceFragmentCompat : PreferenceFragmentCompat() {\n\n        var activity: GroupSettingsActivity? = null\n\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            preferenceManager.preferenceDataStore = DataStore.profileCacheStore\n            try {\n                activity = (requireActivity() as GroupSettingsActivity).apply {\n                    createPreferences(savedInstanceState, rootKey)\n                }\n            } catch (e: Exception) {\n                Toast.makeText(\n                    SagerNet.application,\n                    \"Error on createPreferences, please try again.\",\n                    Toast.LENGTH_SHORT\n                ).show()\n                Logs.e(e)\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n\n            ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener)\n        }\n\n        override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {\n            R.id.action_delete -> {\n                if (DataStore.editingId == 0L) {\n                    requireActivity().finish()\n                } else {\n                    DeleteConfirmationDialogFragment().apply {\n                        arg(GroupIdArg(DataStore.editingId))\n                        key()\n                    }.show(parentFragmentManager, null)\n                }\n                true\n            }\n\n            R.id.action_apply -> {\n                runOnDefaultDispatcher {\n                    activity?.saveAndExit()\n                }\n                true\n            }\n\n            else -> false\n        }\n\n    }\n\n    object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {\n\n        override fun provideSummary(preference: EditTextPreference): CharSequence {\n            val text = preference.text\n            return if (text.isNullOrBlank()) {\n                preference.context.getString(androidx.preference.R.string.not_set)\n            } else {\n                \"\\u2022\".repeat(text.length)\n            }\n        }\n\n    }\n\n    val selectProfileForAddFront = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode == Activity.RESULT_OK) runOnDefaultDispatcher {\n            val profile = ProfileManager.getProfile(\n                it.data!!.getLongExtra(ProfileSelectActivity.EXTRA_PROFILE_ID, 0)\n            ) ?: return@runOnDefaultDispatcher\n            DataStore.frontProxy = profile.id\n            onMainDispatcher {\n                frontProxyPreference.value = \"3\"\n            }\n        }\n    }\n\n    val selectProfileForAddLanding = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult()\n    ) {\n        if (it.resultCode == Activity.RESULT_OK) runOnDefaultDispatcher {\n            val profile = ProfileManager.getProfile(\n                it.data!!.getLongExtra(ProfileSelectActivity.EXTRA_PROFILE_ID, 0)\n            ) ?: return@runOnDefaultDispatcher\n            DataStore.landingProxy = profile.id\n            onMainDispatcher {\n                landingProxyPreference.value = \"3\"\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/LogcatFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.annotation.SuppressLint\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.text.SpannableString\nimport android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE\nimport android.text.style.ForegroundColorSpan\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.doOnLayout\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.databinding.LayoutLogcatBinding\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.widget.ListListener\nimport libcore.Libcore\nimport moe.matsuri.nb4a.utils.SendLog\n\nclass LogcatFragment : ToolbarFragment(R.layout.layout_logcat),\n    Toolbar.OnMenuItemClickListener {\n\n    lateinit var binding: LayoutLogcatBinding\n\n    @SuppressLint(\"RestrictedApi\", \"WrongConstant\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        toolbar.setTitle(R.string.menu_log)\n\n        toolbar.inflateMenu(R.menu.logcat_menu)\n        toolbar.setOnMenuItemClickListener(this)\n\n        binding = LayoutLogcatBinding.bind(view)\n\n        if (Build.VERSION.SDK_INT >= 23) {\n            binding.textview.breakStrategy = 0 // simple\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener)\n\n        reloadSession()\n    }\n\n    private fun getColorForLine(line: String): ForegroundColorSpan {\n        var color = ForegroundColorSpan(Color.GRAY)\n        when {\n            line.contains(\"INFO[\") || line.contains(\" [Info]\") -> {\n                color = ForegroundColorSpan((0xFF86C166).toInt())\n            }\n\n            line.contains(\"ERROR[\") || line.contains(\" [Error]\") -> {\n                color = ForegroundColorSpan(Color.RED)\n            }\n\n            line.contains(\"WARN[\") || line.contains(\" [Warning]\") -> {\n                color = ForegroundColorSpan(Color.RED)\n            }\n        }\n        return color\n    }\n\n    private fun reloadSession() {\n        val span = SpannableString(\n            String(SendLog.getNekoLog(50 * 1024))\n        )\n        var offset = 0\n        for (line in span.lines()) {\n            val color = getColorForLine(line)\n            span.setSpan(\n                color, offset, offset + line.length, SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n            offset += line.length + 1\n        }\n        binding.textview.text = span\n        binding.textview.clearFocus()\n        // 等 textview 完成最终 layout 再滚动到底部\n        binding.textview.doOnLayout {\n            binding.scroolview.scrollTo(0, binding.textview.height)\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_clear_logcat -> {\n                runOnDefaultDispatcher {\n                    try {\n                        Libcore.nekoLogClear()\n                        Runtime.getRuntime().exec(\"/system/bin/logcat -c\")\n                    } catch (e: Exception) {\n                        onMainDispatcher {\n                            snackbar(e.readableMessage).show()\n                        }\n                        return@runOnDefaultDispatcher\n                    }\n                    onMainDispatcher {\n                        binding.textview.text = \"\"\n                    }\n                }\n\n            }\n\n            R.id.action_send_logcat -> {\n                val context = requireContext()\n                runOnDefaultDispatcher {\n                    SendLog.sendLog(context, \"NB4A\")\n                }\n            }\n\n            R.id.action_refresh -> {\n                reloadSession()\n            }\n        }\n        return true\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/MainActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.Manifest.permission.POST_NOTIFICATIONS\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.RemoteException\nimport android.view.KeyEvent\nimport android.view.MenuItem\nimport androidx.activity.addCallback\nimport androidx.annotation.IdRes\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport androidx.preference.PreferenceDataStore\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.navigation.NavigationView\nimport com.google.android.material.snackbar.Snackbar\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.GroupType\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.aidl.SpeedDisplayData\nimport io.nekohasekai.sagernet.aidl.TrafficData\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.GroupManager\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.ProxyGroup\nimport io.nekohasekai.sagernet.database.SubscriptionBean\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.databinding.LayoutMainBinding\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.fmt.KryoConverters\nimport io.nekohasekai.sagernet.fmt.PluginEntry\nimport io.nekohasekai.sagernet.group.GroupInterfaceAdapter\nimport io.nekohasekai.sagernet.group.GroupUpdater\nimport io.nekohasekai.sagernet.ktx.alert\nimport io.nekohasekai.sagernet.ktx.isPlay\nimport io.nekohasekai.sagernet.ktx.isPreview\nimport io.nekohasekai.sagernet.ktx.launchCustomTab\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.parseProxies\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport moe.matsuri.nb4a.utils.Util\n\nclass MainActivity : ThemedActivity(),\n    SagerConnection.Callback,\n    OnPreferenceDataStoreChangeListener,\n    NavigationView.OnNavigationItemSelectedListener {\n\n    lateinit var binding: LayoutMainBinding\n    lateinit var navigation: NavigationView\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = LayoutMainBinding.inflate(layoutInflater)\n        binding.fab.initProgress(binding.fabProgress)\n        if (themeResId !in intArrayOf(\n                R.style.Theme_SagerNet_Black\n            )\n        ) {\n            navigation = binding.navView\n            binding.drawerLayout.removeView(binding.navViewBlack)\n        } else {\n            navigation = binding.navViewBlack\n            binding.drawerLayout.removeView(binding.navView)\n        }\n        navigation.setNavigationItemSelectedListener(this)\n\n        if (savedInstanceState == null) {\n            displayFragmentWithId(R.id.nav_configuration)\n        }\n        onBackPressedDispatcher.addCallback {\n            if (supportFragmentManager.findFragmentById(R.id.fragment_holder) is ConfigurationFragment) {\n                moveTaskToBack(true)\n            } else {\n                displayFragmentWithId(R.id.nav_configuration)\n            }\n        }\n\n        binding.fab.setOnClickListener {\n            if (DataStore.serviceState.canStop) SagerNet.stopService() else connect.launch(\n                null\n            )\n        }\n        binding.stats.setOnClickListener { if (DataStore.serviceState.connected) binding.stats.testConnection() }\n\n        setContentView(binding.root)\n        changeState(BaseService.State.Idle)\n        connection.connect(this, this)\n        DataStore.configurationStore.registerChangeListener(this)\n        GroupManager.userInterface = GroupInterfaceAdapter(this)\n\n        if (intent?.action == Intent.ACTION_VIEW) {\n            onNewIntent(intent)\n        }\n\n        refreshNavMenu(DataStore.enableClashAPI)\n\n        // sdk 33 notification\n        if (Build.VERSION.SDK_INT >= 33) {\n            val checkPermission =\n                ContextCompat.checkSelfPermission(this@MainActivity, POST_NOTIFICATIONS)\n            if (checkPermission != PackageManager.PERMISSION_GRANTED) {\n                //动态申请\n                ActivityCompat.requestPermissions(\n                    this@MainActivity, arrayOf(POST_NOTIFICATIONS), 0\n                )\n            }\n        }\n\n        if (isPreview) {\n            MaterialAlertDialogBuilder(this)\n                .setTitle(BuildConfig.PRE_VERSION_NAME)\n                .setMessage(R.string.preview_version_hint)\n                .setPositiveButton(android.R.string.ok, null)\n                .show()\n        }\n    }\n\n    fun refreshNavMenu(clashApi: Boolean) {\n        if (::navigation.isInitialized) {\n            navigation.menu.findItem(R.id.nav_traffic)?.isVisible = clashApi\n            navigation.menu.findItem(R.id.nav_tuiguang)?.isVisible = !isPlay\n        }\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n\n        val uri = intent.data ?: return\n\n        runOnDefaultDispatcher {\n            if (uri.scheme == \"sn\" && uri.host == \"subscription\" || uri.scheme == \"clash\") {\n                importSubscription(uri)\n            } else {\n                importProfile(uri)\n            }\n        }\n    }\n\n    fun urlTest(): Int {\n        if (!DataStore.serviceState.connected || connection.service == null) {\n            error(\"not started\")\n        }\n        return connection.service!!.urlTest()\n    }\n\n    suspend fun importSubscription(uri: Uri) {\n        val group: ProxyGroup\n\n        val url = uri.getQueryParameter(\"url\")\n        if (!url.isNullOrBlank()) {\n            group = ProxyGroup(type = GroupType.SUBSCRIPTION)\n            val subscription = SubscriptionBean()\n            group.subscription = subscription\n\n            // cleartext format\n            subscription.link = url\n            group.name = uri.getQueryParameter(\"name\")\n        } else {\n            val data = uri.encodedQuery.takeIf { !it.isNullOrBlank() } ?: return\n            try {\n                group = KryoConverters.deserialize(\n                    ProxyGroup().apply { export = true }, Util.zlibDecompress(Util.b64Decode(data))\n                ).apply {\n                    export = false\n                }\n            } catch (e: Exception) {\n                onMainDispatcher {\n                    alert(e.readableMessage).show()\n                }\n                return\n            }\n        }\n\n        val name = group.name.takeIf { !it.isNullOrBlank() } ?: group.subscription?.link\n        ?: group.subscription?.token\n        if (name.isNullOrBlank()) return\n\n        group.name = group.name.takeIf { !it.isNullOrBlank() }\n            ?: (\"Subscription #\" + System.currentTimeMillis())\n\n        onMainDispatcher {\n\n            displayFragmentWithId(R.id.nav_group)\n\n            MaterialAlertDialogBuilder(this@MainActivity).setTitle(R.string.subscription_import)\n                .setMessage(getString(R.string.subscription_import_message, name))\n                .setPositiveButton(R.string.yes) { _, _ ->\n                    runOnDefaultDispatcher {\n                        finishImportSubscription(group)\n                    }\n                }\n                .setNegativeButton(android.R.string.cancel, null)\n                .show()\n\n        }\n\n    }\n\n    private suspend fun finishImportSubscription(subscription: ProxyGroup) {\n        GroupManager.createGroup(subscription)\n        GroupUpdater.startUpdate(subscription, true)\n    }\n\n    suspend fun importProfile(uri: Uri) {\n        val profile = try {\n            parseProxies(uri.toString()).getOrNull(0) ?: error(getString(R.string.no_proxies_found))\n        } catch (e: Exception) {\n            onMainDispatcher {\n                alert(e.readableMessage).show()\n            }\n            return\n        }\n\n        onMainDispatcher {\n            MaterialAlertDialogBuilder(this@MainActivity).setTitle(R.string.profile_import)\n                .setMessage(getString(R.string.profile_import_message, profile.displayName()))\n                .setPositiveButton(R.string.yes) { _, _ ->\n                    runOnDefaultDispatcher {\n                        finishImportProfile(profile)\n                    }\n                }\n                .setNegativeButton(android.R.string.cancel, null)\n                .show()\n        }\n\n    }\n\n    private suspend fun finishImportProfile(profile: AbstractBean) {\n        val targetId = DataStore.selectedGroupForImport()\n\n        ProfileManager.createProfile(targetId, profile)\n\n        onMainDispatcher {\n            displayFragmentWithId(R.id.nav_configuration)\n\n            snackbar(resources.getQuantityString(R.plurals.added, 1, 1)).show()\n        }\n    }\n\n    override fun missingPlugin(profileName: String, pluginName: String) {\n        val pluginEntity = PluginEntry.find(pluginName)\n\n        // unknown exe or neko plugin\n        if (pluginEntity == null) {\n            snackbar(getString(R.string.plugin_unknown, pluginName)).show()\n            return\n        }\n\n        // official exe\n\n        MaterialAlertDialogBuilder(this).setTitle(R.string.missing_plugin)\n            .setMessage(\n                getString(\n                    R.string.profile_requiring_plugin, profileName, pluginEntity.displayName\n                )\n            )\n            .setPositiveButton(R.string.action_download) { _, _ ->\n                showDownloadDialog(pluginEntity)\n            }\n            .setNeutralButton(android.R.string.cancel, null)\n            .setNeutralButton(R.string.action_learn_more) { _, _ ->\n                launchCustomTab(\"https://matsuridayo.github.io/nb4a-plugin/\")\n            }\n            .show()\n    }\n\n    private fun showDownloadDialog(pluginEntry: PluginEntry) {\n        var index = 0\n        var playIndex = -1\n        var fdroidIndex = -1\n\n        val items = mutableListOf<String>()\n        if (pluginEntry.downloadSource.playStore) {\n            items.add(getString(R.string.install_from_play_store))\n            playIndex = index++\n        }\n        if (pluginEntry.downloadSource.fdroid) {\n            items.add(getString(R.string.install_from_fdroid))\n            fdroidIndex = index++\n        }\n\n        items.add(getString(R.string.download))\n        val downloadIndex = index\n\n        MaterialAlertDialogBuilder(this).setTitle(pluginEntry.name)\n            .setItems(items.toTypedArray()) { _, which ->\n                when (which) {\n                    playIndex -> launchCustomTab(\"https://play.google.com/store/apps/details?id=${pluginEntry.packageName}\")\n                    fdroidIndex -> launchCustomTab(\"https://f-droid.org/packages/${pluginEntry.packageName}/\")\n                    downloadIndex -> launchCustomTab(pluginEntry.downloadSource.downloadLink)\n                }\n            }\n            .show()\n    }\n\n    override fun onNavigationItemSelected(item: MenuItem): Boolean {\n        if (item.isChecked) binding.drawerLayout.closeDrawers() else {\n            return displayFragmentWithId(item.itemId)\n        }\n        return true\n    }\n\n\n    @SuppressLint(\"CommitTransaction\")\n    fun displayFragment(fragment: ToolbarFragment) {\n        if (fragment is ConfigurationFragment) {\n            binding.stats.allowShow = true\n            binding.fab.show()\n        } else if (!DataStore.showBottomBar) {\n            binding.stats.allowShow = false\n            binding.stats.performHide()\n            binding.fab.hide()\n        }\n        supportFragmentManager.beginTransaction()\n            .replace(R.id.fragment_holder, fragment)\n            .commitAllowingStateLoss()\n        binding.drawerLayout.closeDrawers()\n    }\n\n    fun displayFragmentWithId(@IdRes id: Int): Boolean {\n        when (id) {\n            R.id.nav_configuration -> {\n                displayFragment(ConfigurationFragment())\n            }\n\n            R.id.nav_group -> displayFragment(GroupFragment())\n            R.id.nav_route -> displayFragment(RouteFragment())\n            R.id.nav_settings -> displayFragment(SettingsFragment())\n            R.id.nav_traffic -> displayFragment(WebviewFragment())\n            R.id.nav_tools -> displayFragment(ToolsFragment())\n            R.id.nav_logcat -> displayFragment(LogcatFragment())\n            R.id.nav_faq -> {\n                launchCustomTab(\"https://matsuridayo.github.io/\")\n                return false\n            }\n\n            R.id.nav_about -> displayFragment(AboutFragment())\n            R.id.nav_tuiguang -> {\n                launchCustomTab(\"https://neko-box.pages.dev/喵\")\n                return false\n            }\n\n            else -> return false\n        }\n        navigation.menu.findItem(id).isChecked = true\n        return true\n    }\n\n    private fun changeState(\n        state: BaseService.State,\n        msg: String? = null,\n        animate: Boolean = false,\n    ) {\n        DataStore.serviceState = state\n\n        binding.fab.changeState(state, DataStore.serviceState, animate)\n        binding.stats.changeState(state)\n        if (msg != null) snackbar(getString(R.string.vpn_error, msg)).show()\n    }\n\n    override fun snackbarInternal(text: CharSequence): Snackbar {\n        return Snackbar.make(binding.coordinator, text, Snackbar.LENGTH_LONG).apply {\n            if (binding.fab.isShown) {\n                anchorView = binding.fab\n            }\n            // TODO\n        }\n    }\n\n    override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {\n        changeState(state, msg, true)\n    }\n\n    val connection = SagerConnection(SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND, true)\n    override fun onServiceConnected(service: ISagerNetService) = changeState(\n        try {\n            BaseService.State.values()[service.state]\n        } catch (_: RemoteException) {\n            BaseService.State.Idle\n        }\n    )\n\n    override fun onServiceDisconnected() = changeState(BaseService.State.Idle)\n    override fun onBinderDied() {\n        connection.disconnect(this)\n        connection.connect(this, this)\n    }\n\n    private val connect = registerForActivityResult(VpnRequestActivity.StartService()) {\n        if (it) snackbar(R.string.vpn_permission_denied).show()\n    }\n\n    // may NOT called when app is in background\n    // ONLY do UI update here, write DB in bg process\n    override fun cbSpeedUpdate(stats: SpeedDisplayData) {\n        binding.stats.updateSpeed(stats.txRateProxy, stats.rxRateProxy)\n    }\n\n    override fun cbTrafficUpdate(data: TrafficData) {\n        runOnDefaultDispatcher {\n            ProfileManager.postUpdate(data)\n        }\n    }\n\n    override fun cbSelectorUpdate(id: Long) {\n        val old = DataStore.selectedProxy\n        DataStore.selectedProxy = id\n        DataStore.currentProfile = id\n        runOnDefaultDispatcher {\n            ProfileManager.postUpdate(old, true)\n            ProfileManager.postUpdate(id, true)\n        }\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        when (key) {\n            Key.SERVICE_MODE -> onBinderDied()\n            Key.PROXY_APPS, Key.BYPASS_MODE, Key.INDIVIDUAL -> {\n                if (DataStore.serviceState.canStop) {\n                    snackbar(getString(R.string.need_reload)).setAction(R.string.apply) {\n                        SagerNet.reloadService()\n                    }.show()\n                }\n            }\n        }\n    }\n\n    override fun onStart() {\n        connection.updateConnectionId(SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_FOREGROUND)\n        super.onStart()\n    }\n\n    override fun onStop() {\n        connection.updateConnectionId(SagerConnection.CONNECTION_ID_MAIN_ACTIVITY_BACKGROUND)\n        super.onStop()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        GroupManager.userInterface = null\n        DataStore.configurationStore.unregisterChangeListener(this)\n        connection.disconnect(this)\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {\n        when (keyCode) {\n            KeyEvent.KEYCODE_DPAD_LEFT -> {\n                if (super.onKeyDown(keyCode, event)) return true\n                binding.drawerLayout.open()\n                navigation.requestFocus()\n            }\n\n            KeyEvent.KEYCODE_DPAD_RIGHT -> {\n                if (binding.drawerLayout.isOpen) {\n                    binding.drawerLayout.close()\n                    return true\n                }\n            }\n        }\n\n        if (super.onKeyDown(keyCode, event)) return true\n        if (binding.drawerLayout.isOpen) return false\n\n        val fragment =\n            supportFragmentManager.findFragmentById(R.id.fragment_holder) as? ToolbarFragment\n        return fragment != null && fragment.onKeyDown(keyCode, event)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/NamedFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport androidx.fragment.app.Fragment\n\nabstract class NamedFragment : Fragment {\n\n    constructor() : super()\n    constructor(contentLayoutId: Int) : super(contentLayoutId)\n\n    private val name by lazy { name0() }\n    fun name() = name\n    protected abstract fun name0(): String\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/NetworkFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.databinding.LayoutNetworkBinding\nimport io.nekohasekai.sagernet.ktx.app\n\nclass NetworkFragment : NamedFragment(R.layout.layout_network) {\n\n    override fun name0() = app.getString(R.string.tools_network)\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        val binding = LayoutNetworkBinding.bind(view)\n        binding.stunTest.setOnClickListener {\n            startActivity(Intent(requireContext(), StunActivity::class.java))\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ProfileSelectActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.ProxyEntity\n\nclass ProfileSelectActivity : ThemedActivity(R.layout.layout_empty),\n    ConfigurationFragment.SelectCallback {\n\n    companion object {\n        const val EXTRA_SELECTED = \"selected\"\n        const val EXTRA_PROFILE_ID = \"id\"\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        val selected = intent.getParcelableExtra<ProxyEntity>(EXTRA_SELECTED)\n\n        supportFragmentManager.beginTransaction()\n            .replace(\n                R.id.fragment_holder,\n                ConfigurationFragment(true, selected, R.string.select_profile)\n            )\n            .commitAllowingStateLoss()\n    }\n\n    override fun returnProfile(profileId: Long) {\n        setResult(RESULT_OK, Intent().apply {\n            putExtra(EXTRA_PROFILE_ID, profileId)\n        })\n        finish()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/QuickDisableShortcut.kt",
    "content": "/*******************************************************************************\n *                                                                             *\n *  Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com>                          *\n *  Copyright (C) 2017 by Mygod Studio <[Email1]>  *\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 *                                                                             *\n *******************************************************************************/\n\npackage io.nekohasekai.sagernet.ui\n\nimport android.app.Activity\nimport android.content.pm.ShortcutManager\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.core.content.getSystemService\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\n\nclass QuickDisableShortcut : Activity(), SagerConnection.Callback {\n    private val connection = SagerConnection(SagerConnection.CONNECTION_ID_SHORTCUT)\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        connection.connect(this, this)\n        if (Build.VERSION.SDK_INT >= 25) {\n            getSystemService<ShortcutManager>()!!.reportShortcutUsed(\"disable\")\n        }\n    }\n\n    override fun onServiceConnected(service: ISagerNetService) {\n        val state = BaseService.State.values()[service.state]\n        if (state.canStop) {\n            SagerNet.stopService()\n        }\n        finish()\n    }\n\n    override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {}\n\n    override fun onDestroy() {\n        connection.disconnect(this)\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/QuickEnableShortcut.kt",
    "content": "/*******************************************************************************\n *                                                                             *\n *  Copyright (C) 2017 by Max Lv <[Email0]>                          *\n *  Copyright (C) 2017 by Mygod Studio <[Email1]>  *\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 *                                                                             *\n *******************************************************************************/\n\npackage io.nekohasekai.sagernet.ui\n\nimport android.app.Activity\nimport android.content.pm.ShortcutManager\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.core.content.getSystemService\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.aidl.ISagerNetService\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.bg.SagerConnection\n\nclass QuickEnableShortcut : Activity(), SagerConnection.Callback {\n    private val connection = SagerConnection(SagerConnection.CONNECTION_ID_SHORTCUT)\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        connection.connect(this, this)\n        if (Build.VERSION.SDK_INT >= 25) {\n            getSystemService<ShortcutManager>()!!.reportShortcutUsed(\"enable\")\n        }\n    }\n\n    override fun onServiceConnected(service: ISagerNetService) {\n        val state = BaseService.State.values()[service.state]\n        if (state == BaseService.State.Stopped) {\n            SagerNet.startService()\n        }\n        finish()\n    }\n\n    override fun stateChanged(state: BaseService.State, profileName: String?, msg: String?) {}\n\n    override fun onDestroy() {\n        connection.disconnect(this)\n        super.onDestroy()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/RouteFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.RuleEntity\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.databinding.LayoutEmptyRouteBinding\nimport io.nekohasekai.sagernet.databinding.LayoutRouteItemBinding\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.widget.ListListener\nimport io.nekohasekai.sagernet.widget.UndoSnackbarManager\n\nclass RouteFragment : ToolbarFragment(R.layout.layout_route), Toolbar.OnMenuItemClickListener {\n\n    lateinit var activity: MainActivity\n    lateinit var ruleListView: RecyclerView\n    lateinit var ruleAdapter: RuleAdapter\n    lateinit var undoManager: UndoSnackbarManager<RuleEntity>\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        activity = requireActivity() as MainActivity\n\n        ViewCompat.setOnApplyWindowInsetsListener(view, ListListener)\n        toolbar.setTitle(R.string.menu_route)\n        toolbar.inflateMenu(R.menu.add_route_menu)\n        toolbar.setOnMenuItemClickListener(this)\n\n        ruleListView = view.findViewById(R.id.route_list)\n        ruleListView.layoutManager = FixedLinearLayoutManager(ruleListView)\n        ruleAdapter = RuleAdapter()\n        ProfileManager.addListener(ruleAdapter)\n        ruleListView.adapter = ruleAdapter\n        undoManager = UndoSnackbarManager(activity, ruleAdapter)\n\n        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START) {\n\n            override fun getSwipeDirs(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) = if (viewHolder is RuleAdapter.DocumentHolder) {\n                0\n            } else {\n                super.getSwipeDirs(recyclerView, viewHolder)\n            }\n\n            override fun getDragDirs(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) = if (viewHolder is RuleAdapter.DocumentHolder) {\n                0\n            } else {\n                super.getDragDirs(recyclerView, viewHolder)\n            }\n\n            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n                val index = viewHolder.bindingAdapterPosition\n                ruleAdapter.remove(index)\n                undoManager.remove(index to (viewHolder as RuleAdapter.RuleHolder).rule)\n            }\n\n            override fun onMove(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder,\n            ): Boolean {\n                return if (target is RuleAdapter.DocumentHolder) {\n                    false\n                } else {\n                    ruleAdapter.move(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition)\n                    true\n                }\n            }\n\n            override fun clearView(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) {\n                super.clearView(recyclerView, viewHolder)\n                ruleAdapter.commitMove()\n            }\n        }).attachToRecyclerView(ruleListView)\n    }\n\n    override fun onDestroy() {\n        if (::ruleAdapter.isInitialized) {\n            ProfileManager.removeListener(ruleAdapter)\n        }\n        super.onDestroy()\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_new_route -> {\n                startActivity(Intent(context, RouteSettingsActivity::class.java))\n            }\n            R.id.action_reset_route -> {\n                MaterialAlertDialogBuilder(activity).setTitle(R.string.confirm)\n                    .setMessage(R.string.clear_profiles_message)\n                    .setPositiveButton(R.string.yes) { _, _ ->\n                        runOnDefaultDispatcher {\n                            SagerDatabase.rulesDao.reset()\n                            DataStore.rulesFirstCreate = false\n                            ruleAdapter.reload()\n                        }\n                    }\n                    .setNegativeButton(R.string.no, null)\n                    .show()\n            }\n            R.id.action_manage_assets -> {\n                startActivity(Intent(requireContext(), AssetsActivity::class.java))\n            }\n        }\n        return true\n    }\n\n    inner class RuleAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ProfileManager.RuleListener, UndoSnackbarManager.Interface<RuleEntity> {\n\n        val ruleList = ArrayList<RuleEntity>()\n        suspend fun reload() {\n            val rules = ProfileManager.getRules()\n            ruleListView.post {\n                ruleList.clear()\n                ruleList.addAll(rules)\n                ruleAdapter.notifyDataSetChanged()\n            }\n        }\n\n        init {\n            runOnDefaultDispatcher {\n                reload()\n            }\n        }\n\n        override fun onCreateViewHolder(\n            parent: ViewGroup,\n            viewType: Int,\n        ): RecyclerView.ViewHolder {\n            return if (viewType == 0) {\n                DocumentHolder(LayoutEmptyRouteBinding.inflate(layoutInflater, parent, false))\n            } else {\n                RuleHolder(LayoutRouteItemBinding.inflate(layoutInflater, parent, false))\n            }\n        }\n\n        override fun getItemViewType(position: Int): Int {\n            if (position == 0) return 0\n            return 1\n        }\n\n        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n            if (holder is DocumentHolder) {\n                holder.bind()\n            } else if (holder is RuleHolder) {\n                holder.bind(ruleList[position - 1])\n            }\n        }\n\n        override fun getItemCount(): Int {\n            return ruleList.size + 1\n        }\n\n        override fun getItemId(position: Int): Long {\n            if (position == 0) return 0L\n            return ruleList[position - 1].id\n        }\n\n        private val updated = HashSet<RuleEntity>()\n        fun move(from: Int, to: Int) {\n            val first = ruleList[from - 1]\n            var previousOrder = first.userOrder\n            val (step, range) = if (from < to) Pair(1, from - 1 until to - 1) else Pair(-1, to downTo from - 1)\n            for (i in range) {\n                val next = ruleList[i + step]\n                val order = next.userOrder\n                next.userOrder = previousOrder\n                previousOrder = order\n                ruleList[i] = next\n                updated.add(next)\n            }\n            first.userOrder = previousOrder\n            ruleList[to - 1] = first\n            updated.add(first)\n            notifyItemMoved(from, to)\n        }\n\n        fun commitMove() = runOnDefaultDispatcher {\n            if (updated.isNotEmpty()) {\n                SagerDatabase.rulesDao.updateRules(updated.toList())\n                updated.clear()\n                needReload()\n            }\n        }\n\n        fun remove(index: Int) {\n            ruleList.removeAt(index - 1)\n            notifyItemRemoved(index)\n        }\n\n        override fun undo(actions: List<Pair<Int, RuleEntity>>) {\n            for ((index, item) in actions) {\n                ruleList.add(index - 1, item)\n                notifyItemInserted(index)\n            }\n        }\n\n        override fun commit(actions: List<Pair<Int, RuleEntity>>) {\n            val rules = actions.map { it.second }\n            runOnDefaultDispatcher {\n                ProfileManager.deleteRules(rules)\n            }\n        }\n\n        override suspend fun onAdd(rule: RuleEntity) {\n            ruleListView.post {\n                ruleList.add(rule)\n                ruleAdapter.notifyItemInserted(ruleList.size)\n                needReload()\n            }\n        }\n\n        override suspend fun onUpdated(rule: RuleEntity) {\n            val index = ruleList.indexOfFirst { it.id == rule.id }\n            if (index == -1) return\n            ruleListView.post {\n                ruleList[index] = rule\n                ruleAdapter.notifyItemChanged(index + 1)\n                needReload()\n            }\n        }\n\n        override suspend fun onRemoved(ruleId: Long) {\n            val index = ruleList.indexOfFirst { it.id == ruleId }\n            if (index == -1) {\n                onMainDispatcher {\n                    needReload()\n                }\n            } else ruleListView.post {\n                ruleList.removeAt(index)\n                ruleAdapter.notifyItemRemoved(index + 1)\n                needReload()\n            }\n        }\n\n        override suspend fun onCleared() {\n            ruleListView.post {\n                ruleList.clear()\n                ruleAdapter.notifyDataSetChanged()\n                needReload()\n            }\n        }\n\n        inner class DocumentHolder(binding: LayoutEmptyRouteBinding) : RecyclerView.ViewHolder(binding.root) {\n            fun bind() {\n                itemView.setOnClickListener {\n                    it.context.launchCustomTab(\"https://matsuridayo.github.io/nb4a-route/\")\n                }\n            }\n        }\n\n        inner class RuleHolder(binding: LayoutRouteItemBinding) : RecyclerView.ViewHolder(binding.root) {\n\n            lateinit var rule: RuleEntity\n            val profileName = binding.profileName\n            val profileType = binding.profileType\n            val routeOutbound = binding.routeOutbound\n            val editButton = binding.edit\n            val shareLayout = binding.share\n            val enableSwitch = binding.enable\n\n            fun bind(ruleEntity: RuleEntity) {\n                rule = ruleEntity\n                profileName.text = rule.displayName()\n                profileType.text = rule.mkSummary()\n                routeOutbound.text = rule.displayOutbound()\n                itemView.setOnClickListener {\n                    enableSwitch.performClick()\n                }\n                enableSwitch.isChecked = rule.enabled\n                enableSwitch.setOnCheckedChangeListener { _, isChecked ->\n                    runOnDefaultDispatcher {\n                        rule.enabled = isChecked\n                        SagerDatabase.rulesDao.updateRule(rule)\n                        onMainDispatcher {\n                            needReload()\n                        }\n                    }\n                }\n                editButton.setOnClickListener {\n                    startActivity(Intent(it.context, RouteSettingsActivity::class.java).apply {\n                        putExtra(RouteSettingsActivity.EXTRA_ROUTE_ID, rule.id)\n                    })\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/RouteSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.app.Activity\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.Toast\nimport androidx.activity.result.component1\nimport androidx.activity.result.component2\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.ViewCompat\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceDataStore\nimport androidx.preference.PreferenceFragmentCompat\nimport com.github.shadowsocks.plugin.Empty\nimport com.github.shadowsocks.plugin.fragment.AlertDialogFragment\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.RuleEntity\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport io.nekohasekai.sagernet.widget.AppListPreference\nimport io.nekohasekai.sagernet.widget.ListListener\nimport io.nekohasekai.sagernet.widget.OutboundPreference\nimport kotlinx.parcelize.Parcelize\nimport moe.matsuri.nb4a.ui.EditConfigPreference\n\n@Suppress(\"UNCHECKED_CAST\")\nclass RouteSettingsActivity(\n    @LayoutRes resId: Int = R.layout.layout_settings_activity,\n) : ThemedActivity(resId),\n    OnPreferenceDataStoreChangeListener {\n\n    fun init(packageName: String?) {\n        RuleEntity().apply {\n            if (!packageName.isNullOrBlank()) {\n                packages = setOf(packageName)\n                name = app.getString(R.string.route_for, PackageCache.loadLabel(packageName))\n            }\n        }.init()\n    }\n\n    fun RuleEntity.init() {\n        DataStore.routeName = name\n        DataStore.serverConfig = config\n        DataStore.routeDomain = domains\n        DataStore.routeIP = ip\n        DataStore.routePort = port\n        DataStore.routeSourcePort = sourcePort\n        DataStore.routeNetwork = network\n        DataStore.routeSource = source\n        DataStore.routeProtocol = protocol\n        DataStore.routeOutboundRule = outbound\n        DataStore.routeOutbound = when (outbound) {\n            0L -> 0\n            -1L -> 1\n            -2L -> 2\n            else -> 3\n        }\n        DataStore.routePackages = packages.joinToString(\"\\n\")\n    }\n\n    fun RuleEntity.serialize() {\n        name = DataStore.routeName\n        config = DataStore.serverConfig\n        domains = DataStore.routeDomain\n        ip = DataStore.routeIP\n        port = DataStore.routePort\n        sourcePort = DataStore.routeSourcePort\n        network = DataStore.routeNetwork\n        source = DataStore.routeSource\n        protocol = DataStore.routeProtocol\n        outbound = when (DataStore.routeOutbound) {\n            0 -> 0L\n            1 -> -1L\n            2 -> -2L\n            else -> DataStore.routeOutboundRule\n        }\n        packages = DataStore.routePackages.split(\"\\n\").filter { it.isNotBlank() }.toSet()\n\n        if (DataStore.editingId == 0L) {\n            enabled = true\n        }\n    }\n\n    private lateinit var editConfigPreference: EditConfigPreference\n\n    fun needSave(): Boolean {\n        return DataStore.dirty\n    }\n\n    fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.route_preferences)\n\n        editConfigPreference = findPreference(Key.SERVER_CONFIG)!!\n    }\n\n    override fun onResume() {\n        super.onResume()\n\n        if (::editConfigPreference.isInitialized) {\n            editConfigPreference.notifyChanged()\n        }\n    }\n\n    val selectProfileForAdd = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult()\n    ) { (resultCode, data) ->\n        if (resultCode == Activity.RESULT_OK) runOnDefaultDispatcher {\n            val profile = ProfileManager.getProfile(\n                data!!.getLongExtra(\n                    ProfileSelectActivity.EXTRA_PROFILE_ID, 0\n                )\n            ) ?: return@runOnDefaultDispatcher\n            DataStore.routeOutboundRule = profile.id\n            onMainDispatcher {\n                outbound.value = \"3\"\n            }\n        }\n    }\n\n    val selectAppList = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult()\n    ) { (_, _) ->\n        apps.postUpdate()\n    }\n\n    lateinit var outbound: OutboundPreference\n    lateinit var apps: AppListPreference\n\n    fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) {\n        outbound = findPreference(Key.ROUTE_OUTBOUND)!!\n        apps = findPreference(Key.ROUTE_PACKAGES)!!\n\n        outbound.setOnPreferenceChangeListener { _, newValue ->\n            if (newValue.toString() == \"3\") {\n                selectProfileForAdd.launch(\n                    Intent(\n                        this@RouteSettingsActivity, ProfileSelectActivity::class.java\n                    )\n                )\n                false\n            } else {\n                true\n            }\n        }\n\n        apps.setOnPreferenceClickListener {\n            selectAppList.launch(\n                Intent(\n                    this@RouteSettingsActivity, AppListActivity::class.java\n                )\n            )\n            true\n        }\n    }\n\n    fun displayPreferenceDialog(preference: Preference): Boolean {\n        return false\n    }\n\n    class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.unsaved_changes_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    (requireActivity() as RouteSettingsActivity).saveAndExit()\n                }\n            }\n            setNegativeButton(R.string.no) { _, _ ->\n                requireActivity().finish()\n            }\n            setNeutralButton(android.R.string.cancel, null)\n        }\n    }\n\n    @Parcelize\n    data class ProfileIdArg(val ruleId: Long) : Parcelable\n    class DeleteConfirmationDialogFragment : AlertDialogFragment<ProfileIdArg, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.delete_route_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    ProfileManager.deleteRule(arg.ruleId)\n                }\n                requireActivity().finish()\n            }\n            setNegativeButton(R.string.no, null)\n        }\n    }\n\n    companion object {\n        const val EXTRA_ROUTE_ID = \"id\"\n        const val EXTRA_PACKAGE_NAME = \"pkg\"\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.cag_route)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        if (savedInstanceState == null) {\n            val editingId = intent.getLongExtra(EXTRA_ROUTE_ID, 0L)\n            DataStore.editingId = editingId\n            runOnDefaultDispatcher {\n                if (editingId == 0L) {\n                    init(intent.getStringExtra(EXTRA_PACKAGE_NAME))\n                } else {\n                    val ruleEntity = SagerDatabase.rulesDao.getById(editingId)\n                    if (ruleEntity == null) {\n                        onMainDispatcher {\n                            finish()\n                        }\n                        return@runOnDefaultDispatcher\n                    }\n                    ruleEntity.init()\n                }\n\n                onMainDispatcher {\n                    supportFragmentManager.beginTransaction()\n                        .replace(R.id.settings, MyPreferenceFragmentCompat())\n                        .commit()\n\n                    DataStore.dirty = false\n                    DataStore.profileCacheStore.registerChangeListener(this@RouteSettingsActivity)\n                }\n            }\n\n\n        }\n\n    }\n\n    suspend fun saveAndExit() {\n\n        if (!needSave()) {\n            onMainDispatcher {\n                MaterialAlertDialogBuilder(this@RouteSettingsActivity).setTitle(R.string.empty_route)\n                    .setMessage(R.string.empty_route_notice)\n                    .setPositiveButton(android.R.string.ok, null)\n                    .show()\n            }\n            return\n        }\n\n        val editingId = DataStore.editingId\n        if (editingId == 0L) {\n            if (intent.hasExtra(EXTRA_PACKAGE_NAME)) {\n                setResult(RESULT_OK, Intent())\n            }\n\n            ProfileManager.createRule(RuleEntity().apply { serialize() })\n        } else {\n            val entity = SagerDatabase.rulesDao.getById(DataStore.editingId)\n            if (entity == null) {\n                finish()\n                return\n            }\n            ProfileManager.updateRule(entity.apply { serialize() })\n        }\n        finish()\n\n    }\n\n    val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.profile_config_menu, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)\n\n    override fun onBackPressed() {\n        if (needSave()) {\n            UnsavedChangesDialogFragment().apply { key() }.show(supportFragmentManager, null)\n        } else super.onBackPressed()\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        if (!super.onSupportNavigateUp()) finish()\n        return true\n    }\n\n    override fun onDestroy() {\n        DataStore.profileCacheStore.unregisterChangeListener(this)\n        super.onDestroy()\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        if (key != Key.PROFILE_DIRTY) {\n            DataStore.dirty = true\n        }\n    }\n\n    class MyPreferenceFragmentCompat : PreferenceFragmentCompat() {\n\n        var activity: RouteSettingsActivity? = null\n\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            preferenceManager.preferenceDataStore = DataStore.profileCacheStore\n            try {\n                activity = (requireActivity() as RouteSettingsActivity).apply {\n                    createPreferences(savedInstanceState, rootKey)\n                }\n            } catch (e: Exception) {\n                Toast.makeText(\n                    SagerNet.application,\n                    \"Error on createPreferences, please try again.\",\n                    Toast.LENGTH_SHORT\n                ).show()\n                Logs.e(e)\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n\n            ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener)\n\n            activity?.apply {\n                viewCreated(view, savedInstanceState)\n            }\n        }\n\n        override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {\n            R.id.action_delete -> {\n                if (DataStore.editingId == 0L) {\n                    requireActivity().finish()\n                } else {\n                    DeleteConfirmationDialogFragment().apply {\n                        arg(ProfileIdArg(DataStore.editingId))\n                        key()\n                    }.show(parentFragmentManager, null)\n                }\n                true\n            }\n\n            R.id.action_apply -> {\n                runOnDefaultDispatcher {\n                    activity?.saveAndExit()\n                }\n                true\n            }\n\n            else -> false\n        }\n\n        override fun onDisplayPreferenceDialog(preference: Preference) {\n            activity?.apply {\n                if (displayPreferenceDialog(preference)) return\n            }\n            super.onDisplayPreferenceDialog(preference)\n        }\n\n    }\n\n    object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {\n\n        override fun provideSummary(preference: EditTextPreference): CharSequence {\n            val text = preference.text\n            return if (text.isNullOrBlank()) {\n                preference.context.getString(androidx.preference.R.string.not_set)\n            } else {\n                \"\\u2022\".repeat(text.length)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ScannerActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.Manifest\nimport android.content.Intent\nimport android.content.pm.ShortcutManager\nimport android.graphics.ImageDecoder\nimport android.os.Build\nimport android.os.Bundle\nimport android.provider.MediaStore\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.content.getSystemService\nimport androidx.core.net.toUri\nimport com.google.zxing.Result\nimport com.king.zxing.CameraScan\nimport com.king.zxing.DefaultCameraScan\nimport com.king.zxing.analyze.QRCodeAnalyzer\nimport com.king.zxing.util.CodeUtils\nimport com.king.zxing.util.LogUtils\nimport com.king.zxing.util.PermissionUtils\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.databinding.LayoutScannerBinding\nimport io.nekohasekai.sagernet.group.RawUpdater\nimport io.nekohasekai.sagernet.ktx.*\nimport java.util.concurrent.atomic.AtomicBoolean\nimport java.util.concurrent.atomic.AtomicInteger\n\n\nclass ScannerActivity : ThemedActivity(),\n    CameraScan.OnScanResultCallback {\n\n    lateinit var binding: LayoutScannerBinding\n    lateinit var cameraScan: CameraScan\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        if (Build.VERSION.SDK_INT >= 25) getSystemService<ShortcutManager>()!!.reportShortcutUsed(\"scan\")\n        binding = LayoutScannerBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        // 二维码库\n        initCameraScan()\n        startCamera()\n        binding.ivFlashlight.setOnClickListener { toggleTorchState() }\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.scanner_menu, menu)\n        return true\n    }\n\n    val importCodeFile = registerForActivityResult(ActivityResultContracts.GetMultipleContents()) {\n        runOnDefaultDispatcher {\n            try {\n                it.forEachTry { uri ->\n                    val bitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n                        ImageDecoder.decodeBitmap(\n                            ImageDecoder.createSource(\n                                contentResolver, uri\n                            )\n                        ) { decoder, _, _ ->\n                            decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE\n                            decoder.isMutableRequired = true\n                        }\n                    } else {\n                        @Suppress(\"DEPRECATION\") MediaStore.Images.Media.getBitmap(\n                            contentResolver, uri\n                        )\n                    }\n                    val result = CodeUtils.parseCodeResult(bitmap)\n                    onMainDispatcher {\n                        onScanResultCallback(result, true)\n                    }\n                }\n                finish()\n            } catch (e: Exception) {\n                Logs.w(e)\n                onMainDispatcher {\n                    Toast.makeText(app, e.readableMessage, Toast.LENGTH_LONG).show()\n                }\n            }\n        }\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        return if (item.itemId == R.id.action_import_file) {\n            startFilesForResult(importCodeFile, \"image/*\")\n            true\n        } else {\n            super.onOptionsItemSelected(item)\n        }\n    }\n\n    var finished = AtomicBoolean(false)\n    var importedN = AtomicInteger(0)\n\n    /**\n     * 接收扫码结果回调\n     * @param result 扫码结果\n     * @return 返回true表示拦截，将不自动执行后续逻辑，为false表示不拦截，默认不拦截\n     */\n    override fun onScanResultCallback(result: Result?): Boolean {\n        return onScanResultCallback(result, false)\n    }\n\n    fun onScanResultCallback(result: Result?, multi: Boolean): Boolean {\n        if (!multi && finished.getAndSet(true)) return true\n        if (!multi) finish()\n        runOnDefaultDispatcher {\n            try {\n                val text = result?.text ?: throw Exception(\"QR code not found\")\n                val results = RawUpdater.parseRaw(text)\n                if (!results.isNullOrEmpty()) {\n                    val currentGroupId = DataStore.selectedGroupForImport()\n                    if (DataStore.selectedGroup != currentGroupId) {\n                        DataStore.selectedGroup = currentGroupId\n                    }\n\n                    for (profile in results) {\n                        ProfileManager.createProfile(currentGroupId, profile)\n                        importedN.addAndGet(1)\n                    }\n                } else {\n                    onMainDispatcher {\n                        Toast.makeText(app, R.string.action_import_err, Toast.LENGTH_SHORT).show()\n                    }\n                }\n            } catch (e: SubscriptionFoundException) {\n                startActivity(Intent(this@ScannerActivity, MainActivity::class.java).apply {\n                    action = Intent.ACTION_VIEW\n                    data = e.link.toUri()\n                })\n            } catch (e: Throwable) {\n                Logs.w(e)\n                onMainDispatcher {\n                    var text = getString(R.string.action_import_err)\n                    text += \"\\n\" + e.readableMessage\n                    Toast.makeText(app, text, Toast.LENGTH_SHORT).show()\n                }\n            }\n        }\n        return true\n    }\n\n    /**\n     * 初始化CameraScan\n     */\n    fun initCameraScan() {\n        cameraScan = DefaultCameraScan(this, binding.previewView)\n        cameraScan.setAnalyzer(QRCodeAnalyzer())\n        cameraScan.setOnScanResultCallback(this)\n        cameraScan.setNeedAutoZoom(true)\n    }\n\n    /**\n     * 启动相机预览\n     */\n    fun startCamera() {\n        if (PermissionUtils.checkPermission(this, Manifest.permission.CAMERA)) {\n            cameraScan.startCamera()\n        } else {\n            LogUtils.d(\"checkPermissionResult != PERMISSION_GRANTED\")\n            PermissionUtils.requestPermission(\n                this, Manifest.permission.CAMERA, CAMERA_PERMISSION_REQUEST_CODE\n            )\n        }\n    }\n\n    /**\n     * 释放相机\n     */\n    private fun releaseCamera() {\n        cameraScan.release()\n    }\n\n    /**\n     * 切换闪光灯状态（开启/关闭）\n     */\n    protected fun toggleTorchState() {\n        val isTorch = cameraScan.isTorchEnabled\n        cameraScan.enableTorch(!isTorch)\n        binding.ivFlashlight.isSelected = !isTorch\n    }\n\n    val CAMERA_PERMISSION_REQUEST_CODE = 0X86\n\n    override fun onRequestPermissionsResult(\n        requestCode: Int, permissions: Array<String>, grantResults: IntArray\n    ) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n        if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {\n            requestCameraPermissionResult(permissions, grantResults)\n        }\n    }\n\n    /**\n     * 请求Camera权限回调结果\n     * @param permissions\n     * @param grantResults\n     */\n    fun requestCameraPermissionResult(permissions: Array<String>, grantResults: IntArray) {\n        if (PermissionUtils.requestPermissionsResult(\n                Manifest.permission.CAMERA, permissions, grantResults\n            )\n        ) {\n            startCamera()\n        } else {\n            finish()\n        }\n    }\n\n    override fun onDestroy() {\n        releaseCamera()\n        super.onDestroy()\n        if (importedN.get() > 0) {\n            var text = getString(R.string.action_import_msg)\n            text += \"\\n\" + importedN.get() + \" profile(s)\"\n            Toast.makeText(app, text, Toast.LENGTH_LONG).show()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/SettingsFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.ViewCompat\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.widget.ListListener\n\nclass SettingsFragment : ToolbarFragment(R.layout.layout_config_settings) {\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        ViewCompat.setOnApplyWindowInsetsListener(view, ListListener)\n        toolbar.setTitle(R.string.settings)\n\n        parentFragmentManager.beginTransaction()\n            .replace(R.id.settings, SettingsPreferenceFragment())\n            .commitAllowingStateLoss()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.inputmethod.EditorInfo\nimport android.widget.EditText\nimport androidx.core.app.ActivityCompat\nimport androidx.preference.*\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.utils.Theme\nimport moe.matsuri.nb4a.ui.*\n\nclass SettingsPreferenceFragment : PreferenceFragmentCompat() {\n\n    private lateinit var isProxyApps: SwitchPreference\n\n    private lateinit var globalCustomConfig: EditConfigPreference\n\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        listView.layoutManager = FixedLinearLayoutManager(listView)\n    }\n\n    private val reloadListener = Preference.OnPreferenceChangeListener { _, _ ->\n        needReload()\n        true\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        preferenceManager.preferenceDataStore = DataStore.configurationStore\n        DataStore.initGlobal()\n        addPreferencesFromResource(R.xml.global_preferences)\n\n        val appTheme = findPreference<ColorPickerPreference>(Key.APP_THEME)!!\n        appTheme.setOnPreferenceChangeListener { _, newTheme ->\n            if (DataStore.serviceState.started) {\n                SagerNet.reloadService()\n            }\n            val theme = Theme.getTheme(newTheme as Int)\n            app.setTheme(theme)\n            requireActivity().apply {\n                setTheme(theme)\n                ActivityCompat.recreate(this)\n            }\n            true\n        }\n\n        val nightTheme = findPreference<SimpleMenuPreference>(Key.NIGHT_THEME)!!\n        nightTheme.setOnPreferenceChangeListener { _, newTheme ->\n            Theme.currentNightMode = (newTheme as String).toInt()\n            Theme.applyNightTheme()\n            true\n        }\n        val mixedPort = findPreference<EditTextPreference>(Key.MIXED_PORT)!!\n        val serviceMode = findPreference<Preference>(Key.SERVICE_MODE)!!\n        val allowAccess = findPreference<Preference>(Key.ALLOW_ACCESS)!!\n        val appendHttpProxy = findPreference<SwitchPreference>(Key.APPEND_HTTP_PROXY)!!\n\n        val showDirectSpeed = findPreference<SwitchPreference>(Key.SHOW_DIRECT_SPEED)!!\n        val ipv6Mode = findPreference<Preference>(Key.IPV6_MODE)!!\n        val trafficSniffing = findPreference<Preference>(Key.TRAFFIC_SNIFFING)!!\n\n        val bypassLan = findPreference<SwitchPreference>(Key.BYPASS_LAN)!!\n        val bypassLanInCore = findPreference<SwitchPreference>(Key.BYPASS_LAN_IN_CORE)!!\n\n        val remoteDns = findPreference<EditTextPreference>(Key.REMOTE_DNS)!!\n        val directDns = findPreference<EditTextPreference>(Key.DIRECT_DNS)!!\n        val enableDnsRouting = findPreference<SwitchPreference>(Key.ENABLE_DNS_ROUTING)!!\n        val enableFakeDns = findPreference<SwitchPreference>(Key.ENABLE_FAKEDNS)!!\n\n        val logLevel = findPreference<LongClickListPreference>(Key.LOG_LEVEL)!!\n        val mtu = findPreference<MTUPreference>(Key.MTU)!!\n        globalCustomConfig = findPreference(Key.GLOBAL_CUSTOM_CONFIG)!!\n        globalCustomConfig.useConfigStore(Key.GLOBAL_CUSTOM_CONFIG)\n\n        logLevel.dialogLayoutResource = R.layout.layout_loglevel_help\n        logLevel.setOnPreferenceChangeListener { _, _ ->\n            needRestart()\n            true\n        }\n        logLevel.setOnLongClickListener {\n            if (context == null) return@setOnLongClickListener true\n\n            val view = EditText(context).apply {\n                inputType = EditorInfo.TYPE_CLASS_NUMBER\n                var size = DataStore.logBufSize\n                if (size == 0) size = 50\n                setText(size.toString())\n            }\n\n            MaterialAlertDialogBuilder(requireContext()).setTitle(\"Log buffer size (kb)\")\n                .setView(view)\n                .setPositiveButton(android.R.string.ok) { _, _ ->\n                    DataStore.logBufSize = view.text.toString().toInt()\n                    if (DataStore.logBufSize <= 0) DataStore.logBufSize = 50\n                    needRestart()\n                }\n                .setNegativeButton(android.R.string.cancel, null)\n                .show()\n            true\n        }\n\n        mixedPort.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n\n        val metedNetwork = findPreference<Preference>(Key.METERED_NETWORK)!!\n        if (Build.VERSION.SDK_INT < 28) {\n            metedNetwork.remove()\n        }\n        isProxyApps = findPreference(Key.PROXY_APPS)!!\n        isProxyApps.setOnPreferenceChangeListener { _, newValue ->\n            startActivity(Intent(activity, AppManagerActivity::class.java))\n            if (newValue as Boolean) DataStore.dirty = true\n            newValue\n        }\n\n        val profileTrafficStatistics =\n            findPreference<SwitchPreference>(Key.PROFILE_TRAFFIC_STATISTICS)!!\n        val speedInterval = findPreference<SimpleMenuPreference>(Key.SPEED_INTERVAL)!!\n        profileTrafficStatistics.isEnabled = speedInterval.value.toString() != \"0\"\n        speedInterval.setOnPreferenceChangeListener { _, newValue ->\n            profileTrafficStatistics.isEnabled = newValue.toString() != \"0\"\n            needReload()\n            true\n        }\n\n        serviceMode.setOnPreferenceChangeListener { _, _ ->\n            if (DataStore.serviceState.started) SagerNet.stopService()\n            true\n        }\n\n        val tunImplementation = findPreference<SimpleMenuPreference>(Key.TUN_IMPLEMENTATION)!!\n        val resolveDestination = findPreference<SwitchPreference>(Key.RESOLVE_DESTINATION)!!\n        val acquireWakeLock = findPreference<SwitchPreference>(Key.ACQUIRE_WAKE_LOCK)!!\n        val enableClashAPI = findPreference<SwitchPreference>(Key.ENABLE_CLASH_API)!!\n        enableClashAPI.setOnPreferenceChangeListener { _, newValue ->\n            (activity as MainActivity?)?.refreshNavMenu(newValue as Boolean)\n            needReload()\n            true\n        }\n\n        mixedPort.onPreferenceChangeListener = reloadListener\n        appendHttpProxy.onPreferenceChangeListener = reloadListener\n        showDirectSpeed.onPreferenceChangeListener = reloadListener\n        trafficSniffing.onPreferenceChangeListener = reloadListener\n        bypassLan.onPreferenceChangeListener = reloadListener\n        bypassLanInCore.onPreferenceChangeListener = reloadListener\n        mtu.onPreferenceChangeListener = reloadListener\n\n        enableFakeDns.onPreferenceChangeListener = reloadListener\n        remoteDns.onPreferenceChangeListener = reloadListener\n        directDns.onPreferenceChangeListener = reloadListener\n        enableDnsRouting.onPreferenceChangeListener = reloadListener\n\n        ipv6Mode.onPreferenceChangeListener = reloadListener\n        allowAccess.onPreferenceChangeListener = reloadListener\n\n        resolveDestination.onPreferenceChangeListener = reloadListener\n        tunImplementation.onPreferenceChangeListener = reloadListener\n        acquireWakeLock.onPreferenceChangeListener = reloadListener\n        globalCustomConfig.onPreferenceChangeListener = reloadListener\n    }\n\n    override fun onResume() {\n        super.onResume()\n\n        if (::isProxyApps.isInitialized) {\n            isProxyApps.isChecked = DataStore.proxyApps\n        }\n        if (::globalCustomConfig.isInitialized) {\n            globalCustomConfig.notifyChanged()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/StunActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.isVisible\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.databinding.LayoutStunBinding\nimport io.nekohasekai.sagernet.ktx.onMainDispatcher\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport libcore.Libcore\n\nclass StunActivity : ThemedActivity() {\n\n    private lateinit var binding: LayoutStunBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        binding = LayoutStunBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.stun_test)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.baseline_arrow_back_24)\n        }\n        binding.stunTest.setOnClickListener {\n            doTest()\n        }\n    }\n\n    fun doTest() {\n        binding.waitLayout.isVisible = true\n        runOnDefaultDispatcher {\n            val result = try {\n                val _result = Libcore.stunTest(binding.natStunServer.text.toString())\n                if (_result!!.success) {\n                    _result.text\n                } else {\n                    throw Exception(_result.text)\n                }\n            } catch (e: Exception) {\n                onMainDispatcher {\n                    AlertDialog.Builder(this@StunActivity)\n                        .setTitle(R.string.error_title)\n                        .setMessage(e.readableMessage)\n                        .setPositiveButton(android.R.string.ok) { _, _ ->\n                            finish()\n                        }\n                        .setOnCancelListener {\n                            finish()\n                        }\n                        .runCatching { show() }\n                }\n                return@runOnDefaultDispatcher\n            }\n            onMainDispatcher {\n                binding.waitLayout.isVisible = false\n                binding.natResult.text = result\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/SwitchActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\n\nclass SwitchActivity : ThemedActivity(R.layout.layout_empty),\n    ConfigurationFragment.SelectCallback {\n\n    override val isDialog = true\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        supportFragmentManager.beginTransaction()\n            .replace(\n                R.id.fragment_holder,\n                ConfigurationFragment(true, null, R.string.action_switch)\n            )\n            .commitAllowingStateLoss()\n    }\n\n    override fun returnProfile(profileId: Long) {\n        val old = DataStore.selectedProxy\n        DataStore.selectedProxy = profileId\n        runOnMainDispatcher {\n            ProfileManager.postUpdate(old, true)\n            ProfileManager.postUpdate(profileId, true)\n        }\n        SagerNet.reloadService()\n        finish()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.content.res.Configuration\nimport android.os.Build\nimport android.os.Bundle\nimport android.widget.TextView\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.app.ActivityCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\nimport com.google.android.material.appbar.AppBarLayout\nimport com.google.android.material.snackbar.Snackbar\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.utils.Theme\n\nabstract class ThemedActivity : AppCompatActivity {\n    constructor() : super()\n    constructor(contentLayoutId: Int) : super(contentLayoutId)\n\n    var themeResId = 0\n    var uiMode = 0\n    open val isDialog = false\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        if (!isDialog) {\n            Theme.apply(this)\n        } else {\n            Theme.applyDialog(this)\n        }\n        Theme.applyNightTheme()\n\n        super.onCreate(savedInstanceState)\n\n        uiMode = resources.configuration.uiMode\n\n        if (Build.VERSION.SDK_INT >= 35) {\n            ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content)) { _, insets ->\n                val top = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top\n                findViewById<AppBarLayout>(R.id.appbar)?.apply {\n                    updatePadding(top = top)\n//                Logs.w(\"appbar $top\")\n                }\n//            findViewById<NavigationView>(R.id.nav_view)?.apply {\n//                updatePadding(top = top)\n//            }\n                insets\n            }\n        }\n    }\n\n    override fun setTheme(resId: Int) {\n        super.setTheme(resId)\n\n        themeResId = resId\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n\n        if (newConfig.uiMode != uiMode) {\n            uiMode = newConfig.uiMode\n            ActivityCompat.recreate(this)\n        }\n    }\n\n    fun snackbar(@StringRes resId: Int): Snackbar = snackbar(\"\").setText(resId)\n    fun snackbar(text: CharSequence): Snackbar = snackbarInternal(text).apply {\n        view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text).apply {\n            maxLines = 10\n        }\n    }\n\n    internal open fun snackbarInternal(text: CharSequence): Snackbar = throw NotImplementedError()\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ToolbarFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.View\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.GravityCompat\nimport androidx.fragment.app.Fragment\nimport io.nekohasekai.sagernet.R\n\nopen class ToolbarFragment : Fragment {\n\n    constructor() : super()\n    constructor(contentLayoutId: Int) : super(contentLayoutId)\n\n    lateinit var toolbar: Toolbar\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        toolbar = view.findViewById(R.id.toolbar)\n        toolbar.setNavigationIcon(R.drawable.ic_navigation_menu)\n        toolbar.setNavigationOnClickListener {\n            (activity as MainActivity).binding.drawerLayout.openDrawer(GravityCompat.START)\n        }\n    }\n\n    open fun onKeyDown(ketCode: Int, event: KeyEvent) = false\n    open fun onBackPressed(): Boolean = false\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/ToolsFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.Fragment\nimport androidx.viewpager2.adapter.FragmentStateAdapter\nimport com.google.android.material.tabs.TabLayoutMediator\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.databinding.LayoutToolsBinding\n\nclass ToolsFragment : ToolbarFragment(R.layout.layout_tools) {\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        toolbar.setTitle(R.string.menu_tools)\n\n        val tools = mutableListOf<NamedFragment>()\n        tools.add(NetworkFragment())\n        tools.add(BackupFragment())\n\n        val binding = LayoutToolsBinding.bind(view)\n        binding.toolsPager.adapter = ToolsAdapter(tools)\n\n        TabLayoutMediator(binding.toolsTab, binding.toolsPager) { tab, position ->\n            tab.text = tools[position].name()\n            tab.view.setOnLongClickListener { // clear toast\n                true\n            }\n        }.attach()\n    }\n\n    inner class ToolsAdapter(val tools: List<Fragment>) : FragmentStateAdapter(this) {\n\n        override fun getItemCount() = tools.size\n\n        override fun createFragment(position: Int) = tools[position]\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/VpnRequestActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.app.Activity\nimport android.app.KeyguardManager\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.VpnService\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.content.getSystemService\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.broadcastReceiver\n\nclass VpnRequestActivity : AppCompatActivity() {\n    private var receiver: BroadcastReceiver? = null\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {\n            receiver = broadcastReceiver { _, _ -> connect.launch(null) }\n            if (SDK_INT >= 33) {\n                registerReceiver(\n                    receiver,\n                    IntentFilter(Intent.ACTION_USER_PRESENT),\n                    Context.RECEIVER_EXPORTED\n                )\n            } else {\n                registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT))\n            }\n        } else connect.launch(null)\n    }\n\n    private val connect = registerForActivityResult(StartService()) {\n        if (it) Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()\n        finish()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (receiver != null) unregisterReceiver(receiver)\n    }\n\n    class StartService : ActivityResultContract<Void?, Boolean>() {\n        private var cachedIntent: Intent? = null\n\n        override fun getSynchronousResult(\n            context: Context,\n            input: Void?,\n        ): SynchronousResult<Boolean>? {\n            if (DataStore.serviceMode == Key.MODE_VPN) VpnService.prepare(context)?.let { intent ->\n                cachedIntent = intent\n                return null\n            }\n            SagerNet.startService()\n            return SynchronousResult(false)\n        }\n\n        override fun createIntent(context: Context, input: Void?) =\n            cachedIntent!!.also { cachedIntent = null }\n\n        override fun parseResult(resultCode: Int, intent: Intent?) =\n            if (resultCode == Activity.RESULT_OK) {\n                SagerNet.startService()\n                false\n            } else {\n                Logs.e(\"Failed to start VpnService: $intent\")\n                true\n            }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/WebviewFragment.kt",
    "content": "package io.nekohasekai.sagernet.ui\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.text.InputType\nimport android.view.MenuItem\nimport android.view.View\nimport android.webkit.*\nimport android.widget.EditText\nimport androidx.appcompat.widget.Toolbar\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.databinding.LayoutWebviewBinding\nimport moe.matsuri.nb4a.utils.WebViewUtil\n\n// Fragment必须有一个无参public的构造函数，否则在数据恢复的时候，会报crash\n\nclass WebviewFragment : ToolbarFragment(R.layout.layout_webview), Toolbar.OnMenuItemClickListener {\n\n    lateinit var mWebView: WebView\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n\n        // layout\n        toolbar.setTitle(R.string.menu_dashboard)\n        toolbar.inflateMenu(R.menu.yacd_menu)\n        toolbar.setOnMenuItemClickListener(this)\n\n        val binding = LayoutWebviewBinding.bind(view)\n\n        // webview\n        WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)\n        mWebView = binding.webview\n        mWebView.settings.domStorageEnabled = true\n        mWebView.settings.javaScriptEnabled = true\n        mWebView.webViewClient = object : WebViewClient() {\n            override fun onReceivedError(\n                view: WebView?, request: WebResourceRequest?, error: WebResourceError?\n            ) {\n                WebViewUtil.onReceivedError(view, request, error)\n            }\n\n            override fun onPageFinished(view: WebView?, url: String?) {\n                super.onPageFinished(view, url)\n            }\n        }\n        mWebView.loadUrl(DataStore.yacdURL)\n    }\n\n    @SuppressLint(\"CheckResult\")\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_set_url -> {\n                val view = EditText(context).apply {\n                    inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_URI\n                    setText(DataStore.yacdURL)\n                }\n                MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.set_panel_url)\n                    .setView(view)\n                    .setPositiveButton(android.R.string.ok) { _, _ ->\n                        DataStore.yacdURL = view.text.toString()\n                        mWebView.loadUrl(DataStore.yacdURL)\n                    }\n                    .setNegativeButton(android.R.string.cancel, null)\n                    .show()\n            }\n            R.id.close -> {\n                mWebView.onPause()\n                mWebView.removeAllViews()\n                mWebView.destroy()\n            }\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/ChainSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundle\nimport android.text.format.Formatter\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.activity.result.component1\nimport androidx.activity.result.component2\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.core.view.isVisible\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.ProxyEntity\nimport io.nekohasekai.sagernet.databinding.LayoutAddEntityBinding\nimport io.nekohasekai.sagernet.databinding.LayoutProfileBinding\nimport io.nekohasekai.sagernet.fmt.internal.ChainBean\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.ui.ProfileSelectActivity\nimport moe.matsuri.nb4a.Protocols.getProtocolColor\n\nclass ChainSettingsActivity : ProfileSettingsActivity<ChainBean>(R.layout.layout_chain_settings) {\n\n    override fun createEntity() = ChainBean()\n\n    val proxyList = ArrayList<ProxyEntity>()\n\n    override fun ChainBean.init() {\n        DataStore.profileName = name\n        DataStore.serverProtocol = proxies.joinToString(\",\")\n    }\n\n    override fun ChainBean.serialize() {\n        name = DataStore.profileName\n        proxies = proxyList.map { it.id }\n        initializeDefaultValues()\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.name_preferences)\n    }\n\n    lateinit var configurationList: RecyclerView\n    lateinit var configurationAdapter: ProxiesAdapter\n    lateinit var layoutManager: LinearLayoutManager\n\n    @SuppressLint(\"InlinedApi\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        supportActionBar!!.setTitle(R.string.chain_settings)\n        configurationList = findViewById(R.id.configuration_list)\n        layoutManager = FixedLinearLayoutManager(configurationList)\n        configurationList.layoutManager = layoutManager\n        configurationAdapter = ProxiesAdapter()\n        configurationList.adapter = configurationAdapter\n\n        ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(\n            ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.START\n        ) {\n            override fun getSwipeDirs(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) = if (viewHolder is ProfileHolder) {\n                super.getSwipeDirs(recyclerView, viewHolder)\n            } else 0\n\n            override fun getDragDirs(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n            ) = if (viewHolder is ProfileHolder) {\n                super.getDragDirs(recyclerView, viewHolder)\n            } else 0\n\n            override fun onMove(\n                recyclerView: RecyclerView,\n                viewHolder: RecyclerView.ViewHolder,\n                target: RecyclerView.ViewHolder,\n            ): Boolean {\n                return if (target !is ProfileHolder) false else {\n                    configurationAdapter.move(\n                        viewHolder.bindingAdapterPosition, target.bindingAdapterPosition\n                    )\n                    true\n                }\n            }\n\n            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n                configurationAdapter.remove(viewHolder.bindingAdapterPosition)\n            }\n\n        }).attachToRecyclerView(configurationList)\n    }\n\n    override fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) {\n        view.rootView.findViewById<RecyclerView>(R.id.recycler_view).apply {\n            (layoutParams ?: LinearLayout.LayoutParams(-1, -2)).apply {\n                height = -2\n                layoutParams = this\n            }\n        }\n\n        runOnDefaultDispatcher {\n            configurationAdapter.reload()\n        }\n    }\n\n    inner class ProxiesAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n        suspend fun reload() {\n            val idList = DataStore.serverProtocol.split(\",\")\n                .mapNotNull { it.takeIf { it.isNotBlank() }?.toLong() }\n            if (idList.isNotEmpty()) {\n                val profiles = ProfileManager.getProfiles(idList).map { it.id to it }.toMap()\n                for (id in idList) {\n                    proxyList.add(profiles[id] ?: continue)\n                }\n            }\n            onMainDispatcher {\n                notifyDataSetChanged()\n            }\n        }\n\n        fun move(from: Int, to: Int) {\n            val toMove = proxyList[to - 1]\n            proxyList[to - 1] = proxyList[from - 1]\n            proxyList[from - 1] = toMove\n            notifyItemMoved(from, to)\n            DataStore.dirty = true\n        }\n\n        fun remove(index: Int) {\n            proxyList.removeAt(index - 1)\n            notifyItemRemoved(index)\n            DataStore.dirty = true\n        }\n\n        override fun getItemId(position: Int): Long {\n            return if (position == 0) 0 else proxyList[position - 1].id\n        }\n\n        override fun getItemViewType(position: Int): Int {\n            return if (position == 0) 0 else 1\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n            return if (viewType == 0) {\n                AddHolder(LayoutAddEntityBinding.inflate(layoutInflater, parent, false))\n            } else {\n                ProfileHolder(LayoutProfileBinding.inflate(layoutInflater, parent, false))\n            }\n        }\n\n        override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n            if (holder is AddHolder) {\n                holder.bind()\n            } else if (holder is ProfileHolder) {\n                holder.bind(proxyList[position - 1])\n            }\n        }\n\n        override fun getItemCount(): Int {\n            return proxyList.size + 1\n        }\n\n    }\n\n    fun testProfileAllowed(profile: ProxyEntity): Boolean {\n        if (profile.id == DataStore.editingId) return false\n\n        for (entity in proxyList) {\n            if (testProfileContains(entity, profile)) return false\n        }\n\n        return true\n    }\n\n    fun testProfileContains(profile: ProxyEntity, anotherProfile: ProxyEntity): Boolean {\n        if (profile.type != 8 || anotherProfile.type != 8) return false\n        if (profile.id == anotherProfile.id) return true\n        val proxies = profile.chainBean!!.proxies\n        if (proxies.contains(anotherProfile.id)) return true\n        if (proxies.isNotEmpty()) {\n            for (entity in ProfileManager.getProfiles(proxies)) {\n                if (testProfileContains(entity, anotherProfile)) {\n                    return true\n                }\n            }\n        }\n        return false\n    }\n\n    var replacing = 0\n\n    val selectProfileForAdd =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { (resultCode, data) ->\n            if (resultCode == Activity.RESULT_OK) runOnDefaultDispatcher {\n                DataStore.dirty = true\n\n                val profile = ProfileManager.getProfile(\n                    data!!.getLongExtra(\n                        ProfileSelectActivity.EXTRA_PROFILE_ID, 0\n                    )\n                )!!\n\n                if (!testProfileAllowed(profile)) {\n                    onMainDispatcher {\n                        MaterialAlertDialogBuilder(this@ChainSettingsActivity).setTitle(R.string.circular_reference)\n                            .setMessage(R.string.circular_reference_sum)\n                            .setPositiveButton(android.R.string.ok, null).show()\n                    }\n                } else {\n                    configurationList.post {\n                        if (replacing != 0) {\n                            proxyList[replacing - 1] = profile\n                            configurationAdapter.notifyItemChanged(replacing)\n                        } else {\n                            proxyList.add(profile)\n                            configurationAdapter.notifyItemInserted(proxyList.size)\n                        }\n                    }\n                }\n            }\n        }\n\n    inner class AddHolder(val binding: LayoutAddEntityBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun bind() {\n            binding.root.setOnClickListener {\n                replacing = 0\n                selectProfileForAdd.launch(\n                    Intent(\n                        this@ChainSettingsActivity, ProfileSelectActivity::class.java\n                    )\n                )\n            }\n        }\n    }\n\n    inner class ProfileHolder(binding: LayoutProfileBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        val profileName = binding.profileName\n        val profileType = binding.profileType\n        val trafficText: TextView = binding.trafficText\n        val editButton = binding.edit\n        val shareLayout = binding.share\n\n        fun bind(proxyEntity: ProxyEntity) {\n\n            profileName.text = proxyEntity.displayName()\n            profileType.text = proxyEntity.displayType()\n            profileType.setTextColor(getProtocolColor(proxyEntity.type))\n\n            val rx = proxyEntity.rx\n            val tx = proxyEntity.tx\n\n            val showTraffic = rx + tx != 0L\n            trafficText.isVisible = showTraffic\n            if (showTraffic) {\n                trafficText.text = itemView.context.getString(\n                    R.string.traffic,\n                    Formatter.formatFileSize(itemView.context, tx),\n                    Formatter.formatFileSize(itemView.context, rx)\n                )\n            }\n\n            editButton.setOnClickListener {\n                replacing = bindingAdapterPosition\n                selectProfileForAdd.launch(Intent(\n                    this@ChainSettingsActivity, ProfileSelectActivity::class.java\n                ).apply {\n                    putExtra(ProfileSelectActivity.EXTRA_SELECTED, proxyEntity)\n                })\n            }\n\n            shareLayout.isVisible = false\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/ConfigEditActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.ViewGroup.MarginLayoutParams\nimport android.widget.LinearLayout\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updateLayoutParams\nimport androidx.core.widget.addTextChangedListener\nimport com.blacksquircle.ui.editorkit.insert\nimport com.blacksquircle.ui.language.json.JsonLanguage\nimport com.github.shadowsocks.plugin.Empty\nimport com.github.shadowsocks.plugin.fragment.AlertDialogFragment\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.databinding.LayoutEditConfigBinding\nimport io.nekohasekai.sagernet.ktx.getColorAttr\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ktx.toStringPretty\nimport io.nekohasekai.sagernet.ui.ThemedActivity\nimport io.nekohasekai.sagernet.widget.ListListener\nimport moe.matsuri.nb4a.ui.ExtendedKeyboard\nimport org.json.JSONObject\n\nclass ConfigEditActivity : ThemedActivity() {\n\n    var dirty = false\n    var key = Key.SERVER_CONFIG\n    var useConfigStore = false\n\n    class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.unsaved_changes_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                (requireActivity() as ConfigEditActivity).saveAndExit()\n            }\n            setNegativeButton(R.string.no) { _, _ ->\n                requireActivity().finish()\n            }\n            setNeutralButton(android.R.string.cancel, null)\n        }\n    }\n\n    lateinit var binding: LayoutEditConfigBinding\n\n    @SuppressLint(\"InlinedApi\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        intent?.extras?.apply {\n            getString(\"key\")?.let { key = it }\n            getString(\"useConfigStore\")?.let { useConfigStore = true }\n        }\n\n        binding = LayoutEditConfigBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.config_settings)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        binding.editor.apply {\n            language = JsonLanguage()\n            setHorizontallyScrolling(true)\n            if (useConfigStore) {\n                setTextContent(DataStore.configurationStore.getString(key) ?: \"\")\n            } else {\n                setTextContent(DataStore.profileCacheStore.getString(key) ?: \"\")\n            }\n            addTextChangedListener {\n                if (!dirty) {\n                    dirty = true\n                    DataStore.dirty = true\n                }\n            }\n        }\n\n        binding.actionTab.setOnClickListener {\n            try {\n                binding.editor.insert(binding.editor.tab())\n            } catch (e: Exception) {\n            }\n        }\n        binding.actionUndo.setOnClickListener {\n            try {\n                binding.editor.undo()\n            } catch (_: Exception) {\n            }\n        }\n        binding.actionRedo.setOnClickListener {\n            try {\n                binding.editor.redo()\n            } catch (_: Exception) {\n            }\n        }\n        binding.actionFormat.setOnClickListener {\n            formatText()?.let {\n                binding.editor.setTextContent(it)\n            }\n        }\n\n        val extendedKeyboard = findViewById<ExtendedKeyboard>(R.id.extended_keyboard)\n        extendedKeyboard.setKeyListener { char ->\n            try {\n                binding.editor.insert(char)\n            } catch (e: Exception) {\n            }\n        }\n        extendedKeyboard.setHasFixedSize(true)\n        extendedKeyboard.submitList(\"{},:_\\\"\".map { it.toString() })\n        extendedKeyboard.setBackgroundColor(getColorAttr(R.attr.primaryOrTextPrimary))\n\n        val keyboardContainer = findViewById<LinearLayout>(R.id.keyboard_container)\n        ViewCompat.setOnApplyWindowInsetsListener(keyboardContainer) { v, windowInsets ->\n            val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())\n            val systemBarInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())\n            val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())\n            v.updateLayoutParams<MarginLayoutParams> {\n                // systemBar insets are applied to the bottom of the keyboard\n                if (imeVisible) {\n                    bottomMargin = imeInsets.bottom - systemBarInsets.bottom\n                } else {\n                    bottomMargin = 0\n                }\n            }\n\n            WindowInsetsCompat.CONSUMED\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(binding.root, ListListener)\n    }\n\n    fun formatText(): String? {\n        try {\n            val txt = binding.editor.text.toString()\n            if (txt.isBlank()) {\n                return \"\"\n            }\n            return JSONObject(txt).toStringPretty()\n        } catch (e: Exception) {\n            MaterialAlertDialogBuilder(this).setTitle(R.string.error_title)\n                .setMessage(e.readableMessage).show()\n            return null\n        }\n    }\n\n    fun saveAndExit() {\n        formatText()?.let {\n            if (useConfigStore) {\n                DataStore.configurationStore.putString(key, it)\n            } else {\n                DataStore.profileCacheStore.putString(key, it)\n            }\n            finish()\n        }\n    }\n\n    override fun onBackPressed() {\n        if (dirty) UnsavedChangesDialogFragment().apply { key() }\n            .show(supportFragmentManager, null) else super.onBackPressed()\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        if (!super.onSupportNavigateUp()) finish()\n        return true\n    }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.profile_apply_menu, menu)\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_apply -> {\n                saveAndExit()\n                return true\n            }\n        }\n        return super.onOptionsItemSelected(item)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/HttpSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\n\nclass HttpSettingsActivity : StandardV2RaySettingsActivity() {\n\n    override fun createEntity() = HttpBean()\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/HysteriaSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.SwitchPreference\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.hysteria.HysteriaBean\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass HysteriaSettingsActivity : ProfileSettingsActivity<HysteriaBean>() {\n\n    override fun createEntity() = HysteriaBean().applyDefaultValues()\n\n    override fun HysteriaBean.init() {\n        DataStore.profileName = name\n        DataStore.protocolVersion = protocolVersion\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPorts = serverPorts\n        DataStore.serverObfs = obfuscation\n        DataStore.serverAuthType = authPayloadType\n        DataStore.serverProtocolInt = protocol\n        DataStore.serverPassword = authPayload\n        DataStore.serverSNI = sni\n        DataStore.serverALPN = alpn\n        DataStore.serverCertificates = caText\n        DataStore.serverAllowInsecure = allowInsecure\n        DataStore.serverUploadSpeed = uploadMbps\n        DataStore.serverDownloadSpeed = downloadMbps\n        DataStore.serverStreamReceiveWindow = streamReceiveWindow\n        DataStore.serverConnectionReceiveWindow = connectionReceiveWindow\n        DataStore.serverDisableMtuDiscovery = disableMtuDiscovery\n        DataStore.serverHopInterval = hopInterval\n    }\n\n    override fun HysteriaBean.serialize() {\n        name = DataStore.profileName\n        protocolVersion = DataStore.protocolVersion\n        serverAddress = DataStore.serverAddress\n        serverPorts = DataStore.serverPorts\n        obfuscation = DataStore.serverObfs\n        authPayloadType = DataStore.serverAuthType\n        authPayload = DataStore.serverPassword\n        protocol = DataStore.serverProtocolInt\n        sni = DataStore.serverSNI\n        alpn = DataStore.serverALPN\n        caText = DataStore.serverCertificates\n        allowInsecure = DataStore.serverAllowInsecure\n        uploadMbps = DataStore.serverUploadSpeed\n        downloadMbps = DataStore.serverDownloadSpeed\n        streamReceiveWindow = DataStore.serverStreamReceiveWindow\n        connectionReceiveWindow = DataStore.serverConnectionReceiveWindow\n        disableMtuDiscovery = DataStore.serverDisableMtuDiscovery\n        hopInterval = DataStore.serverHopInterval\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.hysteria_preferences)\n\n        val authType = findPreference<SimpleMenuPreference>(Key.SERVER_AUTH_TYPE)!!\n        val authPayload = findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!\n        authPayload.isVisible = authType.value != \"${HysteriaBean.TYPE_NONE}\"\n        authType.setOnPreferenceChangeListener { _, newValue ->\n            authPayload.isVisible = newValue != \"${HysteriaBean.TYPE_NONE}\"\n            true\n        }\n\n        val protocol = findPreference<SimpleMenuPreference>(Key.SERVER_PROTOCOL)!!\n        val alpn = findPreference<EditTextPreference>(Key.SERVER_ALPN)!!\n\n        fun updateVersion(v: Int) {\n            if (v == 2) {\n                authPayload.isVisible = true\n                //\n                authType.isVisible = false\n                protocol.isVisible = false\n                alpn.isVisible = false\n                //\n                findPreference<EditTextPreference>(Key.SERVER_STREAM_RECEIVE_WINDOW)!!.isVisible =\n                    false\n                findPreference<EditTextPreference>(Key.SERVER_CONNECTION_RECEIVE_WINDOW)!!.isVisible =\n                    false\n                findPreference<SwitchPreference>(Key.SERVER_DISABLE_MTU_DISCOVERY)!!.isVisible =\n                    false\n                //\n                authPayload.title = resources.getString(R.string.password)\n            } else {\n                authType.isVisible = true\n                authPayload.isVisible = true\n                protocol.isVisible = true\n                alpn.isVisible = true\n                //\n                findPreference<EditTextPreference>(Key.SERVER_STREAM_RECEIVE_WINDOW)!!.isVisible =\n                    true\n                findPreference<EditTextPreference>(Key.SERVER_CONNECTION_RECEIVE_WINDOW)!!.isVisible =\n                    true\n                findPreference<SwitchPreference>(Key.SERVER_DISABLE_MTU_DISCOVERY)!!.isVisible =\n                    true\n                //\n                authPayload.title = resources.getString(R.string.hysteria_auth_payload)\n            }\n        }\n        findPreference<SimpleMenuPreference>(Key.PROTOCOL_VERSION)!!.setOnPreferenceChangeListener { _, newValue ->\n            updateVersion(newValue.toString().toIntOrNull() ?: 1)\n            true\n        }\n        updateVersion(DataStore.protocolVersion)\n\n        findPreference<EditTextPreference>(Key.SERVER_UPLOAD_SPEED)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_DOWNLOAD_SPEED)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_STREAM_RECEIVE_WINDOW)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_CONNECTION_RECEIVE_WINDOW)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        findPreference<EditTextPreference>(Key.SERVER_OBFS)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n\n        findPreference<EditTextPreference>(Key.SERVER_HOP_INTERVAL)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/MieruSettingsActivity.kt",
    "content": "/******************************************************************************\n *                                                                            *\n * Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu>             *\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 *                                                                            *\n ******************************************************************************/\n\npackage io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.mieru.MieruBean\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass MieruSettingsActivity : ProfileSettingsActivity<MieruBean>() {\n\n    override fun createEntity() = MieruBean().applyDefaultValues()\n\n    override fun MieruBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n        DataStore.serverProtocol = protocol\n        DataStore.serverUsername = username\n        DataStore.serverPassword = password\n        DataStore.serverMTU = mtu\n    }\n\n    override fun MieruBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n        protocol = DataStore.serverProtocol\n        username = DataStore.serverUsername\n        password = DataStore.serverPassword\n        mtu = DataStore.serverMTU\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.mieru_preferences)\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        val protocol = findPreference<SimpleMenuPreference>(Key.SERVER_PROTOCOL)!!\n        val mtu = findPreference<EditTextPreference>(Key.SERVER_MTU)!!\n        mtu.isVisible = protocol.value.equals(\"UDP\")\n        protocol.setOnPreferenceChangeListener { _, newValue ->\n            mtu.isVisible = newValue.equals(\"UDP\")\n            true\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/NaiveSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.naive.NaiveBean\n\nclass NaiveSettingsActivity : ProfileSettingsActivity<NaiveBean>() {\n\n    override fun createEntity() = NaiveBean()\n\n    override fun NaiveBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n        DataStore.serverUsername = username\n        DataStore.serverPassword = password\n        DataStore.serverProtocol = proto\n        DataStore.serverSNI = sni\n        DataStore.serverCertificates = certificates\n        DataStore.serverHeaders = extraHeaders\n        DataStore.serverInsecureConcurrency = insecureConcurrency\n        DataStore.profileCacheStore.putBoolean(\"sUoT\", sUoT)\n    }\n\n    override fun NaiveBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n        username = DataStore.serverUsername\n        password = DataStore.serverPassword\n        proto = DataStore.serverProtocol\n        sni = DataStore.serverSNI\n        certificates = DataStore.serverCertificates\n        extraHeaders = DataStore.serverHeaders.replace(\"\\r\\n\", \"\\n\")\n        insecureConcurrency = DataStore.serverInsecureConcurrency\n        sUoT = DataStore.profileCacheStore.getBoolean(\"sUoT\")\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.naive_preferences)\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        findPreference<EditTextPreference>(Key.SERVER_INSECURE_CONCURRENCY)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n        }\n    }\n\n    override fun finish() {\n        if (DataStore.profileName == \"喵要打开隐藏功能\") {\n            DataStore.isExpert = true\n        } else if (DataStore.profileName == \"喵要关闭隐藏功能\") {\n            DataStore.isExpert = false\n        }\n        super.finish()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/ProfileSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Parcelable\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.LinearLayout\nimport android.widget.ScrollView\nimport android.widget.Toast\nimport androidx.activity.result.component1\nimport androidx.activity.result.component2\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.isVisible\nimport androidx.preference.EditTextPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceDataStore\nimport androidx.preference.PreferenceFragmentCompat\nimport com.github.shadowsocks.plugin.Empty\nimport com.github.shadowsocks.plugin.fragment.AlertDialogFragment\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.*\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.GroupManager\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.databinding.LayoutGroupItemBinding\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.ui.ThemedActivity\nimport io.nekohasekai.sagernet.widget.ListListener\nimport kotlinx.parcelize.Parcelize\nimport kotlin.properties.Delegates\n\n@Suppress(\"UNCHECKED_CAST\")\nabstract class ProfileSettingsActivity<T : AbstractBean>(\n    @LayoutRes resId: Int = R.layout.layout_config_settings,\n) : ThemedActivity(resId), OnPreferenceDataStoreChangeListener {\n\n    class UnsavedChangesDialogFragment : AlertDialogFragment<Empty, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.unsaved_changes_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    (requireActivity() as ProfileSettingsActivity<*>).saveAndExit()\n                }\n            }\n            setNegativeButton(R.string.no) { _, _ ->\n                requireActivity().finish()\n            }\n            setNeutralButton(android.R.string.cancel, null)\n        }\n    }\n\n    @Parcelize\n    data class ProfileIdArg(val profileId: Long, val groupId: Long) : Parcelable\n    class DeleteConfirmationDialogFragment : AlertDialogFragment<ProfileIdArg, Empty>() {\n        override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {\n            setTitle(R.string.delete_confirm_prompt)\n            setPositiveButton(R.string.yes) { _, _ ->\n                runOnDefaultDispatcher {\n                    ProfileManager.deleteProfile(arg.groupId, arg.profileId)\n                }\n                requireActivity().finish()\n            }\n            setNegativeButton(R.string.no, null)\n        }\n    }\n\n    companion object {\n        const val EXTRA_PROFILE_ID = \"id\"\n        const val EXTRA_IS_SUBSCRIPTION = \"sub\"\n    }\n\n    abstract fun createEntity(): T\n    abstract fun T.init()\n    abstract fun T.serialize()\n\n    val proxyEntity by lazy { SagerDatabase.proxyDao.getById(DataStore.editingId) }\n    protected var isSubscription by Delegates.notNull<Boolean>()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setSupportActionBar(findViewById(R.id.toolbar))\n        supportActionBar?.apply {\n            setTitle(R.string.profile_config)\n            setDisplayHomeAsUpEnabled(true)\n            setHomeAsUpIndicator(R.drawable.ic_navigation_close)\n        }\n\n        if (savedInstanceState == null) {\n            val editingId = intent.getLongExtra(EXTRA_PROFILE_ID, 0L)\n            isSubscription = intent.getBooleanExtra(EXTRA_IS_SUBSCRIPTION, false)\n            DataStore.editingId = editingId\n            runOnDefaultDispatcher {\n                if (editingId == 0L) {\n                    DataStore.editingGroup = DataStore.selectedGroupForImport()\n                    createEntity().applyDefaultValues().init()\n                } else {\n                    if (proxyEntity == null) {\n                        onMainDispatcher {\n                            finish()\n                        }\n                        return@runOnDefaultDispatcher\n                    }\n                    DataStore.editingGroup = proxyEntity!!.groupId\n                    (proxyEntity!!.requireBean() as T).init()\n                }\n\n                onMainDispatcher {\n                    supportFragmentManager.beginTransaction()\n                        .replace(R.id.settings, MyPreferenceFragmentCompat())\n                        .commit()\n                }\n            }\n\n\n        }\n\n    }\n\n    open suspend fun saveAndExit() {\n\n        val editingId = DataStore.editingId\n        if (editingId == 0L) {\n            val editingGroup = DataStore.editingGroup\n            ProfileManager.createProfile(editingGroup, createEntity().apply { serialize() })\n        } else {\n            if (proxyEntity == null) {\n                finish()\n                return\n            }\n            if (proxyEntity!!.id == DataStore.selectedProxy) {\n                SagerNet.stopService()\n            }\n            ProfileManager.updateProfile(proxyEntity!!.apply { (requireBean() as T).serialize() })\n        }\n        finish()\n\n    }\n\n    val child by lazy { supportFragmentManager.findFragmentById(R.id.settings) as MyPreferenceFragmentCompat }\n\n    override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.profile_config_menu, menu)\n        menu.findItem(R.id.action_move)?.apply {\n            if (DataStore.editingId != 0L // not new profile\n                && SagerDatabase.groupDao.getById(DataStore.editingGroup)?.type == GroupType.BASIC // not in subscription group\n                && SagerDatabase.groupDao.allGroups()\n                    .filter { it.type == GroupType.BASIC }.size > 1 // have other basic group\n            ) isVisible = true\n        }\n        menu.findItem(R.id.action_create_shortcut)?.apply {\n            if (Build.VERSION.SDK_INT >= 26 && DataStore.editingId != 0L) {\n                isVisible = true // not new profile\n            }\n        }\n        // shared menu item\n        menu.findItem(R.id.action_custom_outbound_json)?.isVisible = true\n        menu.findItem(R.id.action_custom_config_json)?.isVisible = true\n        return true\n    }\n\n    override fun onOptionsItemSelected(item: MenuItem) = child.onOptionsItemSelected(item)\n\n    override fun onBackPressed() {\n        if (DataStore.dirty) UnsavedChangesDialogFragment().apply { key() }\n            .show(supportFragmentManager, null) else super.onBackPressed()\n    }\n\n    override fun onSupportNavigateUp(): Boolean {\n        if (!super.onSupportNavigateUp()) finish()\n        return true\n    }\n\n    override fun onDestroy() {\n        DataStore.profileCacheStore.unregisterChangeListener(this)\n        super.onDestroy()\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        if (key != Key.PROFILE_DIRTY) {\n            DataStore.dirty = true\n        }\n    }\n\n    abstract fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    )\n\n    open fun PreferenceFragmentCompat.viewCreated(view: View, savedInstanceState: Bundle?) {\n    }\n\n    open fun PreferenceFragmentCompat.displayPreferenceDialog(preference: Preference): Boolean {\n        return false\n    }\n\n    class MyPreferenceFragmentCompat : PreferenceFragmentCompat() {\n\n        var activity: ProfileSettingsActivity<*>? = null\n\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            preferenceManager.preferenceDataStore = DataStore.profileCacheStore\n            try {\n                activity = (requireActivity() as ProfileSettingsActivity<*>).apply {\n                    createPreferences(savedInstanceState, rootKey)\n                }\n            } catch (e: Exception) {\n                Toast.makeText(\n                    SagerNet.application,\n                    \"Error on createPreferences, please try again.\",\n                    Toast.LENGTH_SHORT\n                ).show()\n                Logs.e(e)\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n\n            ViewCompat.setOnApplyWindowInsetsListener(listView, ListListener)\n\n            activity?.apply {\n                viewCreated(view, savedInstanceState)\n                DataStore.dirty = false\n                DataStore.profileCacheStore.registerChangeListener(this)\n            }\n        }\n\n        var callbackCustom: ((String) -> Unit)? = null\n        var callbackCustomOutbound: ((String) -> Unit)? = null\n\n        val resultCallbackCustom = registerForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { (_, _) ->\n            callbackCustom?.let { it(DataStore.serverCustom) }\n        }\n\n        val resultCallbackCustomOutbound = registerForActivityResult(\n            ActivityResultContracts.StartActivityForResult()\n        ) { (_, _) ->\n            callbackCustomOutbound?.let { it(DataStore.serverCustomOutbound) }\n        }\n\n        @SuppressLint(\"CheckResult\")\n        override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {\n            R.id.action_delete -> {\n                if (DataStore.editingId == 0L) {\n                    requireActivity().finish()\n                } else {\n                    DeleteConfirmationDialogFragment().apply {\n                        arg(\n                            ProfileIdArg(\n                                DataStore.editingId, DataStore.editingGroup\n                            )\n                        )\n                        key()\n                    }.show(parentFragmentManager, null)\n                }\n                true\n            }\n\n            R.id.action_apply -> {\n                runOnDefaultDispatcher {\n                    activity?.saveAndExit()\n                }\n                true\n            }\n\n            R.id.action_custom_outbound_json -> {\n                activity?.proxyEntity?.apply {\n                    val bean = requireBean()\n                    DataStore.serverCustomOutbound = bean.customOutboundJson\n                    callbackCustomOutbound = { bean.customOutboundJson = it }\n                    resultCallbackCustomOutbound.launch(\n                        Intent(\n                            requireContext(),\n                            ConfigEditActivity::class.java\n                        ).apply {\n                            putExtra(\"key\", Key.SERVER_CUSTOM_OUTBOUND)\n                        })\n                }\n                true\n            }\n\n            R.id.action_custom_config_json -> {\n                activity?.proxyEntity?.apply {\n                    val bean = requireBean()\n                    DataStore.serverCustom = bean.customConfigJson\n                    callbackCustom = { bean.customConfigJson = it }\n                    resultCallbackCustom.launch(\n                        Intent(\n                            requireContext(),\n                            ConfigEditActivity::class.java\n                        ).apply {\n                            putExtra(\"key\", Key.SERVER_CUSTOM)\n                        })\n                }\n                true\n            }\n\n            R.id.action_create_shortcut -> {\n                val activity = requireActivity() as ProfileSettingsActivity<*>\n                val ent = activity.proxyEntity!!\n                val shortcut = ShortcutInfoCompat.Builder(activity, \"shortcut-profile-${ent.id}\")\n                    .setShortLabel(ent.displayName())\n                    .setLongLabel(ent.displayName())\n                    .setIcon(\n                        IconCompat.createWithResource(\n                            activity, R.drawable.ic_qu_shadowsocks_launcher\n                        )\n                    ).setIntent(Intent(\n                        context, QuickToggleShortcut::class.java\n                    ).apply {\n                        action = Intent.ACTION_MAIN\n                        putExtra(\"profile\", ent.id)\n                    }).build()\n                ShortcutManagerCompat.requestPinShortcut(activity, shortcut, null)\n            }\n\n            R.id.action_move -> {\n                val activity = requireActivity() as ProfileSettingsActivity<*>\n                val view = LinearLayout(context).apply {\n                    val ent = activity.proxyEntity!!\n                    orientation = LinearLayout.VERTICAL\n\n                    SagerDatabase.groupDao.allGroups()\n                        .filter { it.type == GroupType.BASIC && it.id != ent.groupId }\n                        .forEach { group ->\n                            LayoutGroupItemBinding.inflate(layoutInflater, this, true).apply {\n                                edit.isVisible = false\n                                options.isVisible = false\n                                groupName.text = group.displayName()\n                                groupUpdate.text = getString(R.string.move)\n                                groupUpdate.setOnClickListener {\n                                    runOnDefaultDispatcher {\n                                        val oldGroupId = ent.groupId\n                                        val newGroupId = group.id\n                                        ent.groupId = newGroupId\n                                        ProfileManager.updateProfile(ent)\n                                        GroupManager.postUpdate(oldGroupId) // reload\n                                        GroupManager.postUpdate(newGroupId)\n                                        DataStore.editingGroup = newGroupId // post switch animation\n                                        runOnMainDispatcher {\n                                            activity.finish()\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                }\n                val scrollView = ScrollView(context).apply {\n                    addView(view)\n                }\n                MaterialAlertDialogBuilder(activity).setView(scrollView).show()\n                true\n            }\n\n            else -> false\n        }\n\n        override fun onDisplayPreferenceDialog(preference: Preference) {\n            activity?.apply {\n                if (displayPreferenceDialog(preference)) return\n            }\n            super.onDisplayPreferenceDialog(preference)\n        }\n\n    }\n\n    object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {\n\n        override fun provideSummary(preference: EditTextPreference): CharSequence {\n            val text = preference.text\n            return if (text.isNullOrBlank()) {\n                preference.context.getString(androidx.preference.R.string.not_set)\n            } else {\n                \"\\u2022\".repeat(text.length)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/SSHSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.ssh.SSHBean\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass SSHSettingsActivity : ProfileSettingsActivity<SSHBean>() {\n\n    override fun createEntity() = SSHBean()\n\n    override fun SSHBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n        DataStore.serverUsername = username\n        DataStore.serverAuthType = authType\n        DataStore.serverPassword = password\n        DataStore.serverPrivateKey = privateKey\n        DataStore.serverPassword1 = privateKeyPassphrase\n        DataStore.serverCertificates = publicKey\n    }\n\n    override fun SSHBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n        username = DataStore.serverUsername\n        authType = DataStore.serverAuthType\n        when (authType) {\n            SSHBean.AUTH_TYPE_NONE -> {\n            }\n            SSHBean.AUTH_TYPE_PASSWORD -> {\n                password = DataStore.serverPassword\n            }\n            SSHBean.AUTH_TYPE_PRIVATE_KEY -> {\n                privateKey = DataStore.serverPrivateKey\n                privateKeyPassphrase = DataStore.serverPassword1\n            }\n        }\n        publicKey = DataStore.serverCertificates\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.ssh_preferences)\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        val password = findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        val privateKey = findPreference<EditTextPreference>(Key.SERVER_PRIVATE_KEY)!!\n        val privateKeyPassphrase = findPreference<EditTextPreference>(Key.SERVER_PASSWORD1)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        val authType = findPreference<SimpleMenuPreference>(Key.SERVER_AUTH_TYPE)!!\n        fun updateAuthType(type: Int = DataStore.serverAuthType) {\n            password.isVisible = type == SSHBean.AUTH_TYPE_PASSWORD\n            privateKey.isVisible = type == SSHBean.AUTH_TYPE_PRIVATE_KEY\n            privateKeyPassphrase.isVisible = type == SSHBean.AUTH_TYPE_PRIVATE_KEY\n        }\n        updateAuthType()\n        authType.setOnPreferenceChangeListener { _, newValue ->\n            updateAuthType((newValue as String).toInt())\n            true\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/ShadowsocksSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean\nimport moe.matsuri.nb4a.proxy.PreferenceBinding\nimport moe.matsuri.nb4a.proxy.PreferenceBindingManager\nimport moe.matsuri.nb4a.proxy.Type\n\nclass ShadowsocksSettingsActivity : ProfileSettingsActivity<ShadowsocksBean>() {\n\n    override fun createEntity() = ShadowsocksBean()\n\n    private val pbm = PreferenceBindingManager()\n    private val name = pbm.add(PreferenceBinding(Type.Text, \"name\"))\n    private val serverAddress = pbm.add(PreferenceBinding(Type.Text, \"serverAddress\"))\n    private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, \"serverPort\"))\n    private val password = pbm.add(PreferenceBinding(Type.Text, \"password\"))\n    private val method = pbm.add(PreferenceBinding(Type.Text, \"method\"))\n    private val pluginName =\n        pbm.add(PreferenceBinding(Type.Text, \"pluginName\").apply { disable = true })\n    private val pluginConfig =\n        pbm.add(PreferenceBinding(Type.Text, \"pluginConfig\").apply { disable = true })\n    private val sUoT = pbm.add(PreferenceBinding(Type.Bool, \"sUoT\"))\n\n    override fun ShadowsocksBean.init() {\n        pbm.writeToCacheAll(this)\n\n        DataStore.profileCacheStore.putString(\"pluginName\", plugin.substringBefore(\";\"))\n        DataStore.profileCacheStore.putString(\"pluginConfig\", plugin.substringAfter(\";\"))\n    }\n\n    override fun ShadowsocksBean.serialize() {\n        pbm.fromCacheAll(this)\n\n        val pn = pluginName.readStringFromCache()\n        val pc = pluginConfig.readStringFromCache()\n        plugin = if (pn.isNotBlank()) \"$pn;$pc\" else \"\"\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.shadowsocks_preferences)\n        pbm.setPreferenceFragment(this)\n\n        serverPort.preference.apply {\n            this as EditTextPreference\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        password.preference.apply {\n            this as EditTextPreference\n            summaryProvider = PasswordSummaryProvider\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/SocksSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.socks.SOCKSBean\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass SocksSettingsActivity : ProfileSettingsActivity<SOCKSBean>() {\n    override fun createEntity() = SOCKSBean()\n\n    override fun SOCKSBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n\n        DataStore.serverProtocolInt = protocol\n        DataStore.serverUsername = username\n        DataStore.serverPassword = password\n\n        DataStore.profileCacheStore.putBoolean(\"sUoT\", sUoT)\n    }\n\n    override fun SOCKSBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n\n        protocol = DataStore.serverProtocolInt\n        username = DataStore.serverUsername\n        password = DataStore.serverPassword\n\n        sUoT = DataStore.profileCacheStore.getBoolean(\"sUoT\")\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.socks_preferences)\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        val password = findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        val protocol = findPreference<SimpleMenuPreference>(Key.SERVER_PROTOCOL)!!\n\n        fun updateProtocol(version: Int) {\n            password.isVisible = version == SOCKSBean.PROTOCOL_SOCKS5\n        }\n\n        updateProtocol(DataStore.protocolVersion)\n        protocol.setOnPreferenceChangeListener { _, newValue ->\n            updateProtocol((newValue as String).toInt())\n            true\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/StandardV2RaySettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceCategory\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.http.HttpBean\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean\nimport io.nekohasekai.sagernet.fmt.v2ray.VMessBean\nimport moe.matsuri.nb4a.proxy.PreferenceBinding\nimport moe.matsuri.nb4a.proxy.PreferenceBindingManager\nimport moe.matsuri.nb4a.proxy.Type\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nabstract class StandardV2RaySettingsActivity : ProfileSettingsActivity<StandardV2RayBean>() {\n\n    var tmpBean: StandardV2RayBean? = null\n\n    private val pbm = PreferenceBindingManager()\n    private val name = pbm.add(PreferenceBinding(Type.Text, \"name\"))\n    private val serverAddress = pbm.add(PreferenceBinding(Type.Text, \"serverAddress\"))\n    private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, \"serverPort\"))\n    private val uuid = pbm.add(PreferenceBinding(Type.Text, \"uuid\"))\n    private val username = pbm.add(PreferenceBinding(Type.Text, \"username\"))\n    private val password = pbm.add(PreferenceBinding(Type.Text, \"password\"))\n    private val alterId = pbm.add(PreferenceBinding(Type.TextToInt, \"alterId\"))\n    private val encryption = pbm.add(PreferenceBinding(Type.Text, \"encryption\"))\n    private val type = pbm.add(PreferenceBinding(Type.Text, \"type\"))\n    private val host = pbm.add(PreferenceBinding(Type.Text, \"host\"))\n    private val path = pbm.add(PreferenceBinding(Type.Text, \"path\"))\n    private val packetEncoding = pbm.add(PreferenceBinding(Type.TextToInt, \"packetEncoding\"))\n    private val wsMaxEarlyData = pbm.add(PreferenceBinding(Type.TextToInt, \"wsMaxEarlyData\"))\n    private val earlyDataHeaderName = pbm.add(PreferenceBinding(Type.Text, \"earlyDataHeaderName\"))\n    private val security = pbm.add(PreferenceBinding(Type.Text, \"security\"))\n    private val sni = pbm.add(PreferenceBinding(Type.Text, \"sni\"))\n    private val alpn = pbm.add(PreferenceBinding(Type.Text, \"alpn\"))\n    private val certificates = pbm.add(PreferenceBinding(Type.Text, \"certificates\"))\n    private val allowInsecure = pbm.add(PreferenceBinding(Type.Bool, \"allowInsecure\"))\n    private val utlsFingerprint = pbm.add(PreferenceBinding(Type.Text, \"utlsFingerprint\"))\n    private val realityPubKey = pbm.add(PreferenceBinding(Type.Text, \"realityPubKey\"))\n    private val realityShortId = pbm.add(PreferenceBinding(Type.Text, \"realityShortId\"))\n\n    private val enableECH = pbm.add(PreferenceBinding(Type.Bool, \"enableECH\"))\n    private val echConfig = pbm.add(PreferenceBinding(Type.Text, \"echConfig\"))\n\n    private val enableMux = pbm.add(PreferenceBinding(Type.Bool, \"enableMux\"))\n    private val muxPadding = pbm.add(PreferenceBinding(Type.Bool, \"muxPadding\"))\n    private val muxType = pbm.add(PreferenceBinding(Type.TextToInt, \"muxType\"))\n    private val muxConcurrency = pbm.add(PreferenceBinding(Type.TextToInt, \"muxConcurrency\"))\n\n    override fun StandardV2RayBean.init() {\n        if (this is TrojanBean) {\n            this@StandardV2RaySettingsActivity.uuid.fieldName = \"password\"\n            this@StandardV2RaySettingsActivity.password.disable = true\n        }\n\n        tmpBean = this // copy bean\n        pbm.writeToCacheAll(this)\n    }\n\n    override fun StandardV2RayBean.serialize() {\n        pbm.fromCacheAll(this)\n    }\n\n    private lateinit var securityCategory: PreferenceCategory\n    private lateinit var tlsCamouflageCategory: PreferenceCategory\n    private lateinit var wsCategory: PreferenceCategory\n    private lateinit var echCategory: PreferenceCategory\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.standard_v2ray_preferences)\n        pbm.setPreferenceFragment(this)\n        securityCategory = findPreference(Key.SERVER_SECURITY_CATEGORY)!!\n        tlsCamouflageCategory = findPreference(Key.SERVER_TLS_CAMOUFLAGE_CATEGORY)!!\n        echCategory = findPreference(Key.SERVER_ECH_CATEORY)!!\n        wsCategory = findPreference(Key.SERVER_WS_CATEGORY)!!\n\n\n        // vmess/vless/http/trojan\n        val isHttp = tmpBean is HttpBean\n        val isVmess = tmpBean is VMessBean && tmpBean?.isVLESS == false\n        val isVless = tmpBean?.isVLESS == true\n\n        serverPort.preference.apply {\n            this as EditTextPreference\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n\n        alterId.preference.apply {\n            this as EditTextPreference\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n\n        uuid.preference.summaryProvider = PasswordSummaryProvider\n\n        type.preference.isVisible = !isHttp\n        uuid.preference.isVisible = !isHttp\n        packetEncoding.preference.isVisible = isVmess || isVless\n        alterId.preference.isVisible = isVmess\n        encryption.preference.isVisible = isVmess || isVless\n        username.preference.isVisible = isHttp\n        password.preference.isVisible = isHttp\n\n        if (tmpBean is TrojanBean) {\n            uuid.preference.title = resources.getString(R.string.password)\n        }\n\n        encryption.preference.apply {\n            this as SimpleMenuPreference\n            if (isVless) {\n                title = resources.getString(R.string.xtls_flow)\n                setIcon(R.drawable.ic_baseline_stream_24)\n                setEntries(R.array.xtls_flow_value)\n                setEntryValues(R.array.xtls_flow_value)\n            } else {\n                setEntries(R.array.vmess_encryption_value)\n                setEntryValues(R.array.vmess_encryption_value)\n            }\n        }\n\n        // menu with listener\n\n        type.preference.apply {\n            updateView(type.readStringFromCache())\n            this as SimpleMenuPreference\n            setOnPreferenceChangeListener { _, newValue ->\n                updateView(newValue as String)\n                true\n            }\n        }\n\n        security.preference.apply {\n            updateTls(security.readStringFromCache())\n            this as SimpleMenuPreference\n            setOnPreferenceChangeListener { _, newValue ->\n                updateTls(newValue as String)\n                true\n            }\n        }\n    }\n\n    private fun updateView(network: String) {\n        host.preference.isVisible = false\n        path.preference.isVisible = false\n        wsCategory.isVisible = false\n\n        when (network) {\n            \"tcp\" -> {\n                host.preference.setTitle(R.string.http_host)\n                path.preference.setTitle(R.string.http_path)\n            }\n\n            \"http\" -> {\n                host.preference.setTitle(R.string.http_host)\n                path.preference.setTitle(R.string.http_path)\n                host.preference.isVisible = true\n                path.preference.isVisible = true\n            }\n\n            \"ws\" -> {\n                host.preference.setTitle(R.string.ws_host)\n                path.preference.setTitle(R.string.ws_path)\n                host.preference.isVisible = true\n                path.preference.isVisible = true\n                wsCategory.isVisible = true\n            }\n\n            \"grpc\" -> {\n                path.preference.setTitle(R.string.grpc_service_name)\n                path.preference.isVisible = true\n            }\n\n            \"httpupgrade\" -> {\n                host.preference.setTitle(R.string.http_upgrade_host)\n                path.preference.setTitle(R.string.http_upgrade_path)\n                host.preference.isVisible = true\n                path.preference.isVisible = true\n            }\n        }\n    }\n\n    private fun updateTls(tls: String) {\n        val isTLS = \"tls\" in tls\n        securityCategory.isVisible = isTLS\n        tlsCamouflageCategory.isVisible = isTLS\n        echCategory.isVisible = isTLS\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanGoSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceCategory\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.trojan_go.TrojanGoBean\nimport io.nekohasekai.sagernet.ktx.app\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass TrojanGoSettingsActivity : ProfileSettingsActivity<TrojanGoBean>() {\n\n    override fun createEntity() = TrojanGoBean()\n\n    override fun TrojanGoBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n        DataStore.serverPassword = password\n        DataStore.serverSNI = sni\n        DataStore.serverAllowInsecure = allowInsecure\n        DataStore.serverNetwork = type\n        DataStore.serverHost = host\n        DataStore.serverPath = path\n        if (encryption.startsWith(\"ss;\")) {\n            DataStore.serverEncryption = \"ss\"\n            DataStore.serverMethod = encryption.substringAfter(\";\").substringBefore(\":\")\n            DataStore.serverPassword1 = encryption.substringAfter(\":\")\n        } else {\n            DataStore.serverEncryption = encryption\n        }\n    }\n\n    override fun TrojanGoBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n        password = DataStore.serverPassword\n        sni = DataStore.serverSNI\n        allowInsecure = DataStore.serverAllowInsecure\n        type = DataStore.serverNetwork\n        host = DataStore.serverHost\n        path = DataStore.serverPath\n        encryption = when (val security = DataStore.serverEncryption) {\n            \"ss\" -> {\n                \"ss;\" + DataStore.serverMethod + \":\" + DataStore.serverPassword1\n            }\n            else -> {\n                security\n            }\n        }\n    }\n\n    lateinit var network: SimpleMenuPreference\n    lateinit var encryprtion: SimpleMenuPreference\n    lateinit var wsCategory: PreferenceCategory\n    lateinit var ssCategory: PreferenceCategory\n    lateinit var method: SimpleMenuPreference\n\n    val trojanGoMethods = app.resources.getStringArray(R.array.trojan_go_methods)\n    val trojanGoNetworks = app.resources.getStringArray(R.array.trojan_go_networks_value)\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.trojan_go_preferences)\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD1)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n        wsCategory = findPreference(Key.SERVER_WS_CATEGORY)!!\n        ssCategory = findPreference(Key.SERVER_SS_CATEGORY)!!\n        method = findPreference(Key.SERVER_METHOD)!!\n\n        network = findPreference(Key.SERVER_NETWORK)!!\n\n        if (network.value !in trojanGoNetworks) {\n            network.value = trojanGoNetworks[0]\n        }\n\n        updateNetwork(network.value)\n        network.setOnPreferenceChangeListener { _, newValue ->\n            updateNetwork(newValue as String)\n            true\n        }\n        encryprtion = findPreference(Key.SERVER_ENCRYPTION)!!\n        updateEncryption(encryprtion.value)\n        encryprtion.setOnPreferenceChangeListener { _, newValue ->\n            updateEncryption(newValue as String)\n            true\n        }\n    }\n\n    fun updateNetwork(newNet: String) {\n        when (newNet) {\n            \"ws\" -> {\n                wsCategory.isVisible = true\n            }\n            else -> {\n                wsCategory.isVisible = false\n            }\n        }\n    }\n\n    fun updateEncryption(encryption: String) {\n        when (encryption) {\n            \"ss\" -> {\n                ssCategory.isVisible = true\n\n                if (method.value !in trojanGoMethods) {\n                    method.value = trojanGoMethods[0]\n                }\n            }\n            else -> {\n                ssCategory.isVisible = false\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/TrojanSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport io.nekohasekai.sagernet.fmt.trojan.TrojanBean\n\nclass TrojanSettingsActivity : StandardV2RaySettingsActivity() {\n\n    override fun createEntity() = TrojanBean()\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/TuicSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport androidx.preference.SwitchPreference\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.fmt.tuic.TuicBean\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\n\nclass TuicSettingsActivity : ProfileSettingsActivity<TuicBean>() {\n\n    override fun createEntity() = TuicBean().applyDefaultValues()\n\n    override fun TuicBean.init() {\n        DataStore.profileName = name\n        DataStore.serverAddress = serverAddress\n        DataStore.serverPort = serverPort\n        DataStore.serverUsername = uuid\n        DataStore.serverPassword = token\n        DataStore.serverALPN = alpn\n        DataStore.serverCertificates = caText\n        DataStore.serverUDPRelayMode = udpRelayMode\n        DataStore.serverCongestionController = congestionController\n        DataStore.serverDisableSNI = disableSNI\n        DataStore.serverSNI = sni\n        DataStore.serverReduceRTT = reduceRTT\n        DataStore.serverAllowInsecure = allowInsecure\n    }\n\n    override fun TuicBean.serialize() {\n        name = DataStore.profileName\n        serverAddress = DataStore.serverAddress\n        serverPort = DataStore.serverPort\n        uuid = DataStore.serverUsername\n        token = DataStore.serverPassword\n        alpn = DataStore.serverALPN\n        caText = DataStore.serverCertificates\n        udpRelayMode = DataStore.serverUDPRelayMode\n        congestionController = DataStore.serverCongestionController\n        disableSNI = DataStore.serverDisableSNI\n        sni = DataStore.serverSNI\n        reduceRTT = DataStore.serverReduceRTT\n        allowInsecure = DataStore.serverAllowInsecure\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.tuic_preferences)\n\n        val disableSNI = findPreference<SwitchPreference>(Key.SERVER_DISABLE_SNI)!!\n        val sni = findPreference<EditTextPreference>(Key.SERVER_SNI)!!\n        sni.isEnabled = !disableSNI.isChecked\n        disableSNI.setOnPreferenceChangeListener { _, newValue ->\n            sni.isEnabled = !(newValue as Boolean)\n            true\n        }\n\n        findPreference<EditTextPreference>(Key.SERVER_PASSWORD)!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/VMessSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport io.nekohasekai.sagernet.fmt.v2ray.VMessBean\n\nclass VMessSettingsActivity : StandardV2RaySettingsActivity() {\n\n    override fun createEntity() = VMessBean().apply {\n        if (intent?.getBooleanExtra(\"vless\", false) == true) {\n            alterId = -1\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/ui/profile/WireGuardSettingsActivity.kt",
    "content": "package io.nekohasekai.sagernet.ui.profile\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.fmt.wireguard.WireGuardBean\nimport moe.matsuri.nb4a.proxy.PreferenceBinding\nimport moe.matsuri.nb4a.proxy.PreferenceBindingManager\nimport moe.matsuri.nb4a.proxy.Type\n\nclass WireGuardSettingsActivity : ProfileSettingsActivity<WireGuardBean>() {\n\n    override fun createEntity() = WireGuardBean()\n\n    private val pbm = PreferenceBindingManager()\n    private val name = pbm.add(PreferenceBinding(Type.Text, \"name\"))\n    private val serverAddress = pbm.add(PreferenceBinding(Type.Text, \"serverAddress\"))\n    private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, \"serverPort\"))\n    private val localAddress = pbm.add(PreferenceBinding(Type.Text, \"localAddress\"))\n    private val privateKey = pbm.add(PreferenceBinding(Type.Text, \"privateKey\"))\n    private val peerPublicKey = pbm.add(PreferenceBinding(Type.Text, \"peerPublicKey\"))\n    private val peerPreSharedKey = pbm.add(PreferenceBinding(Type.Text, \"peerPreSharedKey\"))\n    private val mtu = pbm.add(PreferenceBinding(Type.TextToInt, \"mtu\"))\n    private val reserved = pbm.add(PreferenceBinding(Type.Text, \"reserved\"))\n\n    override fun WireGuardBean.init() {\n        pbm.writeToCacheAll(this)\n    }\n\n    override fun WireGuardBean.serialize() {\n        pbm.fromCacheAll(this)\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.wireguard_preferences)\n        pbm.setPreferenceFragment(this)\n\n        (serverPort.preference as EditTextPreference)\n            .setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        (privateKey.preference as EditTextPreference).summaryProvider = PasswordSummaryProvider\n        (mtu.preference as EditTextPreference).setOnBindEditTextListener(EditTextPreferenceModifiers.Number)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/Commandline.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport java.util.*\n\n/**\n * Commandline objects help handling command lines specifying processes to\n * execute.\n *\n * The class can be used to define a command line as nested elements or as a\n * helper to define a command line by an application.\n *\n *\n * `\n * <someelement><br></br>\n * &nbsp;&nbsp;<acommandline executable=\"/executable/to/run\"><br></br>\n * &nbsp;&nbsp;&nbsp;&nbsp;<argument value=\"argument 1\" /><br></br>\n * &nbsp;&nbsp;&nbsp;&nbsp;<argument line=\"argument_1 argument_2 argument_3\" /><br></br>\n * &nbsp;&nbsp;&nbsp;&nbsp;<argument value=\"argument 4\" /><br></br>\n * &nbsp;&nbsp;</acommandline><br></br>\n * </someelement><br></br>\n` *\n *\n * Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java\n *\n * Adds support for escape character '\\'.\n */\nobject Commandline {\n\n    /**\n     * Quote the parts of the given array in way that makes them\n     * usable as command line arguments.\n     * @param args the list of arguments to quote.\n     * @return empty string for null or no command, else every argument split\n     * by spaces and quoted by quoting rules.\n     */\n    fun toString(args: Iterable<String>?): String {\n        // empty path return empty string\n        args ?: return \"\"\n        // path containing one or more elements\n        val result = StringBuilder()\n        for (arg in args) {\n            if (result.isNotEmpty()) result.append(' ')\n            arg.indices.map { arg[it] }.forEach {\n                when (it) {\n                    ' ', '\\\\', '\"', '\\'' -> {\n                        result.append('\\\\')  // intentionally no break\n                        result.append(it)\n                    }\n                    else -> result.append(it)\n                }\n            }\n        }\n        return result.toString()\n    }\n\n    /**\n     * Quote the parts of the given array in way that makes them\n     * usable as command line arguments.\n     * @param args the list of arguments to quote.\n     * @return empty string for null or no command, else every argument split\n     * by spaces and quoted by quoting rules.\n     */\n    fun toString(args: Array<String>) =\n        toString(args.asIterable()) // thanks to Java, arrays aren't iterable\n\n    /**\n     * Crack a command line.\n     * @param toProcess the command line to process.\n     * @return the command line broken into strings.\n     * An empty or null toProcess parameter results in a zero sized array.\n     */\n    fun translateCommandline(toProcess: String?): Array<String> {\n        if (toProcess == null || toProcess.isEmpty()) {\n            //no command? no string\n            return arrayOf()\n        }\n        // parse with a simple finite state machine\n\n        val normal = 0\n        val inQuote = 1\n        val inDoubleQuote = 2\n        var state = normal\n        val tok = StringTokenizer(toProcess, \"\\\\\\\"\\' \", true)\n        val result = ArrayList<String>()\n        val current = StringBuilder()\n        var lastTokenHasBeenQuoted = false\n        var lastTokenIsSlash = false\n\n        while (tok.hasMoreTokens()) {\n            val nextTok = tok.nextToken()\n            when (state) {\n                inQuote -> if (\"\\'\" == nextTok) {\n                    lastTokenHasBeenQuoted = true\n                    state = normal\n                } else current.append(nextTok)\n                inDoubleQuote -> when (nextTok) {\n                    \"\\\"\" -> if (lastTokenIsSlash) {\n                        current.append(nextTok)\n                        lastTokenIsSlash = false\n                    } else {\n                        lastTokenHasBeenQuoted = true\n                        state = normal\n                    }\n                    \"\\\\\" -> lastTokenIsSlash = if (lastTokenIsSlash) {\n                        current.append(nextTok)\n                        false\n                    } else true\n                    else -> {\n                        if (lastTokenIsSlash) {\n                            current.append(\"\\\\\")   // unescaped\n                            lastTokenIsSlash = false\n                        }\n                        current.append(nextTok)\n                    }\n                }\n                else -> {\n                    when {\n                        lastTokenIsSlash -> {\n                            current.append(nextTok)\n                            lastTokenIsSlash = false\n                        }\n                        \"\\\\\" == nextTok -> lastTokenIsSlash = true\n                        \"\\'\" == nextTok -> state = inQuote\n                        \"\\\"\" == nextTok -> state = inDoubleQuote\n                        \" \" == nextTok -> if (lastTokenHasBeenQuoted || current.isNotEmpty()) {\n                            result.add(current.toString())\n                            current.setLength(0)\n                        }\n                        else -> current.append(nextTok)\n                    }\n                    lastTokenHasBeenQuoted = false\n                }\n            }\n        }\n        if (lastTokenHasBeenQuoted || current.isNotEmpty()) result.add(current.toString())\n        require(state != inQuote && state != inDoubleQuote) { \"unbalanced quotes in $toProcess\" }\n        require(!lastTokenIsSlash) { \"escape character following nothing in $toProcess\" }\n        return result.toTypedArray()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/CrashHandler.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Build\nimport android.util.Log\nimport com.jakewharton.processphoenix.ProcessPhoenix\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.preference.PublicDatabase\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ui.BlankActivity\nimport java.io.BufferedReader\nimport java.io.IOException\nimport java.io.InputStreamReader\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.regex.Pattern\n\nobject CrashHandler : Thread.UncaughtExceptionHandler {\n\n    @Suppress(\"UNNECESSARY_SAFE_CALL\")\n    override fun uncaughtException(thread: Thread, throwable: Throwable) {\n        // note: libc / go panic is in android log\n\n        try {\n            Log.e(thread.toString(), throwable.stackTraceToString())\n        } catch (e: Exception) {\n        }\n\n        try {\n            Logs.e(thread.toString())\n            Logs.e(throwable.stackTraceToString())\n        } catch (e: Exception) {\n        }\n\n        ProcessPhoenix.triggerRebirth(app, Intent(app, BlankActivity::class.java).apply {\n            putExtra(\"sendLog\", \"NB4A Crash\")\n        })\n    }\n\n    fun formatThrowable(throwable: Throwable): String {\n        var format = throwable.javaClass.name\n        val message = throwable.message\n        if (!message.isNullOrBlank()) {\n            format += \": $message\"\n        }\n        format += \"\\n\"\n\n        format += throwable.stackTrace.joinToString(\"\\n\") {\n            \"    at ${it.className}.${it.methodName}(${it.fileName}:${if (it.isNativeMethod) \"native\" else it.lineNumber})\"\n        }\n\n        val cause = throwable.cause\n        if (cause != null) {\n            format += \"\\n\\nCaused by: \" + formatThrowable(cause)\n        }\n\n        return format\n    }\n\n    fun buildReportHeader(): String {\n        var report = \"\"\n        report += \"NekoBox for Android ${SagerNet.appVersionNameForDisplay} (${BuildConfig.VERSION_CODE})\\n\"\n        report += \"Date: ${getCurrentMilliSecondUTCTimeStamp()}\\n\\n\"\n        report += \"OS_VERSION: ${getSystemPropertyWithAndroidAPI(\"os.version\")}\\n\"\n        report += \"SDK_INT: ${Build.VERSION.SDK_INT}\\n\"\n        report += if (\"REL\" == Build.VERSION.CODENAME) {\n            \"RELEASE: ${Build.VERSION.RELEASE}\"\n        } else {\n            \"CODENAME: ${Build.VERSION.CODENAME}\"\n        } + \"\\n\"\n        report += \"ID: ${Build.ID}\\n\"\n        report += \"DISPLAY: ${Build.DISPLAY}\\n\"\n        report += \"INCREMENTAL: ${Build.VERSION.INCREMENTAL}\\n\"\n\n        val systemProperties = getSystemProperties()\n\n        report += \"SECURITY_PATCH: ${systemProperties.getProperty(\"ro.build.version.security_patch\")}\\n\"\n        report += \"IS_DEBUGGABLE: ${systemProperties.getProperty(\"ro.debuggable\")}\\n\"\n        report += \"IS_EMULATOR: ${systemProperties.getProperty(\"ro.boot.qemu\")}\\n\"\n        report += \"IS_TREBLE_ENABLED: ${systemProperties.getProperty(\"ro.treble.enabled\")}\\n\"\n\n        report += \"TYPE: ${Build.TYPE}\\n\"\n        report += \"TAGS: ${Build.TAGS}\\n\\n\"\n\n        report += \"MANUFACTURER: ${Build.MANUFACTURER}\\n\"\n        report += \"BRAND: ${Build.BRAND}\\n\"\n        report += \"MODEL: ${Build.MODEL}\\n\"\n        report += \"PRODUCT: ${Build.PRODUCT}\\n\"\n        report += \"BOARD: ${Build.BOARD}\\n\"\n        report += \"HARDWARE: ${Build.HARDWARE}\\n\"\n        report += \"DEVICE: ${Build.DEVICE}\\n\"\n        report += \"SUPPORTED_ABIS: ${\n            Build.SUPPORTED_ABIS.filter { it.isNotBlank() }.joinToString(\", \")\n        }\\n\\n\"\n\n\n        try {\n            report += \"Settings: \\n\"\n            for (pair in PublicDatabase.kvPairDao.all()) {\n                report += \"\\n\"\n                report += pair.key + \": \" + pair.toString()\n            }\n        } catch (e: Exception) {\n            report += \"Export settings failed: \" + formatThrowable(e)\n        }\n\n        report += \"\\n\\n\"\n\n        return report\n    }\n\n    private fun getSystemProperties(): Properties {\n        val systemProperties = Properties()\n\n        // getprop commands returns values in the format `[key]: [value]`\n        // Regex matches string starting with a literal `[`,\n        // followed by one or more characters that do not match a closing square bracket as the key,\n        // followed by a literal `]: [`,\n        // followed by one or more characters as the value,\n        // followed by string ending with literal `]`\n        // multiline values will be ignored\n        val propertiesPattern = Pattern.compile(\"^\\\\[([^]]+)]: \\\\[(.+)]$\")\n        try {\n            val process = ProcessBuilder().command(\"/system/bin/getprop\")\n                .redirectErrorStream(true)\n                .start()\n            val inputStream = process.inputStream\n            val bufferedReader = BufferedReader(InputStreamReader(inputStream))\n            var line: String?\n            var key: String\n            var value: String\n            while (bufferedReader.readLine().also { line = it } != null) {\n                val matcher = propertiesPattern.matcher(line)\n                if (matcher.matches()) {\n                    key = matcher.group(1)\n                    value = matcher.group(2)\n                    if (key != null && value != null && !key.isEmpty() && !value.isEmpty()) systemProperties[key] =\n                        value\n                }\n            }\n            bufferedReader.close()\n            process.destroy()\n        } catch (e: IOException) {\n            Logs.e(\n                \"Failed to get run \\\"/system/bin/getprop\\\" to get system properties.\", e\n            )\n        }\n\n        //for (String key : systemProperties.stringPropertyNames()) {\n        //    Logger.logVerbose(key + \": \" +  systemProperties.get(key));\n        //}\n        return systemProperties\n    }\n\n    private fun getSystemPropertyWithAndroidAPI(property: String): String? {\n        return try {\n            System.getProperty(property)\n        } catch (e: Exception) {\n            Logs.e(\"Failed to get system property \\\"\" + property + \"\\\":\" + e.message)\n            null\n        }\n    }\n\n    @SuppressLint(\"SimpleDateFormat\")\n    private fun getCurrentMilliSecondUTCTimeStamp(): String {\n        val df = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss.SSS z\")\n        df.timeZone = TimeZone.getTimeZone(\"UTC\")\n        return df.format(Date())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/DefaultNetworkListener.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport android.annotation.TargetApi\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport android.net.NetworkRequest\nimport android.os.Build\nimport android.os.Handler\nimport android.os.Looper\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\nimport kotlinx.coroutines.CompletableDeferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.channels.actor\nimport kotlinx.coroutines.runBlocking\nimport java.net.UnknownHostException\n\nobject DefaultNetworkListener {\n    private sealed class NetworkMessage {\n        class Start(val key: Any, val listener: (Network?) -> Unit) : NetworkMessage()\n        class Get : NetworkMessage() {\n            val response = CompletableDeferred<Network>()\n        }\n\n        class Stop(val key: Any) : NetworkMessage()\n\n        class Put(val network: Network) : NetworkMessage()\n        class Update(val network: Network) : NetworkMessage()\n        class Lost(val network: Network) : NetworkMessage()\n    }\n\n    private val networkActor = GlobalScope.actor<NetworkMessage>(Dispatchers.Unconfined) {\n        val listeners = mutableMapOf<Any, (Network?) -> Unit>()\n        var network: Network? = null\n        val pendingRequests = arrayListOf<NetworkMessage.Get>()\n        for (message in channel) when (message) {\n            is NetworkMessage.Start -> {\n                if (listeners.isEmpty()) register()\n                listeners[message.key] = message.listener\n                if (network != null) message.listener(network)\n            }\n            is NetworkMessage.Get -> {\n                check(listeners.isNotEmpty()) { \"Getting network without any listeners is not supported\" }\n                if (network == null) pendingRequests += message else message.response.complete(\n                    network\n                )\n            }\n            is NetworkMessage.Stop -> if (listeners.isNotEmpty() && // was not empty\n                listeners.remove(message.key) != null && listeners.isEmpty()\n            ) {\n                network = null\n                unregister()\n            }\n\n            is NetworkMessage.Put -> {\n                network = message.network\n                pendingRequests.forEach { it.response.complete(message.network) }\n                pendingRequests.clear()\n                listeners.values.forEach { it(network) }\n            }\n            is NetworkMessage.Update -> if (network == message.network) listeners.values.forEach {\n                it(\n                    network\n                )\n            }\n            is NetworkMessage.Lost -> if (network == message.network) {\n                network = null\n                listeners.values.forEach { it(null) }\n            }\n        }\n    }\n\n    suspend fun start(key: Any, listener: (Network?) -> Unit) =\n        networkActor.send(NetworkMessage.Start(key, listener))\n\n    suspend fun get() = if (fallback) @TargetApi(23) {\n        SagerNet.connectivity.activeNetwork\n            ?: throw UnknownHostException() // failed to listen, return current if available\n    } else NetworkMessage.Get().run {\n        networkActor.send(this)\n        response.await()\n    }\n\n    suspend fun stop(key: Any) = networkActor.send(NetworkMessage.Stop(key))\n\n    // NB: this runs in ConnectivityThread, and this behavior cannot be changed until API 26\n    private object Callback : ConnectivityManager.NetworkCallback() {\n        override fun onAvailable(network: Network) =\n            runBlocking { networkActor.send(NetworkMessage.Put(network)) }\n\n        override fun onCapabilitiesChanged(\n            network: Network, networkCapabilities: NetworkCapabilities\n        ) { // it's a good idea to refresh capabilities\n            runBlocking { networkActor.send(NetworkMessage.Update(network)) }\n        }\n\n        override fun onLost(network: Network) =\n            runBlocking { networkActor.send(NetworkMessage.Lost(network)) }\n    }\n\n    private var fallback = false\n    private val request = NetworkRequest.Builder().apply {\n        addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)\n        addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)\n        if (Build.VERSION.SDK_INT == 23) {  // workarounds for OEM bugs\n            removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)\n            removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)\n        }\n    }.build()\n    private val mainHandler = Handler(Looper.getMainLooper())\n\n    /**\n     * Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:\n     * https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e\n     *\n     * This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that\n     * satisfies default network capabilities but only THE default network. Unfortunately, we need to have\n     * android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork.\n     *\n     * Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887\n     */\n    private fun register() {\n        try {\n            fallback = false\n            when (Build.VERSION.SDK_INT) {\n                in 31..Int.MAX_VALUE -> @TargetApi(31) {\n                    SagerNet.connectivity.registerBestMatchingNetworkCallback(\n                        request, Callback, mainHandler\n                    )\n                }\n                in 28 until 31 -> @TargetApi(28) {  // we want REQUEST here instead of LISTEN\n                    SagerNet.connectivity.requestNetwork(request, Callback, mainHandler)\n                }\n                in 26 until 28 -> @TargetApi(26) {\n                    SagerNet.connectivity.registerDefaultNetworkCallback(Callback, mainHandler)\n                }\n                in 24 until 26 -> @TargetApi(24) {\n                    SagerNet.connectivity.registerDefaultNetworkCallback(Callback)\n                }\n                else -> {\n                    SagerNet.connectivity.requestNetwork(request, Callback)\n                    // known bug on API 23: https://stackoverflow.com/a/33509180/2245107\n                }\n            }\n        } catch (e: Exception) {\n            Logs.w(e)\n            fallback = true\n        }\n    }\n\n    private fun unregister() = SagerNet.connectivity.unregisterNetworkCallback(Callback)\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/PackageCache.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport android.Manifest\nimport android.annotation.SuppressLint\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.listenForPackageChanges\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport moe.matsuri.nb4a.plugin.Plugins\nimport java.util.concurrent.atomic.AtomicBoolean\n\nobject PackageCache {\n\n    lateinit var installedPackages: Map<String, PackageInfo>\n    lateinit var installedPluginPackages: Map<String, PackageInfo>\n    lateinit var installedApps: Map<String, ApplicationInfo>\n    lateinit var packageMap: Map<String, Int>\n    val uidMap = HashMap<Int, HashSet<String>>()\n    val loaded = Mutex(true)\n    var registerd = AtomicBoolean(false)\n\n    // called from init (suspend)\n    fun register() {\n        if (registerd.getAndSet(true)) return\n        reload()\n        app.listenForPackageChanges(false) {\n            reload()\n            labelMap.clear()\n        }\n        loaded.unlock()\n    }\n\n    @SuppressLint(\"InlinedApi\")\n    fun reload() {\n        val rawPackageInfo = app.packageManager.getInstalledPackages(\n            PackageManager.MATCH_UNINSTALLED_PACKAGES\n                    or PackageManager.GET_PERMISSIONS\n                    or PackageManager.GET_PROVIDERS\n                    or PackageManager.GET_META_DATA\n        )\n\n        installedPackages = rawPackageInfo.filter {\n            when (it.packageName) {\n                \"android\" -> true\n                else -> it.requestedPermissions?.contains(Manifest.permission.INTERNET) == true\n            }\n        }.associateBy { it.packageName }\n\n        installedPluginPackages = rawPackageInfo.filter {\n            Plugins.isExe(it)\n        }.associateBy { it.packageName }\n\n        val installed = app.packageManager.getInstalledApplications(PackageManager.GET_META_DATA)\n        installedApps = installed.associateBy { it.packageName }\n        packageMap = installed.associate { it.packageName to it.uid }\n        uidMap.clear()\n        for (info in installed) {\n            val uid = info.uid\n            uidMap.getOrPut(uid) { HashSet() }.add(info.packageName)\n        }\n    }\n\n    operator fun get(uid: Int) = uidMap[uid]\n    operator fun get(packageName: String) = packageMap[packageName]\n\n    fun awaitLoadSync() {\n        if (::packageMap.isInitialized) {\n            return\n        }\n        if (!registerd.get()) {\n            register()\n            return\n        }\n        runBlocking {\n            loaded.withLock {\n                // just await\n            }\n        }\n    }\n\n    private val labelMap = mutableMapOf<String, String>()\n    fun loadLabel(packageName: String): String {\n        var label = labelMap[packageName]\n        if (label != null) return label\n        val info = installedApps[packageName] ?: return packageName\n        label = info.loadLabel(app.packageManager).toString()\n        labelMap[packageName] = label\n        return label\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/Subnet.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport io.nekohasekai.sagernet.ktx.parseNumericAddress\nimport java.net.InetAddress\nimport java.util.*\n\nclass Subnet(val address: InetAddress, val prefixSize: Int) : Comparable<Subnet> {\n    companion object {\n        fun fromString(value: String, lengthCheck: Int = -1): Subnet? {\n            val parts = value.split('/', limit = 2)\n            val addr = parts[0].parseNumericAddress() ?: return null\n            check(lengthCheck < 0 || addr.address.size == lengthCheck)\n            return if (parts.size == 2) try {\n                val prefixSize = parts[1].toInt()\n                if (prefixSize < 0 || prefixSize > addr.address.size shl 3) null else Subnet(addr,\n                    prefixSize)\n            } catch (_: NumberFormatException) {\n                null\n            } else Subnet(addr, addr.address.size shl 3)\n        }\n    }\n\n    private val addressLength get() = address.address.size shl 3\n\n    init {\n        require(prefixSize in 0..addressLength) { \"prefixSize $prefixSize not in 0..$addressLength\" }\n    }\n\n    class Immutable(private val a: ByteArray, private val prefixSize: Int = 0) {\n        companion object : Comparator<Immutable> {\n            override fun compare(a: Immutable, b: Immutable): Int {\n                check(a.a.size == b.a.size)\n                for (i in a.a.indices) {\n                    val result = a.a[i].compareTo(b.a[i])\n                    if (result != 0) return result\n                }\n                return 0\n            }\n        }\n\n        fun matches(b: Immutable) = matches(b.a)\n        fun matches(b: ByteArray): Boolean {\n            if (a.size != b.size) return false\n            var i = 0\n            while (i * 8 < prefixSize && i * 8 + 8 <= prefixSize) {\n                if (a[i] != b[i]) return false\n                ++i\n            }\n            return i * 8 == prefixSize || a[i] == (b[i].toInt() and -(1 shl i * 8 + 8 - prefixSize)).toByte()\n        }\n    }\n\n    fun toImmutable() = Immutable(address.address.also {\n        var i = prefixSize / 8\n        if (prefixSize % 8 > 0) {\n            it[i] = (it[i].toInt() and -(1 shl i * 8 + 8 - prefixSize)).toByte()\n            ++i\n        }\n        while (i < it.size) it[i++] = 0\n    }, prefixSize)\n\n    override fun toString(): String =\n        if (prefixSize == addressLength) address.hostAddress else address.hostAddress + '/' + prefixSize\n\n    private fun Byte.unsigned() = toInt() and 0xFF\n    override fun compareTo(other: Subnet): Int {\n        val addrThis = address.address\n        val addrThat = other.address.address\n        var result =\n            addrThis.size.compareTo(addrThat.size)                 // IPv4 address goes first\n        if (result != 0) return result\n        for (i in addrThis.indices) {\n            result = addrThis[i].unsigned()\n                .compareTo(addrThat[i].unsigned())   // undo sign extension of signed byte\n            if (result != 0) return result\n        }\n        return prefixSize.compareTo(other.prefixSize)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        val that = other as? Subnet\n        return address == that?.address && prefixSize == that.prefixSize\n    }\n\n    override fun hashCode(): Int = Objects.hash(address, prefixSize)\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt",
    "content": "package io.nekohasekai.sagernet.utils\n\nimport android.content.Context\nimport android.content.res.Configuration\nimport androidx.appcompat.app.AppCompatDelegate\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.app\n\nobject Theme {\n\n    const val RED = 1\n    const val PINK_SSR = 2\n    const val PINK = 3\n    const val PURPLE = 4\n    const val DEEP_PURPLE = 5\n    const val INDIGO = 6\n    const val BLUE = 7\n    const val LIGHT_BLUE = 8\n    const val CYAN = 9\n    const val TEAL = 10\n    const val GREEN = 11\n    const val LIGHT_GREEN = 12\n    const val LIME = 13\n    const val YELLOW = 14\n    const val AMBER = 15\n    const val ORANGE = 16\n    const val DEEP_ORANGE = 17\n    const val BROWN = 18\n    const val GREY = 19\n    const val BLUE_GREY = 20\n    const val BLACK = 21\n\n    private fun defaultTheme() = PINK_SSR\n\n    fun apply(context: Context) {\n        context.setTheme(getTheme())\n    }\n\n    fun applyDialog(context: Context) {\n        context.setTheme(getDialogTheme())\n    }\n\n    fun getTheme(): Int {\n        return getTheme(DataStore.appTheme)\n    }\n\n    fun getDialogTheme(): Int {\n        return getDialogTheme(DataStore.appTheme)\n    }\n\n    fun getTheme(theme: Int): Int {\n        return when (theme) {\n            RED -> R.style.Theme_SagerNet_Red\n            PINK -> R.style.Theme_SagerNet\n            PINK_SSR -> R.style.Theme_SagerNet_Pink_SSR\n            PURPLE -> R.style.Theme_SagerNet_Purple\n            DEEP_PURPLE -> R.style.Theme_SagerNet_DeepPurple\n            INDIGO -> R.style.Theme_SagerNet_Indigo\n            BLUE -> R.style.Theme_SagerNet_Blue\n            LIGHT_BLUE -> R.style.Theme_SagerNet_LightBlue\n            CYAN -> R.style.Theme_SagerNet_Cyan\n            TEAL -> R.style.Theme_SagerNet_Teal\n            GREEN -> R.style.Theme_SagerNet_Green\n            LIGHT_GREEN -> R.style.Theme_SagerNet_LightGreen\n            LIME -> R.style.Theme_SagerNet_Lime\n            YELLOW -> R.style.Theme_SagerNet_Yellow\n            AMBER -> R.style.Theme_SagerNet_Amber\n            ORANGE -> R.style.Theme_SagerNet_Orange\n            DEEP_ORANGE -> R.style.Theme_SagerNet_DeepOrange\n            BROWN -> R.style.Theme_SagerNet_Brown\n            GREY -> R.style.Theme_SagerNet_Grey\n            BLUE_GREY -> R.style.Theme_SagerNet_BlueGrey\n            BLACK -> R.style.Theme_SagerNet_Black\n            else -> getTheme(defaultTheme())\n        }\n    }\n\n    fun getDialogTheme(theme: Int): Int {\n        return when (theme) {\n            RED -> R.style.Theme_SagerNet_Dialog_Red\n            PINK -> R.style.Theme_SagerNet_Dialog\n            PINK_SSR -> R.style.Theme_SagerNet_Dialog_Pink_SSR\n            PURPLE -> R.style.Theme_SagerNet_Dialog_Purple\n            DEEP_PURPLE -> R.style.Theme_SagerNet_Dialog_DeepPurple\n            INDIGO -> R.style.Theme_SagerNet_Dialog_Indigo\n            BLUE -> R.style.Theme_SagerNet_Dialog_Blue\n            LIGHT_BLUE -> R.style.Theme_SagerNet_Dialog_LightBlue\n            CYAN -> R.style.Theme_SagerNet_Dialog_Cyan\n            TEAL -> R.style.Theme_SagerNet_Dialog_Teal\n            GREEN -> R.style.Theme_SagerNet_Dialog_Green\n            LIGHT_GREEN -> R.style.Theme_SagerNet_Dialog_LightGreen\n            LIME -> R.style.Theme_SagerNet_Dialog_Lime\n            YELLOW -> R.style.Theme_SagerNet_Dialog_Yellow\n            AMBER -> R.style.Theme_SagerNet_Dialog_Amber\n            ORANGE -> R.style.Theme_SagerNet_Dialog_Orange\n            DEEP_ORANGE -> R.style.Theme_SagerNet_Dialog_DeepOrange\n            BROWN -> R.style.Theme_SagerNet_Dialog_Brown\n            GREY -> R.style.Theme_SagerNet_Dialog_Grey\n            BLUE_GREY -> R.style.Theme_SagerNet_Dialog_BlueGrey\n            BLACK -> R.style.Theme_SagerNet_Dialog_Black\n            else -> getDialogTheme(defaultTheme())\n        }\n    }\n\n    var currentNightMode = -1\n    fun getNightMode(): Int {\n        if (currentNightMode == -1) {\n            currentNightMode = DataStore.nightTheme\n        }\n        return getNightMode(currentNightMode)\n    }\n\n    fun getNightMode(mode: Int): Int {\n        return when (mode) {\n            0 -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM\n            1 -> AppCompatDelegate.MODE_NIGHT_YES\n            2 -> AppCompatDelegate.MODE_NIGHT_NO\n            else -> AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY\n        }\n    }\n\n    fun usingNightMode(): Boolean {\n        return when (DataStore.nightTheme) {\n            1 -> true\n            2 -> false\n            else -> (app.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES\n        }\n    }\n\n    fun applyNightTheme() {\n        AppCompatDelegate.setDefaultNightMode(getNightMode())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/AppListPreference.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.preference.Preference\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.utils.PackageCache\n\nclass AppListPreference : Preference {\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(\n        context, attrs, defStyle\n    )\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes)\n\n    override fun getSummary(): CharSequence {\n        val packages = DataStore.routePackages.split(\"\\n\").filter { it.isNotBlank() }.map {\n            PackageCache.installedPackages[it]?.applicationInfo?.loadLabel(app.packageManager)\n                ?: PackageCache.installedPluginPackages[it]?.applicationInfo?.loadLabel(app.packageManager)\n                ?: it\n        }\n        if (packages.isEmpty()) {\n            return context.getString(androidx.preference.R.string.not_set)\n        }\n        val count = packages.size\n        if (count <= 5) return packages.joinToString(\"\\n\")\n        return context.getString(R.string.apps_message, count)\n    }\n\n    fun postUpdate() {\n        notifyChanged()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/AutoCollapseTextView.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Rect\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.appcompat.widget.AppCompatTextView\nimport androidx.core.view.isGone\n\nclass AutoCollapseTextView @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0,\n) :\n    AppCompatTextView(context, attrs, defStyleAttr) {\n    override fun onTextChanged(\n        text: CharSequence?,\n        start: Int,\n        lengthBefore: Int,\n        lengthAfter: Int,\n    ) {\n        super.onTextChanged(text, start, lengthBefore, lengthAfter)\n        isGone = text.isNullOrEmpty()\n    }\n\n    // #1874\n    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) =\n        try {\n            super.onFocusChanged(focused, direction, previouslyFocusedRect)\n        } catch (e: IndexOutOfBoundsException) {\n        }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent?) = try {\n        super.onTouchEvent(event)\n    } catch (e: IndexOutOfBoundsException) {\n        false\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/FabProgressBehavior.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport com.google.android.material.progressindicator.CircularProgressIndicator\n\nclass FabProgressBehavior(context: Context, attrs: AttributeSet?) :\n    CoordinatorLayout.Behavior<CircularProgressIndicator>(context, attrs) {\n    override fun layoutDependsOn(\n        parent: CoordinatorLayout,\n        child: CircularProgressIndicator,\n        dependency: View,\n    ): Boolean {\n        return dependency.id == (child.layoutParams as CoordinatorLayout.LayoutParams).anchorId\n    }\n\n    override fun onLayoutChild(\n        parent: CoordinatorLayout, child: CircularProgressIndicator,\n        layoutDirection: Int,\n    ): Boolean {\n        val size = parent.getDependencies(child).single().measuredHeight + child.trackThickness\n        return if (child.indicatorSize != size) {\n            child.indicatorSize = size\n            true\n        } else false\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/GroupPreference.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass GroupPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle\n) : SimpleMenuPreference(context, attrs, defStyle, 0) {\n\n    init {\n        val groups = SagerDatabase.groupDao.allGroups()\n\n        entries = groups.map { it.displayName() }.toTypedArray()\n        entryValues = groups.map { \"${it.id}\" }.toTypedArray()\n    }\n\n    override fun getSummary(): CharSequence? {\n        if (!value.isNullOrBlank() && value != \"0\") {\n            return SagerDatabase.groupDao.getById(value.toLong())?.displayName()\n                ?: super.getSummary()\n        }\n        return super.getSummary()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/LinkOrContentPreference.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.AttributeSet\nimport androidx.core.content.res.TypedArrayUtils\nimport androidx.core.widget.addTextChangedListener\nimport androidx.preference.EditTextPreference\nimport com.google.android.material.textfield.TextInputLayout\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport okhttp3.HttpUrl.Companion.toHttpUrl\n\nclass LinkOrContentPreference\n@JvmOverloads\nconstructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = TypedArrayUtils.getAttr(\n        context, R.attr.editTextPreferenceStyle,\n        android.R.attr.editTextPreferenceStyle\n    ),\n    defStyleRes: Int = 0\n) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) {\n\n    init {\n        dialogLayoutResource = R.layout.layout_urltest_preference_dialog\n\n        setOnBindEditTextListener {\n            val linkLayout = it.rootView.findViewById<TextInputLayout>(R.id.input_layout)\n            fun validate() {\n                val link = it.text\n                if (link.isBlank()) {\n                    linkLayout.isErrorEnabled = false\n                    return\n                }\n\n                try {\n                    if (Uri.parse(link.toString()).scheme == \"content\") {\n                        linkLayout.isErrorEnabled = false\n                        return\n                    }\n                    val url = link.toString().toHttpUrl()\n                    if (\"http\".equals(url.scheme, true)) {\n                        linkLayout.error = app.getString(R.string.cleartext_http_warning)\n                        linkLayout.isErrorEnabled = true\n                    } else {\n                        linkLayout.isErrorEnabled = false\n                    }\n                    if (link.contains(\"\\n\")) {\n                        linkLayout.error = \"Unexpected new line\"\n                    }\n                } catch (e: Exception) {\n                    linkLayout.error = e.readableMessage\n                    linkLayout.isErrorEnabled = true\n                }\n\n            }\n            validate()\n            it.addTextChangedListener {\n                validate()\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/OutboundPreference.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.ProfileManager\nimport moe.matsuri.nb4a.ui.SimpleMenuPreference\n\nclass OutboundPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle\n) : SimpleMenuPreference(context, attrs, defStyle, 0) {\n\n    init {\n        setEntries(R.array.outbound_entry)\n        setEntryValues(R.array.outbound_value)\n    }\n\n    override fun getSummary(): CharSequence? {\n        if (value == \"3\") {\n            val routeOutbound = DataStore.profileCacheStore.getLong(key + \"Long\") ?: 0\n            if (routeOutbound > 0) {\n                ProfileManager.getProfile(routeOutbound)?.displayName()?.let {\n                    return it\n                }\n            }\n        }\n        return super.getSummary()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/QRCodeDialog.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.graphics.Bitmap\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.util.DisplayMetrics\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.DialogFragment\nimport com.google.zxing.BarcodeFormat\nimport com.google.zxing.EncodeHintType\nimport com.google.zxing.MultiFormatWriter\nimport com.google.zxing.WriterException\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ui.MainActivity\nimport java.nio.charset.StandardCharsets\nimport kotlin.math.roundToInt\n\nclass QRCodeDialog() : DialogFragment() {\n\n    companion object {\n        private const val KEY_URL = \"io.nekohasekai.sagernet.QRCodeDialog.KEY_URL\"\n        private const val KEY_NAME = \"io.nekohasekai.sagernet.QRCodeDialog.KEY_NAME\"\n        private val iso88591 = StandardCharsets.ISO_8859_1.newEncoder()\n    }\n\n    constructor(url: String, displayName: String) : this() {\n        arguments = bundleOf(\n            Pair(KEY_URL, url), Pair(KEY_NAME, displayName)\n        )\n    }\n\n    /**\n     * Based on:\n     * https://android.googlesource.com/platform/\n    packages/apps/Settings/+/0d706f0/src/com/android/settings/wifi/qrcode/QrCodeGenerator.java\n     * https://android.googlesource.com/platform/\n    packages/apps/Settings/+/8a9ccfd/src/com/android/settings/wifi/dpp/WifiDppQrCodeGeneratorFragment.java#153\n     */\n    override fun onCreateView(\n        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?\n    ) = try {\n        // get display size\n        var pixelMin = 0\n\n        try {\n            val displayMetrics: DisplayMetrics = requireContext().resources.displayMetrics\n            val height: Int = displayMetrics.heightPixels\n            val width: Int = displayMetrics.widthPixels\n            pixelMin = if (height > width) width else height\n            pixelMin = (pixelMin * 0.8).roundToInt()\n        } catch (e: Exception) {\n        }\n\n        val size = if (pixelMin > 0) pixelMin else resources.getDimensionPixelSize(R.dimen.qrcode_size)\n\n        // draw QR Code\n        val url = arguments?.getString(KEY_URL)!!\n        val displayName = arguments?.getString(KEY_NAME)!!\n\n        val hints = mutableMapOf<EncodeHintType, Any>()\n        if (!iso88591.canEncode(url)) hints[EncodeHintType.CHARACTER_SET] = StandardCharsets.UTF_8.name()\n        val qrBits = MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, size, size, hints)\n        LinearLayout(context).apply {\n            // Layout\n            orientation = LinearLayout.VERTICAL\n            gravity = Gravity.CENTER\n\n            // QR Code Image View\n            addView(ImageView(context).apply {\n                layoutParams = ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT\n                )\n                setImageBitmap(Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565).apply {\n                    for (x in 0 until size) for (y in 0 until size) {\n                        setPixel(x, y, if (qrBits.get(x, y)) Color.BLACK else Color.WHITE)\n                    }\n                })\n            })\n\n            // Text View\n            addView(TextView(context).apply {\n                gravity = Gravity.CENTER\n                layoutParams = ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT\n                )\n                text = displayName\n            })\n        }\n    } catch (e: WriterException) {\n        Logs.w(e)\n        (activity as MainActivity).snackbar(e.readableMessage).show()\n        dismiss()\n        null\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.os.Build\nimport android.util.AttributeSet\nimport android.view.PointerIcon\nimport android.view.View\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.dynamicanimation.animation.DynamicAnimation\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.lifecycleScope\nimport androidx.vectordrawable.graphics.drawable.Animatable2Compat\nimport androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat\nimport com.google.android.material.floatingactionbutton.FloatingActionButton\nimport com.google.android.material.progressindicator.BaseProgressIndicator\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.bg.BaseService\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport java.util.*\n\nclass ServiceButton @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) :\n    FloatingActionButton(context, attrs, defStyleAttr), DynamicAnimation.OnAnimationEndListener {\n\n    private val callback = object : Animatable2Compat.AnimationCallback() {\n        override fun onAnimationEnd(drawable: Drawable) {\n            super.onAnimationEnd(drawable)\n            var next = animationQueue.peek() ?: return\n            if (next.icon.current == drawable) {\n                animationQueue.pop()\n                next = animationQueue.peek() ?: return\n            }\n            next.start()\n        }\n    }\n\n    private inner class AnimatedState(\n        @DrawableRes resId: Int,\n        private val onStart: BaseProgressIndicator<*>.() -> Unit = { hideProgress() }\n    ) {\n        val icon: AnimatedVectorDrawableCompat =\n            AnimatedVectorDrawableCompat.create(context, resId)!!.apply {\n                registerAnimationCallback(this@ServiceButton.callback)\n            }\n\n        fun start() {\n            setImageDrawable(icon)\n            icon.start()\n            progress.onStart()\n        }\n\n        fun stop() = icon.stop()\n    }\n\n    private val iconStopped by lazy { AnimatedState(R.drawable.ic_service_stopped) }\n    private val iconConnecting by lazy {\n        AnimatedState(R.drawable.ic_service_connecting) {\n            hideProgress()\n            delayedAnimation = (context as LifecycleOwner).lifecycleScope.launchWhenStarted {\n                delay(context.resources.getInteger(android.R.integer.config_mediumAnimTime) + 1000L)\n                isIndeterminate = true\n                show()\n            }\n        }\n    }\n    private val iconConnected by lazy {\n        AnimatedState(R.drawable.ic_service_connected) {\n            delayedAnimation?.cancel()\n            setProgressCompat(1, true)\n        }\n    }\n    private val iconStopping by lazy { AnimatedState(R.drawable.ic_service_stopping) }\n    private val animationQueue = ArrayDeque<AnimatedState>()\n\n    private var checked = false\n    private var delayedAnimation: Job? = null\n    private lateinit var progress: BaseProgressIndicator<*>\n    fun initProgress(progress: BaseProgressIndicator<*>) {\n        this.progress = progress\n        progress.progressDrawable?.addSpringAnimationEndListener(this)\n    }\n\n    override fun onAnimationEnd(\n        animation: DynamicAnimation<out DynamicAnimation<*>>?, canceled: Boolean, value: Float,\n        velocity: Float\n    ) {\n        if (!canceled) progress.hide()\n    }\n\n    private fun hideProgress() {\n        delayedAnimation?.cancel()\n        progress.hide()\n    }\n\n    override fun onCreateDrawableState(extraSpace: Int): IntArray {\n        val drawableState = super.onCreateDrawableState(extraSpace + 1)\n        if (checked) View.mergeDrawableStates(\n            drawableState,\n            intArrayOf(android.R.attr.state_checked)\n        )\n        return drawableState\n    }\n\n    fun changeState(state: BaseService.State, previousState: BaseService.State, animate: Boolean) {\n        when (state) {\n            BaseService.State.Connecting -> changeState(iconConnecting, animate)\n            BaseService.State.Connected -> changeState(iconConnected, animate)\n            BaseService.State.Stopping -> {\n                changeState(iconStopping, animate && previousState == BaseService.State.Connected)\n            }\n            else -> changeState(iconStopped, animate)\n        }\n        checked = state == BaseService.State.Connected\n        refreshDrawableState()\n        val description = context.getText(if (state.canStop) R.string.stop else R.string.connect)\n        contentDescription = description\n        TooltipCompat.setTooltipText(this, description)\n        val enabled = state.canStop || state == BaseService.State.Stopped\n        isEnabled = enabled\n        if (Build.VERSION.SDK_INT >= 24) pointerIcon = PointerIcon.getSystemIcon(\n            context,\n            if (enabled) PointerIcon.TYPE_HAND else PointerIcon.TYPE_WAIT\n        )\n    }\n\n    private fun changeState(icon: AnimatedState, animate: Boolean) {\n        fun counters(a: AnimatedState, b: AnimatedState): Boolean =\n            a == iconStopped && b == iconConnecting ||\n                    a == iconConnecting && b == iconStopped ||\n                    a == iconConnected && b == iconStopping ||\n                    a == iconStopping && b == iconConnected\n        if (animate) {\n            if (animationQueue.size < 2 || !counters(animationQueue.last, icon)) {\n                animationQueue.add(icon)\n                if (animationQueue.size == 1) icon.start()\n            } else animationQueue.removeLast()\n        } else {\n            animationQueue.peekFirst()?.stop()\n            animationQueue.clear()\n            icon.start()    // force ensureAnimatorSet to be called so that stop() will work\n            icon.stop()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/StatsBar.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.text.format.Formatter\nimport android.util.AttributeSet\nimport android.view.View\nimport android.widget.TextView\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.whenStarted\nimport com.google.android.material.bottomappbar.BottomAppBar\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.bg.BaseService\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.*\nimport io.nekohasekai.sagernet.ui.MainActivity\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\nclass StatsBar @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null,\n    defStyleAttr: Int = R.attr.bottomAppBarStyle,\n) : BottomAppBar(context, attrs, defStyleAttr) {\n    private lateinit var statusText: TextView\n    private lateinit var txText: TextView\n    private lateinit var rxText: TextView\n    private lateinit var behavior: YourBehavior\n\n    var allowShow = true\n\n    override fun getBehavior(): YourBehavior {\n        if (!this::behavior.isInitialized) behavior = YourBehavior { allowShow }\n        return behavior\n    }\n\n    class YourBehavior(val getAllowShow: () -> Boolean) : Behavior() {\n\n        override fun onNestedScroll(\n            coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View,\n            dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,\n            type: Int, consumed: IntArray,\n        ) {\n            super.onNestedScroll(\n                coordinatorLayout,\n                child,\n                target,\n                dxConsumed,\n                dyConsumed + dyUnconsumed,\n                dxUnconsumed,\n                0,\n                type,\n                consumed\n            )\n        }\n\n        override fun slideUp(child: BottomAppBar) {\n            if (!getAllowShow()) return\n            super.slideUp(child)\n        }\n\n        override fun slideDown(child: BottomAppBar) {\n            if (!getAllowShow()) return\n            super.slideDown(child)\n        }\n    }\n\n\n    override fun setOnClickListener(l: OnClickListener?) {\n        statusText = findViewById(R.id.status)\n        txText = findViewById(R.id.tx)\n        rxText = findViewById(R.id.rx)\n        super.setOnClickListener(l)\n    }\n\n    private fun setStatus(text: CharSequence) {\n        statusText.text = text\n        TooltipCompat.setTooltipText(this, text)\n    }\n\n    fun changeState(state: BaseService.State) {\n        val activity = context as MainActivity\n        fun postWhenStarted(what: () -> Unit) = activity.lifecycleScope.launch(Dispatchers.Main) {\n            delay(100L)\n            activity.whenStarted { what() }\n        }\n        if ((state == BaseService.State.Connected).also { hideOnScroll = it }) {\n            postWhenStarted {\n                if (allowShow) performShow()\n                setStatus(app.getText(R.string.vpn_connected))\n            }\n        } else {\n            postWhenStarted {\n                performHide()\n            }\n            updateSpeed(0, 0)\n            setStatus(\n                context.getText(\n                    when (state) {\n                        BaseService.State.Connecting -> R.string.connecting\n                        BaseService.State.Stopping -> R.string.stopping\n                        else -> R.string.not_connected\n                    }\n                )\n            )\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    fun updateSpeed(txRate: Long, rxRate: Long) {\n        txText.text = \"▲  ${\n            context.getString(\n                R.string.speed, Formatter.formatFileSize(context, txRate)\n            )\n        }\"\n        rxText.text = \"▼  ${\n            context.getString(\n                R.string.speed, Formatter.formatFileSize(context, rxRate)\n            )\n        }\"\n    }\n\n    fun testConnection() {\n        val activity = context as MainActivity\n        isEnabled = false\n        setStatus(app.getText(R.string.connection_test_testing))\n        runOnDefaultDispatcher {\n            try {\n                val elapsed = activity.urlTest()\n                onMainDispatcher {\n                    isEnabled = true\n                    setStatus(\n                        app.getString(\n                            if (DataStore.connectionTestURL.startsWith(\"https://\")) {\n                                R.string.connection_test_available\n                            } else {\n                                R.string.connection_test_available_http\n                            }, elapsed\n                        )\n                    )\n                }\n\n            } catch (e: Exception) {\n                Logs.w(e.toString())\n                onMainDispatcher {\n                    isEnabled = true\n                    setStatus(app.getText(R.string.connection_test_testing))\n\n                    activity.snackbar(\n                        app.getString(\n                            R.string.connection_test_error, e.readableMessage\n                        )\n                    ).show()\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/UndoSnackbarManager.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport com.google.android.material.snackbar.Snackbar\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ui.ThemedActivity\n\n/**\n * @param activity ThemedActivity.\n * //@param view The view to find a parent from.\n * @param undo Callback for undoing removals.\n * @param commit Callback for committing removals.\n * @tparam T Item type.\n */\nclass UndoSnackbarManager<in T>(\n    private val activity: ThemedActivity,\n    private val callback: Interface<T>,\n) {\n\n    interface Interface<in T> {\n        fun undo(actions: List<Pair<Int, T>>)\n        fun commit(actions: List<Pair<Int, T>>)\n    }\n\n    private val recycleBin = ArrayList<Pair<Int, T>>()\n    private val removedCallback = object : Snackbar.Callback() {\n        override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {\n            if (last === transientBottomBar && event != DISMISS_EVENT_ACTION) {\n                callback.commit(recycleBin)\n                recycleBin.clear()\n                last = null\n            }\n        }\n    }\n\n    private var last: Snackbar? = null\n\n    fun remove(items: Collection<Pair<Int, T>>) {\n        recycleBin.addAll(items)\n        val count = recycleBin.size\n        activity.snackbar(activity.resources.getQuantityString(R.plurals.removed, count, count))\n            .apply {\n                addCallback(removedCallback)\n                setAction(R.string.undo) {\n                    callback.undo(recycleBin.reversed())\n                    recycleBin.clear()\n                }\n                last = this\n                show()\n            }\n    }\n\n    fun remove(vararg items: Pair<Int, T>) = remove(items.toList())\n\n    fun flush() = last?.dismiss()\n}\n"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/UserAgentPreference.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.core.content.res.TypedArrayUtils\nimport androidx.preference.EditTextPreference\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.USER_AGENT\n\nclass UserAgentPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = TypedArrayUtils.getAttr(\n        context, R.attr.editTextPreferenceStyle, android.R.attr.editTextPreferenceStyle\n    )\n) : EditTextPreference(context, attrs, defStyle) {\n\n    public override fun notifyChanged() {\n        super.notifyChanged()\n    }\n\n    override fun getSummary(): CharSequence? {\n        if (text.isNullOrBlank()) {\n            return USER_AGENT\n        }\n        return super.getSummary()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/nekohasekai/sagernet/widget/WindowInsetsListeners.kt",
    "content": "package io.nekohasekai.sagernet.widget\n\nimport android.view.View\nimport androidx.core.view.OnApplyWindowInsetsListener\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.updatePadding\n\nobject ListListener : OnApplyWindowInsetsListener {\n    override fun onApplyWindowInsets(view: View, insets: WindowInsetsCompat) = insets.apply {\n        view.updatePadding(bottom = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/NativeInterface.kt",
    "content": "package moe.matsuri.nb4a\n\nimport android.content.Context\nimport android.net.ConnectivityManager\nimport android.net.wifi.WifiManager\nimport android.os.Build\nimport android.os.Build.VERSION_CODES\nimport androidx.annotation.RequiresApi\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.bg.ServiceNotification\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.SagerDatabase\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.runOnDefaultDispatcher\nimport io.nekohasekai.sagernet.utils.PackageCache\nimport libcore.BoxPlatformInterface\nimport libcore.Libcore\nimport libcore.NB4AInterface\nimport java.net.InetSocketAddress\n\nclass NativeInterface : BoxPlatformInterface, NB4AInterface {\n\n    //  libbox interface\n\n    override fun autoDetectInterfaceControl(fd: Int) {\n        DataStore.vpnService?.protect(fd)\n    }\n\n    override fun openTun(singTunOptionsJson: String, tunPlatformOptionsJson: String): Long {\n        if (DataStore.vpnService == null) {\n            throw Exception(\"no VpnService\")\n        }\n        return DataStore.vpnService!!.startVpn(singTunOptionsJson, tunPlatformOptionsJson).toLong()\n    }\n\n    override fun useProcFS(): Boolean {\n        return Build.VERSION.SDK_INT < Build.VERSION_CODES.Q\n    }\n\n    @RequiresApi(Build.VERSION_CODES.Q)\n    override fun findConnectionOwner(\n        ipProto: Int, srcIp: String, srcPort: Int, destIp: String, destPort: Int\n    ): Int {\n        return SagerNet.connectivity.getConnectionOwnerUid(\n            ipProto, InetSocketAddress(srcIp, srcPort), InetSocketAddress(destIp, destPort)\n        )\n    }\n\n    override fun packageNameByUid(uid: Int): String {\n        PackageCache.awaitLoadSync()\n\n        if (uid <= 1000L) {\n            return \"android\"\n        }\n\n        val packageNames = PackageCache.uidMap[uid]\n        if (!packageNames.isNullOrEmpty()) for (packageName in packageNames) {\n            return packageName\n        }\n\n        error(\"unknown uid $uid\")\n    }\n\n    override fun uidByPackageName(packageName: String): Int {\n        PackageCache.awaitLoadSync()\n        return PackageCache[packageName] ?: 0\n    }\n\n    // TODO: 'getter for connectionInfo: WifiInfo!' is deprecated\n    override fun wifiState(): String {\n        val wifiManager =\n            app.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager\n        val connectionInfo = wifiManager.connectionInfo\n        return \"${connectionInfo.ssid},${connectionInfo.bssid}\"\n    }\n\n    // nb4a interface\n\n    override fun useOfficialAssets(): Boolean {\n        return DataStore.rulesProvider == 0\n    }\n\n    override fun selector_OnProxySelected(selectorTag: String, tag: String) {\n        if (selectorTag != \"proxy\") {\n            Logs.d(\"other selector: $selectorTag\")\n            return\n        }\n        Libcore.resetAllConnections(true)\n        DataStore.baseService?.apply {\n            runOnDefaultDispatcher {\n                val id = data.proxy!!.config.profileTagMap\n                    .filterValues { it == tag }.keys.firstOrNull() ?: -1\n                val ent = SagerDatabase.proxyDao.getById(id) ?: return@runOnDefaultDispatcher\n                // traffic & title\n                data.proxy?.apply {\n                    looper?.selectMain(id)\n                    displayProfileName = ServiceNotification.genTitle(ent)\n                    data.notification?.postNotificationTitle(displayProfileName)\n                }\n                // post binder\n                data.binder.broadcast { b ->\n                    b.cbSelectorUpdate(id)\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/Protocols.kt",
    "content": "package moe.matsuri.nb4a\n\nimport android.content.Context\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.ProxyEntity.Companion.TYPE_NEKO\nimport io.nekohasekai.sagernet.fmt.AbstractBean\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.getColorAttr\nimport moe.matsuri.nb4a.proxy.config.ConfigBean\n\n// Settings for all protocols, built-in or plugin\nobject Protocols {\n\n    // Deduplication\n\n    class Deduplication(\n        val bean: AbstractBean, val type: String\n    ) {\n\n        fun hash(): String {\n            if (bean is ConfigBean) {\n                return bean.config\n            }\n            return bean.serverAddress + bean.serverPort + type\n        }\n\n        override fun hashCode(): Int {\n            return hash().toByteArray().contentHashCode()\n        }\n\n        override fun equals(other: Any?): Boolean {\n            if (this === other) return true\n            if (javaClass != other?.javaClass) return false\n\n            other as Deduplication\n\n            return hash() == other.hash()\n        }\n\n    }\n\n    // Display\n\n    fun Context.getProtocolColor(type: Int): Int {\n        return when (type) {\n            TYPE_NEKO -> getColorAttr(android.R.attr.textColorPrimary)\n            else -> getColorAttr(R.attr.accentOrTextSecondary)\n        }\n    }\n\n    // Test\n\n    fun genFriendlyMsg(msg: String): String {\n        val msgL = msg.lowercase()\n        return when {\n            msgL.contains(\"timeout\") || msgL.contains(\"deadline\") -> {\n                app.getString(R.string.connection_test_timeout)\n            }\n\n            msgL.contains(\"refused\") || msgL.contains(\"closed pipe\") -> {\n                app.getString(R.string.connection_test_refused)\n            }\n\n            else -> msg\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/SingBoxOptions.java",
    "content": "package moe.matsuri.nb4a;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.JsonElement;\nimport com.google.gson.JsonSerializationContext;\nimport com.google.gson.JsonSerializer;\nimport com.google.gson.ToNumberPolicy;\nimport com.google.gson.TypeAdapter;\nimport com.google.gson.TypeAdapterFactory;\nimport com.google.gson.annotations.SerializedName;\nimport com.google.gson.reflect.TypeToken;\n\nimport java.lang.reflect.Type;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport moe.matsuri.nb4a.utils.Util;\n\npublic class SingBoxOptions {\n\n    // base\n\n    private static final Gson gsonSingbox = new GsonBuilder()\n            .registerTypeHierarchyAdapter(SingBoxOption.class, new SingBoxOptionSerializer())\n            .setPrettyPrinting()\n            .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n            .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n            .setLenient()\n            .disableHtmlEscaping()\n            .create();\n\n    public static class SingBoxOption {\n\n        public transient Map<String, Object> _hack_config_map; // 仍然用普通json方式合并，所以Object内不要使用 _hack\n\n        public transient String _hack_custom_config;\n\n        public SingBoxOption() {\n            _hack_config_map = new HashMap<>();\n        }\n\n        public Map<String, Object> asMap() {\n            return gsonSingbox.fromJson(gsonSingbox.toJson(this), Map.class);\n        }\n\n    }\n\n    public static final class CustomSingBoxOption extends SingBoxOption {\n\n        public transient String config;\n\n        public CustomSingBoxOption(String config) {\n            super();\n            this.config = config;\n        }\n\n        public Map<String, Object> getBasicMap() {\n            Map<String, Object> map = gsonSingbox.fromJson(config, Map.class);\n            if (map == null) {\n                map = new HashMap<>();\n            }\n            return map;\n        }\n    }\n\n    // 自定义序列化器\n    public static class SingBoxOptionSerializer implements JsonSerializer<SingBoxOption> {\n        @Override\n        public JsonElement serialize(SingBoxOption src, Type typeOfSrc, JsonSerializationContext context) {\n            // 拿到原始的 delegate（默认序列化器）\n            TypeAdapter<?> delegate = gsonSingbox.getDelegateAdapter(\n                    new TypeAdapterFactory() {\n                        @Override\n                        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {\n                            return null; // 返回 null，表示只作为“跳过当前自定义”的 marker\n                        }\n                    },\n                    TypeToken.get(src.getClass())\n            );\n            Map<String, Object> map;\n            if (src instanceof CustomSingBoxOption) {\n                map = ((CustomSingBoxOption) src).getBasicMap();\n            } else {\n                map = gsonSingbox.fromJson(((TypeAdapter<SingBoxOption>) delegate).toJson(src), Map.class);\n            }\n            if (src._hack_config_map != null && !src._hack_config_map.isEmpty()) {\n                Util.INSTANCE.mergeMap(map, src._hack_config_map);\n            }\n            if (src._hack_custom_config != null && !src._hack_custom_config.isBlank()) {\n                Util.INSTANCE.mergeJSON(map, src._hack_custom_config);\n            }\n            return gsonSingbox.toJsonTree(map);\n        }\n    }\n\n    // custom classes\n\n    public static class User {\n        public String username;\n        public String password;\n    }\n\n    public static class MyOptions extends SingBoxOption {\n        public LogOptions log;\n\n        public DNSOptions dns;\n\n        public NTPOptions ntp;\n\n        public List<Inbound> inbounds;\n\n        public List<SingBoxOption> outbounds;\n\n        public RouteOptions route;\n\n        public ExperimentalOptions experimental;\n\n    }\n\n    // paste generate output here\n\n    public static class ClashAPIOptions extends SingBoxOption {\n\n        public String external_controller;\n\n        public String external_ui;\n\n        public String external_ui_download_url;\n\n        public String external_ui_download_detour;\n\n        public String secret;\n\n        public String default_mode;\n\n        // Generate note: option type:  public List<String> ModeList;\n\n    }\n\n    public static class SelectorOutboundOptions extends SingBoxOption {\n\n        public List<String> outbounds;\n\n        @SerializedName(\"default\")\n        public String default_;\n\n    }\n\n    public static class URLTestOutboundOptions extends SingBoxOption {\n\n        public List<String> outbounds;\n\n        public String url;\n\n        public Long interval;\n\n        public Integer tolerance;\n\n    }\n\n\n    public static class Options extends SingBoxOption {\n\n        public String $schema;\n\n        public LogOptions log;\n\n        public DNSOptions dns;\n\n        public NTPOptions ntp;\n\n        public List<Inbound> inbounds;\n\n        public List<Outbound> outbounds;\n\n        public RouteOptions route;\n\n        public ExperimentalOptions experimental;\n\n    }\n\n    public static class LogOptions extends SingBoxOption {\n\n        public Boolean disabled;\n\n        public String level;\n\n        public String output;\n\n        public Boolean timestamp;\n\n        // Generate note: option type:  public Boolean DisableColor;\n\n    }\n\n    public static class DebugOptions extends SingBoxOption {\n\n        public String listen;\n\n        public Integer gc_percent;\n\n        public Integer max_stack;\n\n        public Integer max_threads;\n\n        public Boolean panic_on_fault;\n\n        public String trace_back;\n\n        public Long memory_limit;\n\n        public Boolean oom_killer;\n\n    }\n\n\n    public static class DirectInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n        public String override_address;\n\n        public Integer override_port;\n\n    }\n\n    public static class DirectOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public String override_address;\n\n        public Integer override_port;\n\n        public Integer proxy_protocol;\n\n    }\n\n    public static class DNSOptions extends SingBoxOption {\n\n        public List<DNSServerOptions> servers;\n\n        public List<DNSRule> rules;\n\n        @SerializedName(\"final\")\n        public String final_;\n\n        public Boolean reverse_mapping;\n\n        public DNSFakeIPOptions fakeip;\n\n        // Generate note: nested type DNSClientOptions\n        public String strategy;\n\n        public Boolean disable_cache;\n\n        public Boolean disable_expire;\n\n        public Boolean independent_cache;\n\n        // End of public DNSClientOptions ;\n\n    }\n\n    public static class DNSServerOptions extends SingBoxOption {\n\n        public String tag;\n\n        public String address;\n\n        public String address_resolver;\n\n        public String address_strategy;\n\n        public Long address_fallback_delay;\n\n        public String strategy;\n\n        public String detour;\n\n    }\n\n    public static class DNSClientOptions extends SingBoxOption {\n\n        public String strategy;\n\n        public Boolean disable_cache;\n\n        public Boolean disable_expire;\n\n        public Boolean independent_cache;\n\n    }\n\n    public static class DNSFakeIPOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public String inet4_range;\n\n        public String inet6_range;\n\n    }\n\n    public static class ExperimentalOptions extends SingBoxOption {\n\n        public ClashAPIOptions clash_api;\n\n        public V2RayAPIOptions v2ray_api;\n\n        public CacheFile cache_file;\n\n        public DebugOptions debug;\n\n    }\n\n    public static class CacheFile extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public Boolean store_fakeip;\n\n        public String path;\n\n        public String cache_id;\n\n    }\n\n    public static class HysteriaInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String up;\n\n        public Integer up_mbps;\n\n        public String down;\n\n        public Integer down_mbps;\n\n        public String obfs;\n\n        public List<HysteriaUser> users;\n\n        public Long recv_window_conn;\n\n        public Long recv_window_client;\n\n        public Integer max_conn_client;\n\n        public Boolean disable_mtu_discovery;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class HysteriaUser extends SingBoxOption {\n\n        public String name;\n\n        // Generate note: Base64 String\n        public String auth;\n\n        public String auth_str;\n\n    }\n\n    public static class HysteriaOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String up;\n\n        public Integer up_mbps;\n\n        public String down;\n\n        public Integer down_mbps;\n\n        public String obfs;\n\n        // Generate note: Base64 String\n        public String auth;\n\n        public String auth_str;\n\n        public Long recv_window_conn;\n\n        public Long recv_window;\n\n        public Boolean disable_mtu_discovery;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public List<String> server_ports;\n\n        public String hop_interval;\n\n    }\n\n    public static class Hysteria2InboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public Integer up_mbps;\n\n        public Integer down_mbps;\n\n        public Hysteria2Obfs obfs;\n\n        public List<Hysteria2User> users;\n\n        public Boolean ignore_client_bandwidth;\n\n        public InboundTLSOptions tls;\n\n        public String masquerade;\n\n    }\n\n    public static class Hysteria2Obfs extends SingBoxOption {\n\n        public String type;\n\n        public String password;\n\n    }\n\n    public static class Hysteria2User extends SingBoxOption {\n\n        public String name;\n\n        public String password;\n\n    }\n\n    public static class Hysteria2OutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public Integer up_mbps;\n\n        public Integer down_mbps;\n\n        public Hysteria2Obfs obfs;\n\n        public String password;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public List<String> server_ports;\n\n        public String hop_interval;\n\n    }\n\n\n    public static class Inbound extends SingBoxOption {\n\n        public String type;\n\n        public String tag;\n\n        // Generate note: option type:  public TunInboundOptions TunOptions;\n\n        // Generate note: option type:  public RedirectInboundOptions RedirectOptions;\n\n        // Generate note: option type:  public TProxyInboundOptions TProxyOptions;\n\n        // Generate note: option type:  public DirectInboundOptions DirectOptions;\n\n        // Generate note: option type:  public SocksInboundOptions SocksOptions;\n\n        // Generate note: option type:  public HTTPMixedInboundOptions HTTPOptions;\n\n        // Generate note: option type:  public HTTPMixedInboundOptions MixedOptions;\n\n        // Generate note: option type:  public ShadowsocksInboundOptions ShadowsocksOptions;\n\n        // Generate note: option type:  public VMessInboundOptions VMessOptions;\n\n        // Generate note: option type:  public TrojanInboundOptions TrojanOptions;\n\n        // Generate note: option type:  public NaiveInboundOptions NaiveOptions;\n\n        // Generate note: option type:  public HysteriaInboundOptions HysteriaOptions;\n\n        // Generate note: option type:  public ShadowTLSInboundOptions ShadowTLSOptions;\n\n        // Generate note: option type:  public VLESSInboundOptions VLESSOptions;\n\n        // Generate note: option type:  public TUICInboundOptions TUICOptions;\n\n        // Generate note: option type:  public Hysteria2InboundOptions Hysteria2Options;\n\n    }\n\n    public static class InboundOptions extends SingBoxOption {\n\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n    }\n\n    public static class ListenOptions extends SingBoxOption {\n\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n    }\n\n    public static class NaiveInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n        public String network;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class NTPOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public Long interval;\n\n        public Boolean write_to_system;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n    }\n\n\n    public static class Outbound extends SingBoxOption {\n\n        public String type;\n\n        public String tag;\n\n        // Generate note: option type:  public DirectOutboundOptions DirectOptions;\n\n        // Generate note: option type:  public SocksOutboundOptions SocksOptions;\n\n        // Generate note: option type:  public HTTPOutboundOptions HTTPOptions;\n\n        // Generate note: option type:  public ShadowsocksOutboundOptions ShadowsocksOptions;\n\n        // Generate note: option type:  public VMessOutboundOptions VMessOptions;\n\n        // Generate note: option type:  public TrojanOutboundOptions TrojanOptions;\n\n        // Generate note: option type:  public WireGuardOutboundOptions WireGuardOptions;\n\n        // Generate note: option type:  public HysteriaOutboundOptions HysteriaOptions;\n\n        // Generate note: option type:  public TorOutboundOptions TorOptions;\n\n        // Generate note: option type:  public SSHOutboundOptions SSHOptions;\n\n        // Generate note: option type:  public ShadowTLSOutboundOptions ShadowTLSOptions;\n\n        // Generate note: option type:  public ShadowsocksROutboundOptions ShadowsocksROptions;\n\n        // Generate note: option type:  public VLESSOutboundOptions VLESSOptions;\n\n        // Generate note: option type:  public TUICOutboundOptions TUICOptions;\n\n        // Generate note: option type:  public Hysteria2OutboundOptions Hysteria2Options;\n\n        // Generate note: option type:  public SelectorOutboundOptions SelectorOptions;\n\n        // Generate note: option type:  public URLTestOutboundOptions URLTestOptions;\n\n    }\n\n    public static class DialerOptions extends SingBoxOption {\n\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n    }\n\n    public static class ServerOptions extends SingBoxOption {\n\n        public String server;\n\n        public Integer server_port;\n\n    }\n\n    public static class MultiplexOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public String protocol;\n\n        public Integer max_connections;\n\n        public Integer min_streams;\n\n        public Integer max_streams;\n\n        public Boolean padding;\n\n    }\n\n    public static class OnDemandOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public List<OnDemandRule> rules;\n\n    }\n\n    public static class OnDemandRule extends SingBoxOption {\n\n        public String action;\n\n        // Generate note: Listable\n        public List<String> dns_search_domain_match;\n\n        // Generate note: Listable\n        public List<String> dns_server_address_match;\n\n        public String interface_type_match;\n\n        // Generate note: Listable\n        public List<String> ssid_match;\n\n        public String probe_url;\n\n    }\n\n\n    public static class RedirectInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n    }\n\n    public static class TProxyInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n    }\n\n    public static class RouteOptions extends SingBoxOption {\n\n        public List<Rule> rules;\n\n        public List<RuleSet> rule_set;\n\n        @SerializedName(\"final\")\n        public String final_;\n\n        public Boolean find_process;\n\n        public Boolean auto_detect_interface;\n\n        public Boolean override_android_vpn;\n\n        public String default_interface;\n\n        public Integer default_mark;\n\n    }\n\n\n    public static class Rule extends SingBoxOption {\n\n        public String type;\n\n        // Generate note: option type:  public DefaultRule DefaultOptions;\n\n        // Generate note: option type:  public LogicalRule LogicalOptions;\n\n    }\n\n    public static class RuleSet extends SingBoxOption {\n\n        public String type;\n\n        public String tag;\n\n        public String format;\n\n        public String path;\n\n        public String url;\n\n    }\n\n    public static class DefaultRule extends SingBoxOption {\n\n        // Generate note: Listable\n        public List<String> inbound;\n\n        public Integer ip_version;\n\n        // Generate note: Listable\n        public List<String> network;\n\n        // Generate note: Listable\n        public List<String> auth_user;\n\n        // Generate note: Listable\n        public List<String> protocol;\n\n        // Generate note: Listable\n        public List<String> domain;\n\n        // Generate note: Listable\n        public List<String> domain_suffix;\n\n        // Generate note: Listable\n        public List<String> domain_keyword;\n\n        // Generate note: Listable\n        public List<String> domain_regex;\n\n        // Generate note: Listable\n        public List<String> source_ip_cidr;\n\n        // Generate note: Listable\n        public List<String> ip_cidr;\n\n        // Generate note: Listable\n        public List<Integer> source_port;\n\n        // Generate note: Listable\n        public List<String> source_port_range;\n\n        // Generate note: Listable\n        public List<Integer> port;\n\n        // Generate note: Listable\n        public List<String> port_range;\n\n        // Generate note: Listable\n        public List<String> process_name;\n\n        // Generate note: Listable\n        public List<String> process_path;\n\n        // Generate note: Listable\n        public List<String> package_name;\n\n        // Generate note: Listable\n        public List<String> user;\n\n        // Generate note: Listable\n        public List<Integer> user_id;\n\n        public String clash_mode;\n\n        public Boolean invert;\n\n        public String outbound;\n\n    }\n\n\n    public static class DNSRule extends SingBoxOption {\n\n        public String type;\n\n        // Generate note: option type:  public DefaultDNSRule DefaultOptions;\n\n        // Generate note: option type:  public LogicalDNSRule LogicalOptions;\n\n    }\n\n    public static class DefaultDNSRule extends SingBoxOption {\n\n        // Generate note: Listable\n        public List<String> inbound;\n\n        public Integer ip_version;\n\n        // Generate note: Listable\n        public List<String> query_type;\n\n        // Generate note: Listable\n        public List<String> network;\n\n        // Generate note: Listable\n        public List<String> auth_user;\n\n        // Generate note: Listable\n        public List<String> protocol;\n\n        // Generate note: Listable\n        public List<String> domain;\n\n        // Generate note: Listable\n        public List<String> domain_suffix;\n\n        // Generate note: Listable\n        public List<String> domain_keyword;\n\n        // Generate note: Listable\n        public List<String> domain_regex;\n\n        // Generate note: Listable\n        public List<String> geosite;\n\n        // Generate note: Listable\n        public List<String> source_ip_cidr;\n\n        // Generate note: Listable\n        public List<Integer> source_port;\n\n        // Generate note: Listable\n        public List<String> source_port_range;\n\n        // Generate note: Listable\n        public List<Integer> port;\n\n        // Generate note: Listable\n        public List<String> port_range;\n\n        // Generate note: Listable\n        public List<String> process_name;\n\n        // Generate note: Listable\n        public List<String> process_path;\n\n        // Generate note: Listable\n        public List<String> package_name;\n\n        // Generate note: Listable\n        public List<String> user;\n\n        // Generate note: Listable\n        public List<Integer> user_id;\n\n        // Generate note: Listable\n        public List<String> outbound;\n\n        public String clash_mode;\n\n        public Boolean invert;\n\n        public String server;\n\n        public Boolean disable_cache;\n\n        public Integer rewrite_ttl;\n\n    }\n\n    public static class ShadowsocksInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n        public String method;\n\n        public String password;\n\n        public List<ShadowsocksUser> users;\n\n        public List<ShadowsocksDestination> destinations;\n\n    }\n\n    public static class ShadowsocksUser extends SingBoxOption {\n\n        public String name;\n\n        public String password;\n\n    }\n\n    public static class ShadowsocksDestination extends SingBoxOption {\n\n        public String name;\n\n        public String password;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n    }\n\n    public static class ShadowsocksOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String method;\n\n        public String password;\n\n        public String plugin;\n\n        public String plugin_opts;\n\n        public String network;\n\n        public UDPOverTCPOptions udp_over_tcp;\n\n        public MultiplexOptions multiplex;\n\n    }\n\n    public static class ShadowsocksROutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String method;\n\n        public String password;\n\n        public String obfs;\n\n        public String obfs_param;\n\n        public String protocol;\n\n        public String protocol_param;\n\n        public String network;\n\n    }\n\n    public static class ShadowTLSInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public Integer version;\n\n        public String password;\n\n        public List<ShadowTLSUser> users;\n\n        public ShadowTLSHandshakeOptions handshake;\n\n        public Map<String, ShadowTLSHandshakeOptions> handshake_for_server_name;\n\n        public Boolean strict_mode;\n\n    }\n\n    public static class ShadowTLSUser extends SingBoxOption {\n\n        public String name;\n\n        public String password;\n\n    }\n\n    public static class ShadowTLSHandshakeOptions extends SingBoxOption {\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n    }\n\n    public static class ShadowTLSOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public Integer version;\n\n        public String password;\n\n        public OutboundTLSOptions tls;\n\n    }\n\n    public static class SocksInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n    }\n\n    public static class HTTPMixedInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n        public Boolean set_system_proxy;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class SocksOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String version;\n\n        public String username;\n\n        public String password;\n\n        public String network;\n\n        public UDPOverTCPOptions udp_over_tcp;\n\n    }\n\n    public static class HTTPOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String username;\n\n        public String password;\n\n        public OutboundTLSOptions tls;\n\n        public String path;\n\n        public Map<String, String> headers;\n\n    }\n\n    public static class SSHOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String user;\n\n        public String password;\n\n        public String private_key;\n\n        public String private_key_path;\n\n        public String private_key_passphrase;\n\n        // Generate note: Listable\n        public List<String> host_key;\n\n        // Generate note: Listable\n        public List<String> host_key_algorithms;\n\n        public String client_version;\n\n    }\n\n    public static class InboundTLSOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public String server_name;\n\n        public Boolean insecure;\n\n        // Generate note: Listable\n        public List<String> alpn;\n\n        public String min_version;\n\n        public String max_version;\n\n        // Generate note: Listable\n        public List<String> cipher_suites;\n\n        // Generate note: Listable\n        public List<String> certificate;\n\n        public String certificate_path;\n\n        // Generate note: Listable\n        public List<String> key;\n\n        public String key_path;\n\n        public InboundACMEOptions acme;\n\n        public InboundECHOptions ech;\n\n        public InboundRealityOptions reality;\n\n    }\n\n    public static class OutboundTLSOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public Boolean disable_sni;\n\n        public String server_name;\n\n        public Boolean insecure;\n\n        // Generate note: Listable\n        public List<String> alpn;\n\n        public String min_version;\n\n        public String max_version;\n\n        // Generate note: Listable\n        public List<String> cipher_suites;\n\n        public String certificate;\n\n        public String certificate_path;\n\n        public OutboundECHOptions ech;\n\n        public OutboundUTLSOptions utls;\n\n        public OutboundRealityOptions reality;\n\n    }\n\n    public static class InboundRealityOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public InboundRealityHandshakeOptions handshake;\n\n        public String private_key;\n\n        // Generate note: Listable\n        public List<String> short_id;\n\n        public Long max_time_difference;\n\n    }\n\n    public static class InboundRealityHandshakeOptions extends SingBoxOption {\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n    }\n\n    public static class InboundECHOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        // Generate note: Listable\n        public List<String> key;\n\n        public String key_path;\n\n    }\n\n    public static class OutboundECHOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        // Generate note: Listable\n        public List<String> config;\n\n        public String config_path;\n\n    }\n\n    public static class OutboundUTLSOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public String fingerprint;\n\n    }\n\n    public static class OutboundRealityOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public String public_key;\n\n        public String short_id;\n\n    }\n\n    public static class InboundACMEOptions extends SingBoxOption {\n\n        // Generate note: Listable\n        public List<String> domain;\n\n        public String data_directory;\n\n        public String default_server_name;\n\n        public String email;\n\n        public String provider;\n\n        public Boolean disable_http_challenge;\n\n        public Boolean disable_tls_alpn_challenge;\n\n        public Integer alternative_http_port;\n\n        public Integer alternative_tls_port;\n\n        public ACMEExternalAccountOptions external_account;\n\n    }\n\n    public static class ACMEExternalAccountOptions extends SingBoxOption {\n\n        public String key_id;\n\n        public String mac_key;\n\n    }\n\n    public static class TorOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public String executable_path;\n\n        public List<String> extra_args;\n\n        public String data_directory;\n\n        public Map<String, String> torrc;\n\n    }\n\n    public static class TrojanInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<TrojanUser> users;\n\n        public InboundTLSOptions tls;\n\n        public ServerOptions fallback;\n\n        public Map<String, ServerOptions> fallback_for_alpn;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class TrojanUser extends SingBoxOption {\n\n        public String name;\n\n        public String password;\n\n    }\n\n    public static class TrojanOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String password;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class TUICInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<TUICUser> users;\n\n        public String congestion_control;\n\n        public Long auth_timeout;\n\n        public Boolean zero_rtt_handshake;\n\n        public Long heartbeat;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class TUICUser extends SingBoxOption {\n\n        public String name;\n\n        public String uuid;\n\n        public String password;\n\n    }\n\n    public static class TUICOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String password;\n\n        public String congestion_control;\n\n        public String udp_relay_mode;\n\n        public Boolean udp_over_stream;\n\n        public Boolean zero_rtt_handshake;\n\n        public Long heartbeat;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n    }\n\n    public static class TunInboundOptions extends SingBoxOption {\n\n        public String interface_name;\n\n        public Integer mtu;\n\n        // Generate note: Listable\n        public List<String> inet4_address;\n\n        // Generate note: Listable\n        public List<String> inet6_address;\n\n        public Boolean auto_route;\n\n        public Boolean strict_route;\n\n        // Generate note: Listable\n        public List<String> inet4_route_address;\n\n        // Generate note: Listable\n        public List<String> inet6_route_address;\n\n        // Generate note: Listable\n        public List<String> include_interface;\n\n        // Generate note: Listable\n        public List<String> exclude_interface;\n\n        // Generate note: Listable\n        public List<Integer> include_uid;\n\n        // Generate note: Listable\n        public List<String> include_uid_range;\n\n        // Generate note: Listable\n        public List<Integer> exclude_uid;\n\n        // Generate note: Listable\n        public List<String> exclude_uid_range;\n\n        // Generate note: Listable\n        public List<Integer> include_android_user;\n\n        // Generate note: Listable\n        public List<String> include_package;\n\n        // Generate note: Listable\n        public List<String> exclude_package;\n\n        public Boolean endpoint_independent_nat;\n\n        public Long udp_timeout;\n\n        public String stack;\n\n        public TunPlatformOptions platform;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n    }\n\n    public static class TunPlatformOptions extends SingBoxOption {\n\n        public HTTPProxyOptions http_proxy;\n\n    }\n\n    public static class HTTPProxyOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n    }\n\n\n    public static class UDPOverTCPOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public Integer version;\n\n    }\n\n    public static class V2RayAPIOptions extends SingBoxOption {\n\n        public String listen;\n\n        public V2RayStatsServiceOptions stats;\n\n    }\n\n    public static class V2RayStatsServiceOptions extends SingBoxOption {\n\n        public Boolean enabled;\n\n        public List<String> inbounds;\n\n        public List<String> outbounds;\n\n        public List<String> users;\n\n    }\n\n\n    public static class V2RayTransportOptions extends SingBoxOption {\n\n        public String type;\n\n        // Generate note: option type:  public V2RayHTTPOptions HTTPOptions;\n\n        // Generate note: option type:  public V2RayWebsocketOptions WebsocketOptions;\n\n        // Generate note: option type:  public V2RayQUICOptions QUICOptions;\n\n        // Generate note: option type:  public V2RayGRPCOptions GRPCOptions;\n\n    }\n\n    public static class V2RayHTTPOptions extends SingBoxOption {\n\n        // Generate note: Listable\n        public List<String> host;\n\n        public String path;\n\n        public String method;\n\n        public Map<String, String> headers;\n\n        public Long idle_timeout;\n\n        public Long ping_timeout;\n\n    }\n\n    public static class V2RayWebsocketOptions extends SingBoxOption {\n\n        public String path;\n\n        public Map<String, String> headers;\n\n        public Integer max_early_data;\n\n        public String early_data_header_name;\n\n    }\n\n\n    public static class V2RayGRPCOptions extends SingBoxOption {\n\n        public String service_name;\n\n        public Long idle_timeout;\n\n        public Long ping_timeout;\n\n        public Boolean permit_without_stream;\n\n        // Generate note: option type:  public Boolean ForceLite;\n\n    }\n\n    public static class V2RayHTTPUpgradeOptions extends SingBoxOption {\n\n        public String host;\n\n        public String path;\n\n        public Map<String, String> headers;\n\n    }\n\n    public static class VLESSInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<VLESSUser> users;\n\n        public InboundTLSOptions tls;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class VLESSUser extends SingBoxOption {\n\n        public String name;\n\n        public String uuid;\n\n        public String flow;\n\n    }\n\n    public static class VLESSOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String flow;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n        public String packet_encoding;\n\n    }\n\n    public static class VMessInboundOptions extends SingBoxOption {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<VMessUser> users;\n\n        public InboundTLSOptions tls;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class VMessUser extends SingBoxOption {\n\n        public String name;\n\n        public String uuid;\n\n        public Integer alterId;\n\n    }\n\n    public static class VMessOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String security;\n\n        public Integer alter_id;\n\n        public Boolean global_padding;\n\n        public Boolean authenticated_length;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public String packet_encoding;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class WireGuardOutboundOptions extends SingBoxOption {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        // Generate note: option type:  public Boolean UDPFragmentDefault;\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public Boolean system_interface;\n\n        public String interface_name;\n\n        // Generate note: Listable\n        public List<String> local_address;\n\n        public String private_key;\n\n        public List<WireGuardPeer> peers;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String peer_public_key;\n\n        public String pre_shared_key;\n\n        // Generate note: Base64 String\n        public String reserved;\n\n        public Integer workers;\n\n        public Integer mtu;\n\n        public String network;\n\n    }\n\n    public static class WireGuardPeer extends SingBoxOption {\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String public_key;\n\n        public String pre_shared_key;\n\n        // Generate note: Listable\n        public List<String> allowed_ips;\n\n        // Generate note: Base64 String\n        public String reserved;\n\n    }\n\n    public static class Inbound_TunOptions extends Inbound {\n\n        public String interface_name;\n\n        public Integer mtu;\n\n        // Generate note: Listable\n        public List<String> inet4_address;\n\n        // Generate note: Listable\n        public List<String> inet6_address;\n\n        public Boolean auto_route;\n\n        public Boolean strict_route;\n\n        // Generate note: Listable\n        public List<String> inet4_route_address;\n\n        // Generate note: Listable\n        public List<String> inet6_route_address;\n\n        // Generate note: Listable\n        public List<String> include_interface;\n\n        // Generate note: Listable\n        public List<String> exclude_interface;\n\n        // Generate note: Listable\n        public List<Integer> include_uid;\n\n        // Generate note: Listable\n        public List<String> include_uid_range;\n\n        // Generate note: Listable\n        public List<Integer> exclude_uid;\n\n        // Generate note: Listable\n        public List<String> exclude_uid_range;\n\n        // Generate note: Listable\n        public List<Integer> include_android_user;\n\n        // Generate note: Listable\n        public List<String> include_package;\n\n        // Generate note: Listable\n        public List<String> exclude_package;\n\n        public Boolean endpoint_independent_nat;\n\n        public Long udp_timeout;\n\n        public String stack;\n\n        public TunPlatformOptions platform;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n    }\n\n    public static class Inbound_RedirectOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n    }\n\n    public static class Inbound_TProxyOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n    }\n\n    public static class Inbound_DirectOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n        public String override_address;\n\n        public Integer override_port;\n\n    }\n\n    public static class Inbound_SocksOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n    }\n\n    public static class Inbound_HTTPOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n        public Boolean set_system_proxy;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class Inbound_MixedOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n        public Boolean set_system_proxy;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class Inbound_ShadowsocksOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String network;\n\n        public String method;\n\n        public String password;\n\n        public List<ShadowsocksUser> users;\n\n        public List<ShadowsocksDestination> destinations;\n\n    }\n\n    public static class Inbound_VMessOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<VMessUser> users;\n\n        public InboundTLSOptions tls;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class Inbound_TrojanOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<TrojanUser> users;\n\n        public InboundTLSOptions tls;\n\n        public ServerOptions fallback;\n\n        public Map<String, ServerOptions> fallback_for_alpn;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class Inbound_NaiveOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<User> users;\n\n        public String network;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class Inbound_HysteriaOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public String up;\n\n        public Integer up_mbps;\n\n        public String down;\n\n        public Integer down_mbps;\n\n        public String obfs;\n\n        public List<HysteriaUser> users;\n\n        public Long recv_window_conn;\n\n        public Long recv_window_client;\n\n        public Integer max_conn_client;\n\n        public Boolean disable_mtu_discovery;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class Inbound_ShadowTLSOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public Integer version;\n\n        public String password;\n\n        public List<ShadowTLSUser> users;\n\n        public ShadowTLSHandshakeOptions handshake;\n\n        public Map<String, ShadowTLSHandshakeOptions> handshake_for_server_name;\n\n        public Boolean strict_mode;\n\n    }\n\n    public static class Inbound_VLESSOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<VLESSUser> users;\n\n        public InboundTLSOptions tls;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class Inbound_TUICOptions extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public List<TUICUser> users;\n\n        public String congestion_control;\n\n        public Long auth_timeout;\n\n        public Boolean zero_rtt_handshake;\n\n        public Long heartbeat;\n\n        public InboundTLSOptions tls;\n\n    }\n\n    public static class Inbound_Hysteria2Options extends Inbound {\n\n        // Generate note: nested type ListenOptions\n        public String listen;\n\n        public Integer listen_port;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public Long udp_timeout;\n\n        public Boolean proxy_protocol;\n\n        public Boolean proxy_protocol_accept_no_header;\n\n        public String detour;\n\n        // Generate note: nested type InboundOptions\n        public Boolean sniff;\n\n        public Boolean sniff_override_destination;\n\n        public Long sniff_timeout;\n\n        public String domain_strategy;\n\n        // End of public InboundOptions ;\n\n        // End of public ListenOptions ;\n\n        public Integer up_mbps;\n\n        public Integer down_mbps;\n\n        public Hysteria2Obfs obfs;\n\n        public List<Hysteria2User> users;\n\n        public Boolean ignore_client_bandwidth;\n\n        public InboundTLSOptions tls;\n\n        public String masquerade;\n\n    }\n\n    public static class Outbound_DirectOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public String override_address;\n\n        public Integer override_port;\n\n        public Integer proxy_protocol;\n\n    }\n\n    public static class Outbound_SocksOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String version;\n\n        public String username;\n\n        public String password;\n\n        public String network;\n\n        public UDPOverTCPOptions udp_over_tcp;\n\n    }\n\n    public static class Outbound_HTTPOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String username;\n\n        public String password;\n\n        public OutboundTLSOptions tls;\n\n        public String path;\n\n        public Map<String, String> headers;\n\n    }\n\n    public static class Outbound_ShadowsocksOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String method;\n\n        public String password;\n\n        public String plugin;\n\n        public String plugin_opts;\n\n        public String network;\n\n        public UDPOverTCPOptions udp_over_tcp;\n\n        public MultiplexOptions multiplex;\n\n    }\n\n    public static class Outbound_VMessOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String security;\n\n        public Integer alter_id;\n\n        public Boolean global_padding;\n\n        public Boolean authenticated_length;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public String packet_encoding;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class Outbound_TrojanOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String password;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n    }\n\n    public static class Outbound_WireGuardOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public Boolean system_interface;\n\n        public String interface_name;\n\n        // Generate note: Listable\n        public List<String> local_address;\n\n        public String private_key;\n\n        public List<WireGuardPeer> peers;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String peer_public_key;\n\n        public String pre_shared_key;\n\n        // Generate note: Base64 String\n        public String reserved;\n\n        public Integer workers;\n\n        public Integer mtu;\n\n        public String network;\n\n    }\n\n    public static class Outbound_HysteriaOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String up;\n\n        public Integer up_mbps;\n\n        public String down;\n\n        public Integer down_mbps;\n\n        public String obfs;\n\n        // Generate note: Base64 String\n        public String auth;\n\n        public String auth_str;\n\n        public Long recv_window_conn;\n\n        public Long recv_window;\n\n        public Boolean disable_mtu_discovery;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public List<String> server_ports;\n\n        public String hop_interval;\n\n    }\n\n    public static class Outbound_TorOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        public String executable_path;\n\n        public List<String> extra_args;\n\n        public String data_directory;\n\n        public Map<String, String> torrc;\n\n    }\n\n    public static class Outbound_SSHOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String user;\n\n        public String password;\n\n        public String private_key;\n\n        public String private_key_path;\n\n        public String private_key_passphrase;\n\n        // Generate note: Listable\n        public List<String> host_key;\n\n        // Generate note: Listable\n        public List<String> host_key_algorithms;\n\n        public String client_version;\n\n    }\n\n    public static class Outbound_ShadowTLSOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public Integer version;\n\n        public String password;\n\n        public OutboundTLSOptions tls;\n\n    }\n\n    public static class Outbound_ShadowsocksROptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String method;\n\n        public String password;\n\n        public String obfs;\n\n        public String obfs_param;\n\n        public String protocol;\n\n        public String protocol_param;\n\n        public String network;\n\n    }\n\n    public static class Outbound_VLESSOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String flow;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public MultiplexOptions multiplex;\n\n        public V2RayTransportOptions transport;\n\n        public String packet_encoding;\n\n    }\n\n    public static class Outbound_TUICOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public String uuid;\n\n        public String password;\n\n        public String congestion_control;\n\n        public String udp_relay_mode;\n\n        public Boolean udp_over_stream;\n\n        public Boolean zero_rtt_handshake;\n\n        public Long heartbeat;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n    }\n\n    public static class Outbound_Hysteria2Options extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public Long connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n\n        public String domain_strategy;\n\n        public Long fallback_delay;\n\n        // End of public DialerOptions ;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // End of public ServerOptions ;\n\n        public Integer up_mbps;\n\n        public Integer down_mbps;\n\n        public Hysteria2Obfs obfs;\n\n        public String password;\n\n        public String network;\n\n        public OutboundTLSOptions tls;\n\n        public List<String> server_ports;\n\n        public String hop_interval;\n\n    }\n\n    public static class Outbound_SelectorOptions extends Outbound {\n\n        public List<String> outbounds;\n\n        @SerializedName(\"default\")\n        public String default_;\n\n    }\n\n    public static class Outbound_URLTestOptions extends Outbound {\n\n        public List<String> outbounds;\n\n        public String url;\n\n        public Long interval;\n\n        public Integer tolerance;\n\n    }\n\n    public static class Rule_DefaultOptions extends Rule {\n\n        // Generate note: Listable\n        public List<String> inbound;\n\n        public Integer ip_version;\n\n        // Generate note: Listable\n        public List<String> network;\n\n        // Generate note: Listable\n        public List<String> auth_user;\n\n        // Generate note: Listable\n        public List<String> protocol;\n\n        // Generate note: Listable\n        public List<String> domain;\n\n        // Generate note: Listable\n        public List<String> domain_suffix;\n\n        // Generate note: Listable\n        public List<String> domain_keyword;\n\n        // Generate note: Listable\n        public List<String> domain_regex;\n\n        public List<String> rule_set;\n\n        public Boolean source_ip_is_private;\n\n        public Boolean ip_is_private;\n\n        // Generate note: Listable\n        public List<String> source_ip_cidr;\n\n        // Generate note: Listable\n        public List<String> ip_cidr;\n\n        // Generate note: Listable\n        public List<Integer> source_port;\n\n        // Generate note: Listable\n        public List<String> source_port_range;\n\n        // Generate note: Listable\n        public List<Integer> port;\n\n        // Generate note: Listable\n        public List<String> port_range;\n\n        // Generate note: Listable\n        public List<String> process_name;\n\n        // Generate note: Listable\n        public List<String> process_path;\n\n        // Generate note: Listable\n        public List<String> package_name;\n\n        // Generate note: Listable\n        public List<String> user;\n\n        // Generate note: Listable\n        public List<Integer> user_id;\n\n        public String clash_mode;\n\n        public Boolean invert;\n\n        public String action;\n\n        public String outbound;\n\n    }\n\n    public static class DNSRule_DefaultOptions extends DNSRule {\n\n        // Generate note: Listable\n        public List<String> inbound;\n\n        public Integer ip_version;\n\n        // Generate note: Listable\n        public List<String> query_type;\n\n        // Generate note: Listable\n        public List<String> network;\n\n        // Generate note: Listable\n        public List<String> auth_user;\n\n        // Generate note: Listable\n        public List<String> protocol;\n\n        // Generate note: Listable\n        public List<String> domain;\n\n        // Generate note: Listable\n        public List<String> domain_suffix;\n\n        // Generate note: Listable\n        public List<String> domain_keyword;\n\n        // Generate note: Listable\n        public List<String> domain_regex;\n\n        public List<String> rule_set;\n\n        // Generate note: Listable\n        public List<String> source_ip_cidr;\n\n        // Generate note: Listable\n        public List<Integer> source_port;\n\n        // Generate note: Listable\n        public List<String> source_port_range;\n\n        // Generate note: Listable\n        public List<Integer> port;\n\n        // Generate note: Listable\n        public List<String> port_range;\n\n        // Generate note: Listable\n        public List<String> process_name;\n\n        // Generate note: Listable\n        public List<String> process_path;\n\n        // Generate note: Listable\n        public List<String> package_name;\n\n        // Generate note: Listable\n        public List<String> user;\n\n        // Generate note: Listable\n        public List<Integer> user_id;\n\n        // Generate note: Listable\n        public List<String> outbound;\n\n        public String clash_mode;\n\n        public Boolean invert;\n\n        public String server;\n\n        public Boolean disable_cache;\n\n        public Integer rewrite_ttl;\n\n    }\n\n    public static class V2RayTransportOptions_HTTPOptions extends V2RayTransportOptions {\n\n        // Generate note: Listable\n        public List<String> host;\n\n        public String path;\n\n        public String method;\n\n        public Map<String, String> headers;\n\n        public Long idle_timeout;\n\n        public Long ping_timeout;\n\n    }\n\n    public static class V2RayTransportOptions_WebsocketOptions extends V2RayTransportOptions {\n\n        public String path;\n\n        public Map<String, String> headers;\n\n        public Integer max_early_data;\n\n        public String early_data_header_name;\n\n    }\n\n\n    public static class V2RayTransportOptions_GRPCOptions extends V2RayTransportOptions {\n\n        public String service_name;\n\n        public Long idle_timeout;\n\n        public Long ping_timeout;\n\n        public Boolean permit_without_stream;\n\n\n    }\n\n    public static class V2RayTransportOptions_HTTPUpgradeOptions extends V2RayTransportOptions {\n\n        public String host;\n\n        public String path;\n\n\n    }\n\n    // sing-box Options 生成器已经坏了，以下是从 husi 抄的\n\n    public static class Outbound_AnyTLSOptions extends Outbound {\n\n        // Generate note: nested type DialerOptions\n        public String detour;\n\n        public String bind_interface;\n\n        public String inet4_bind_address;\n\n        public String inet6_bind_address;\n\n        public String protect_path;\n\n        public Integer routing_mark;\n\n        public Boolean reuse_addr;\n\n        public String connect_timeout;\n\n        public Boolean tcp_fast_open;\n\n        public Boolean tcp_multi_path;\n\n        public Boolean udp_fragment;\n\n        public String domain_strategy;\n\n        public String network_strategy;\n\n        public List<String> network_type;\n\n        public List<String> fallback_network_type;\n\n        public String fallback_delay;\n\n        // Generate note: nested type ServerOptions\n        public String server;\n\n        public Integer server_port;\n\n        // Generate note: nested type OutboundTLSOptionsContainer\n        public OutboundTLSOptions tls;\n\n        public String password;\n\n        public String idle_session_check_interval;\n\n        public String idle_session_timeout;\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/SingBoxOptionsUtil.kt",
    "content": "package moe.matsuri.nb4a\n\nimport io.nekohasekai.sagernet.database.DataStore\nimport moe.matsuri.nb4a.SingBoxOptions.RuleSet\n\nobject SingBoxOptionsUtil {\n\n    fun domainStrategy(tag: String): String {\n        fun auto2(key: String, newS: String): String {\n            return (DataStore.configurationStore.getString(key) ?: \"\").replace(\"auto\", newS)\n        }\n        return when (tag) {\n            \"dns-remote\" -> {\n                auto2(\"domain_strategy_for_remote\", \"\")\n            }\n\n            \"dns-direct\" -> {\n                auto2(\"domain_strategy_for_direct\", \"\")\n            }\n\n            // server\n            else -> {\n                auto2(\"domain_strategy_for_server\", \"prefer_ipv4\")\n            }\n        }\n    }\n\n}\n\nfun SingBoxOptions.DNSRule_DefaultOptions.makeSingBoxRule(list: List<String>) {\n    rule_set = mutableListOf<String>()\n    domain = mutableListOf<String>()\n    domain_suffix = mutableListOf<String>()\n    domain_regex = mutableListOf<String>()\n    domain_keyword = mutableListOf<String>()\n    list.forEach {\n        if (it.startsWith(\"geosite:\")) {\n            rule_set.plusAssign(it)\n        } else if (it.startsWith(\"full:\")) {\n            domain.plusAssign(it.removePrefix(\"full:\").lowercase())\n        } else if (it.startsWith(\"domain:\")) {\n            domain_suffix.plusAssign(it.removePrefix(\"domain:\").lowercase())\n        } else if (it.startsWith(\"regexp:\")) {\n            domain_regex.plusAssign(it.removePrefix(\"regexp:\").lowercase())\n        } else if (it.startsWith(\"keyword:\")) {\n            domain_keyword.plusAssign(it.removePrefix(\"keyword:\").lowercase())\n        } else {\n            domain_suffix.plusAssign(it.lowercase())\n        }\n    }\n    rule_set?.removeIf { it.isNullOrBlank() }\n    domain?.removeIf { it.isNullOrBlank() }\n    domain_suffix?.removeIf { it.isNullOrBlank() }\n    domain_regex?.removeIf { it.isNullOrBlank() }\n    domain_keyword?.removeIf { it.isNullOrBlank() }\n    if (rule_set?.isEmpty() == true) rule_set = null\n    if (domain?.isEmpty() == true) domain = null\n    if (domain_suffix?.isEmpty() == true) domain_suffix = null\n    if (domain_regex?.isEmpty() == true) domain_regex = null\n    if (domain_keyword?.isEmpty() == true) domain_keyword = null\n}\n\nfun SingBoxOptions.DNSRule_DefaultOptions.checkEmpty(): Boolean {\n    if (rule_set?.isNotEmpty() == true) return false\n    if (domain?.isNotEmpty() == true) return false\n    if (domain_suffix?.isNotEmpty() == true) return false\n    if (domain_regex?.isNotEmpty() == true) return false\n    if (domain_keyword?.isNotEmpty() == true) return false\n    if (user_id?.isNotEmpty() == true) return false\n    return true\n}\n\nfun generateRuleSet(ruleSetString: List<String>, ruleSet: MutableList<RuleSet>) {\n    ruleSetString.forEach {\n        when {\n            it.startsWith(\"geoip:\") -> {\n                ruleSet.add(RuleSet().apply {\n                    type = \"local\"\n                    tag = it\n                    format = \"binary\"\n                    path = it\n                })\n            }\n\n            it.startsWith(\"geosite:\") -> {\n                ruleSet.add(RuleSet().apply {\n                    type = \"local\"\n                    tag = it\n                    format = \"binary\"\n                    path = it\n                })\n            }\n        }\n    }\n}\n\nfun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP: Boolean) {\n    if (isIP) {\n        ip_cidr = mutableListOf<String>()\n        rule_set = mutableListOf<String>()\n    } else {\n        rule_set = mutableListOf<String>()\n        domain = mutableListOf<String>()\n        domain_suffix = mutableListOf<String>()\n        domain_regex = mutableListOf<String>()\n        domain_keyword = mutableListOf<String>()\n    }\n    list.forEach {\n        if (isIP) {\n            if (it.startsWith(\"geoip:\")) {\n                if (it == \"geoip:private\") {\n                    ip_is_private = true\n                } else {\n                    rule_set.plusAssign(it)\n                }\n            } else {\n                ip_cidr.plusAssign(it)\n            }\n            return@forEach\n        }\n        if (it.startsWith(\"geosite:\")) {\n            rule_set.plusAssign(it)\n        } else if (it.startsWith(\"full:\")) {\n            domain.plusAssign(it.removePrefix(\"full:\").lowercase())\n        } else if (it.startsWith(\"domain:\")) {\n            domain_suffix.plusAssign(it.removePrefix(\"domain:\").lowercase())\n        } else if (it.startsWith(\"regexp:\")) {\n            domain_regex.plusAssign(it.removePrefix(\"regexp:\").lowercase())\n        } else if (it.startsWith(\"keyword:\")) {\n            domain_keyword.plusAssign(it.removePrefix(\"keyword:\").lowercase())\n        } else {\n            domain_suffix.plusAssign(it.lowercase())\n        }\n    }\n    ip_cidr?.removeIf { it.isNullOrBlank() }\n    rule_set?.removeIf { it.isNullOrBlank() }\n    domain?.removeIf { it.isNullOrBlank() }\n    domain_suffix?.removeIf { it.isNullOrBlank() }\n    domain_regex?.removeIf { it.isNullOrBlank() }\n    domain_keyword?.removeIf { it.isNullOrBlank() }\n    if (ip_cidr?.isEmpty() == true) ip_cidr = null\n    if (domain?.isEmpty() == true) domain = null\n    if (domain_suffix?.isEmpty() == true) domain_suffix = null\n    if (domain_regex?.isEmpty() == true) domain_regex = null\n    if (domain_keyword?.isEmpty() == true) domain_keyword = null\n}\n\nfun SingBoxOptions.Rule_DefaultOptions.checkEmpty(): Boolean {\n    if (ip_cidr?.isNotEmpty() == true) return false\n    if (domain?.isNotEmpty() == true) return false\n    if (rule_set?.isNotEmpty() == true) return false\n    if (domain_suffix?.isNotEmpty() == true) return false\n    if (domain_regex?.isNotEmpty() == true) return false\n    if (domain_keyword?.isNotEmpty() == true) return false\n    if (user_id?.isNotEmpty() == true) return false\n    //\n    if (port?.isNotEmpty() == true) return false\n    if (port_range?.isNotEmpty() == true) return false\n    if (source_ip_cidr?.isNotEmpty() == true) return false\n    //\n    if (!_hack_custom_config.isNullOrBlank()) return false\n    return true\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/TempDatabase.kt",
    "content": "package moe.matsuri.nb4a\n\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.database.preference.KeyValuePair\nimport kotlinx.coroutines.GlobalScope\nimport kotlinx.coroutines.launch\n\n@Database(entities = [KeyValuePair::class], version = 1)\nabstract class TempDatabase : RoomDatabase() {\n\n    companion object {\n        @Suppress(\"EXPERIMENTAL_API_USAGE\")\n        private val instance by lazy {\n            Room.inMemoryDatabaseBuilder(SagerNet.application, TempDatabase::class.java)\n                .allowMainThreadQueries()\n                .fallbackToDestructiveMigration()\n                .setQueryExecutor { GlobalScope.launch { it.run() } }\n                .build()\n        }\n\n        val profileCacheDao get() = instance.profileCacheDao()\n\n    }\n\n    abstract fun profileCacheDao(): KeyValuePair.Dao\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/net/LocalResolverImpl.kt",
    "content": "package moe.matsuri.nb4a.net\n\nimport android.net.DnsResolver\nimport android.os.Build\nimport android.os.CancellationSignal\nimport android.system.ErrnoException\nimport androidx.annotation.RequiresApi\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.runOnIoDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.asExecutor\nimport libcore.ExchangeContext\nimport libcore.LocalDNSTransport\nimport java.net.InetAddress\nimport java.net.UnknownHostException\n\nobject LocalResolverImpl : LocalDNSTransport {\n\n    // new local\n\n    private const val RCODE_NXDOMAIN = 3\n\n    override fun raw(): Boolean {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q\n    }\n\n    override fun networkHandle(): Long {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            return SagerNet.underlyingNetwork?.networkHandle ?: 0\n        }\n        return 0\n    }\n\n    @RequiresApi(Build.VERSION_CODES.Q)\n    override fun exchange(ctx: ExchangeContext, message: ByteArray) {\n        val signal = CancellationSignal()\n        ctx.onCancel(signal::cancel)\n\n        val callback = object : DnsResolver.Callback<ByteArray> {\n            override fun onAnswer(answer: ByteArray, rcode: Int) {\n                ctx.rawSuccess(answer)\n            }\n\n            override fun onError(error: DnsResolver.DnsException) {\n                val cause = error.cause\n                if (cause is ErrnoException) {\n                    ctx.errnoCode(cause.errno)\n                } else {\n                    Logs.w(error)\n                    ctx.errnoCode(114514)\n                }\n            }\n        }\n\n        DnsResolver.getInstance().rawQuery(\n            SagerNet.underlyingNetwork,\n            message,\n            DnsResolver.FLAG_NO_RETRY,\n            Dispatchers.IO.asExecutor(),\n            signal,\n            callback\n        )\n    }\n\n    override fun lookup(ctx: ExchangeContext, network: String, domain: String) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            val signal = CancellationSignal()\n            ctx.onCancel(signal::cancel)\n\n            val callback = object : DnsResolver.Callback<Collection<InetAddress>> {\n                override fun onAnswer(answer: Collection<InetAddress>, rcode: Int) {\n                    try {\n                        if (rcode == 0) {\n                            ctx.success(answer.mapNotNull { it.hostAddress }.joinToString(\"\\n\"))\n                        } else {\n                            ctx.errorCode(rcode)\n                        }\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                        ctx.errnoCode(114514)\n                    }\n                }\n\n                override fun onError(error: DnsResolver.DnsException) {\n                    try {\n                        val cause = error.cause\n                        if (cause is ErrnoException) {\n                            ctx.errnoCode(cause.errno)\n                        } else {\n                            Logs.w(error)\n                            ctx.errnoCode(114514)\n                        }\n                    } catch (e: Exception) {\n                        Logs.w(e)\n                        ctx.errnoCode(114514)\n                    }\n                }\n            }\n\n            val type = when {\n                network.endsWith(\"4\") -> DnsResolver.TYPE_A\n                network.endsWith(\"6\") -> DnsResolver.TYPE_AAAA\n                else -> null\n            }\n            if (type != null) {\n                DnsResolver.getInstance().query(\n                    SagerNet.underlyingNetwork,\n                    domain,\n                    type,\n                    DnsResolver.FLAG_NO_RETRY,\n                    Dispatchers.IO.asExecutor(),\n                    signal,\n                    callback\n                )\n            } else {\n                DnsResolver.getInstance().query(\n                    SagerNet.underlyingNetwork,\n                    domain,\n                    DnsResolver.FLAG_NO_RETRY,\n                    Dispatchers.IO.asExecutor(),\n                    signal,\n                    callback\n                )\n            }\n        } else {\n            runOnIoDispatcher {\n                // 老版本系统，继续用阻塞的 InetAddress\n                try {\n                    val u = SagerNet.underlyingNetwork\n                    val answer = try {\n                        u?.getAllByName(domain)\n                    } catch (e: UnknownHostException) {\n                        null\n                    } ?: InetAddress.getAllByName(domain)\n                    if (answer != null) {\n                        ctx.success(answer.mapNotNull { it.hostAddress }.joinToString(\"\\n\"))\n                    } else {\n                        ctx.errnoCode(114514)\n                    }\n                } catch (e: UnknownHostException) {\n                    ctx.errorCode(RCODE_NXDOMAIN)\n                } catch (e: Exception) {\n                    Logs.w(e)\n                    ctx.errnoCode(114514)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/plugin/Plugins.kt",
    "content": "package moe.matsuri.nb4a.plugin\n\nimport android.content.Intent\nimport android.content.pm.PackageInfo\nimport android.content.pm.PackageManager\nimport android.content.pm.ProviderInfo\nimport android.net.Uri\nimport android.os.Build\nimport android.widget.Toast\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.plugin.PluginManager.loadString\nimport io.nekohasekai.sagernet.utils.PackageCache\n\nobject Plugins {\n    const val AUTHORITIES_PREFIX_SEKAI_EXE = \"io.nekohasekai.sagernet.plugin.\"\n    const val AUTHORITIES_PREFIX_NEKO_EXE = \"moe.matsuri.exe.\"\n\n    const val ACTION_NATIVE_PLUGIN = \"io.nekohasekai.sagernet.plugin.ACTION_NATIVE_PLUGIN\"\n\n    const val METADATA_KEY_ID = \"io.nekohasekai.sagernet.plugin.id\"\n    const val METADATA_KEY_EXECUTABLE_PATH = \"io.nekohasekai.sagernet.plugin.executable_path\"\n\n    fun isExe(pkg: PackageInfo): Boolean {\n        if (pkg.providers?.isEmpty() == true) return false\n        val provider = pkg.providers?.get(0) ?: return false\n        val auth = provider.authority ?: return false\n        return auth.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE)\n                || auth.startsWith(AUTHORITIES_PREFIX_NEKO_EXE)\n    }\n\n    fun preferExePrefix(): String {\n        return AUTHORITIES_PREFIX_NEKO_EXE\n    }\n\n    fun isUsingMatsuriExe(pluginId: String): Boolean {\n        getPlugin(pluginId)?.apply {\n            if (authority.startsWith(AUTHORITIES_PREFIX_NEKO_EXE)) {\n                return true\n            }\n        }\n        return false;\n    }\n\n    fun displayExeProvider(pkgName: String): String {\n        return if (pkgName.startsWith(AUTHORITIES_PREFIX_SEKAI_EXE)) {\n            \"SagerNet\"\n        } else if (pkgName.startsWith(AUTHORITIES_PREFIX_NEKO_EXE)) {\n            \"Matsuri\"\n        } else {\n            \"Unknown\"\n        }\n    }\n\n    fun getPlugin(pluginId: String): ProviderInfo? {\n        if (pluginId.isBlank()) return null\n        getPluginExternal(pluginId)?.let { return it }\n        // internal so\n        return ProviderInfo().apply { authority = AUTHORITIES_PREFIX_NEKO_EXE }\n    }\n\n    fun getPluginExternal(pluginId: String): ProviderInfo? {\n        if (pluginId.isBlank()) return null\n\n        // try queryIntentContentProviders\n        var providers = getExtPluginOld(pluginId)\n\n        // try PackageCache\n        if (providers.isEmpty()) providers = getExtPluginNew(pluginId)\n\n        // not found\n        if (providers.isEmpty()) return null\n\n        if (providers.size > 1) {\n            val prefer = providers.filter {\n                it.authority.startsWith(preferExePrefix())\n            }\n            if (prefer.size == 1) providers = prefer\n        }\n\n        if (providers.size > 1) {\n            val message =\n                \"Conflicting plugins found from: ${providers.joinToString { it.packageName }}\"\n            Toast.makeText(SagerNet.application, message, Toast.LENGTH_LONG).show()\n        }\n\n        return providers[0]\n    }\n\n    private fun getExtPluginNew(pluginId: String): List<ProviderInfo> {\n        PackageCache.awaitLoadSync()\n        val pkgs = PackageCache.installedPluginPackages\n            .map { it.value }\n            .filter { it.providers?.get(0)?.loadString(METADATA_KEY_ID) == pluginId }\n        return pkgs.mapNotNull { it.providers?.get(0) }\n    }\n\n    private fun buildUri(id: String, auth: String) = Uri.Builder()\n        .scheme(\"plugin\")\n        .authority(auth)\n        .path(\"/$id\")\n        .build()\n\n    private fun getExtPluginOld(pluginId: String): List<ProviderInfo> {\n        var flags = PackageManager.GET_META_DATA\n        if (Build.VERSION.SDK_INT >= 24) {\n            flags =\n                flags or PackageManager.MATCH_DIRECT_BOOT_UNAWARE or PackageManager.MATCH_DIRECT_BOOT_AWARE\n        }\n        val list1 = SagerNet.application.packageManager.queryIntentContentProviders(\n            Intent(ACTION_NATIVE_PLUGIN, buildUri(pluginId, \"io.nekohasekai.sagernet\")), flags\n        )\n        val list2 = SagerNet.application.packageManager.queryIntentContentProviders(\n            Intent(ACTION_NATIVE_PLUGIN, buildUri(pluginId, \"moe.matsuri.lite\")), flags\n        )\n        return (list1 + list2).mapNotNull {\n            it.providerInfo\n        }.filter { it.exported }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBinding.kt",
    "content": "package moe.matsuri.nb4a.proxy\n\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.readableMessage\n\nobject Type {\n    const val Text = 0\n    const val TextToInt = 1\n    const val Int = 2\n    const val Bool = 3\n}\n\nclass PreferenceBinding(\n    val type: Int = Type.Text,\n    var fieldName: String,\n    var bean: Any? = null,\n    var pf: PreferenceFragmentCompat? = null\n) {\n\n    var cacheName = fieldName\n    var disable = false\n\n    fun readStringFromCache(): String {\n        return DataStore.profileCacheStore.getString(cacheName) ?: \"\"\n    }\n\n    fun readBoolFromCache(): Boolean {\n        return DataStore.profileCacheStore.getBoolean(cacheName, false)\n    }\n\n    fun readIntFromCache(): Int {\n        return DataStore.profileCacheStore.getInt(cacheName, 0)\n    }\n\n    fun readStringToIntFromCache(): Int {\n        val value = DataStore.profileCacheStore.getString(cacheName)?.toIntOrNull() ?: 0\n//        Logs.d(\"readStringToIntFromCache $value $cacheName -> $fieldName\")\n        return value\n    }\n\n    fun fromCache() {\n        if (disable) return\n        val f = try {\n            bean!!.javaClass.getField(fieldName)\n        } catch (e: Exception) {\n            Logs.d(\"binding no field: ${e.readableMessage}\")\n            return\n        }\n        when (type) {\n            Type.Text -> f.set(bean, readStringFromCache())\n            Type.TextToInt -> f.set(bean, readStringToIntFromCache())\n            Type.Int -> f.set(bean, readIntFromCache())\n            Type.Bool -> f.set(bean, readBoolFromCache())\n        }\n    }\n\n    fun writeToCache() {\n        if (disable) return\n        val f = try {\n            bean!!.javaClass.getField(fieldName) ?: return\n        } catch (e: Exception) {\n            Logs.d(\"binding no field: ${e.readableMessage}\")\n            return\n        }\n        val value = f.get(bean)\n        when (type) {\n            Type.Text -> {\n                if (value is String) {\n//                    Logs.d(\"writeToCache TEXT $value $cacheName -> $fieldName\")\n                    DataStore.profileCacheStore.putString(cacheName, value)\n                }\n            }\n            Type.TextToInt -> {\n                if (value is Int) {\n//                    Logs.d(\"writeToCache TEXT2INT $value $cacheName -> $fieldName\")\n                    DataStore.profileCacheStore.putString(cacheName, value.toString())\n                }\n            }\n            Type.Int -> {\n                if (value is Int) {\n                    DataStore.profileCacheStore.putInt(cacheName, value)\n                }\n            }\n            Type.Bool -> {\n                if (value is Boolean) {\n                    DataStore.profileCacheStore.putBoolean(cacheName, value)\n                }\n            }\n        }\n    }\n\n    val preference by lazy {\n        pf!!.findPreference<Preference>(cacheName)!!\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/PreferenceBindingManager.kt",
    "content": "package moe.matsuri.nb4a.proxy\n\nimport androidx.preference.PreferenceFragmentCompat\n\n\nclass PreferenceBindingManager {\n    val items = mutableListOf<PreferenceBinding>()\n\n    fun add(b: PreferenceBinding): PreferenceBinding {\n        items.add(b)\n        return b\n    }\n\n    fun fromCacheAll(bean: Any) {\n        items.forEach {\n            it.bean = bean\n            it.fromCache()\n        }\n    }\n\n    fun writeToCacheAll(bean: Any) {\n        items.forEach {\n            it.bean = bean\n            it.writeToCache()\n        }\n    }\n\n    fun setPreferenceFragment(pf: PreferenceFragmentCompat) {\n        items.forEach {\n            it.pf = pf\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSBean.java",
    "content": "package moe.matsuri.nb4a.proxy.anytls;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\n\npublic class AnyTLSBean extends AbstractBean {\n\n    public static final Creator<AnyTLSBean> CREATOR = new CREATOR<AnyTLSBean>() {\n        @NonNull\n        @Override\n        public AnyTLSBean newInstance() {\n            return new AnyTLSBean();\n        }\n\n        @Override\n        public AnyTLSBean[] newArray(int size) {\n            return new AnyTLSBean[size];\n        }\n    };\n    public String password;\n    public String sni;\n    public String alpn;\n    public String certificates;\n    public String utlsFingerprint;\n    public Boolean allowInsecure;\n    // In sing-box, this seemed can be used with REALITY.\n    // But even mihomo appended many options, it still not provide REALITY.\n    // https://github.com/anytls/anytls-go/blob/4636d90462fa21a510420512d7706a9acf69c7b9/docs/faq.md?plain=1#L25-L37\n\n    public String echConfig;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (password == null) password = \"\";\n        if (sni == null) sni = \"\";\n        if (alpn == null) alpn = \"\";\n        if (certificates == null) certificates = \"\";\n        if (utlsFingerprint == null) utlsFingerprint = \"\";\n        if (allowInsecure == null) allowInsecure = false;\n        if (echConfig == null) echConfig = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeString(password);\n        output.writeString(sni);\n        output.writeString(alpn);\n        output.writeString(certificates);\n        output.writeString(utlsFingerprint);\n        output.writeBoolean(allowInsecure);\n        output.writeString(echConfig);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        password = input.readString();\n        sni = input.readString();\n        alpn = input.readString();\n        certificates = input.readString();\n        utlsFingerprint = input.readString();\n        allowInsecure = input.readBoolean();\n        echConfig = input.readString();\n    }\n\n    @NotNull\n    @Override\n    public AnyTLSBean clone() {\n        return KryoConverters.deserialize(new AnyTLSBean(), KryoConverters.serialize(this));\n    }\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSFmt.kt",
    "content": "package moe.matsuri.nb4a.proxy.anytls\n\nimport io.nekohasekai.sagernet.ktx.blankAsNull\nimport io.nekohasekai.sagernet.ktx.linkBuilder\nimport io.nekohasekai.sagernet.ktx.toLink\nimport io.nekohasekai.sagernet.ktx.urlSafe\nimport moe.matsuri.nb4a.SingBoxOptions\nimport moe.matsuri.nb4a.utils.listByLineOrComma\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\n\nfun buildSingBoxOutboundAnyTLSBean(bean: AnyTLSBean): SingBoxOptions.Outbound_AnyTLSOptions {\n    return SingBoxOptions.Outbound_AnyTLSOptions().apply {\n        type = \"anytls\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        password = bean.password\n\n        tls = SingBoxOptions.OutboundTLSOptions().apply {\n            enabled = true\n            server_name = bean.sni.blankAsNull()\n            if (bean.allowInsecure) insecure = true\n            alpn = bean.alpn.blankAsNull()?.listByLineOrComma()\n            bean.certificates.blankAsNull()?.let {\n                certificate = it\n            }\n            bean.utlsFingerprint.blankAsNull()?.let {\n                utls = SingBoxOptions.OutboundUTLSOptions().apply {\n                    enabled = true\n                    fingerprint = it\n                }\n            }\n            bean.echConfig.blankAsNull()?.let {\n                // In new version, some complex options will be deprecated, so we just do this.\n                ech = SingBoxOptions.OutboundECHOptions().apply {\n                    enabled = true\n                    config = listOf(it)\n                }\n            }\n        }\n    }\n}\n\nfun AnyTLSBean.toUri(): String {\n    val builder = linkBuilder()\n        .host(serverAddress)\n        .port(serverPort)\n        .username(password)\n    if (!name.isNullOrBlank()) {\n        builder.encodedFragment(name.urlSafe())\n    }\n    if (allowInsecure) {\n        builder.addQueryParameter(\"insecure\", \"1\")\n    }\n    if (!sni.isNullOrBlank()) {\n        builder.addQueryParameter(\"sni\", sni)\n    }\n    if (!utlsFingerprint.isNullOrBlank()) {\n        builder.addQueryParameter(\"fp\", utlsFingerprint)\n    }\n    return builder.toLink(\"anytls\")\n}\n\nfun parseAnytls(url: String): AnyTLSBean {\n    // https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md\n    val link = url.replace(\"anytls://\", \"https://\").toHttpUrlOrNull() ?: error(\n        \"invalid anytls link $url\"\n    )\n    return AnyTLSBean().apply {\n        serverAddress = link.host\n        serverPort = link.port\n        name = link.fragment\n        password = link.username\n        sni = link.queryParameter(\"sni\") ?: \"\"\n        link.queryParameter(\"insecure\")?.also {\n            allowInsecure = it == \"1\" || it == \"true\"\n        }\n        link.queryParameter(\"fp\")?.let {\n            utlsFingerprint = it\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/anytls/AnyTLSSettingsActivity.kt",
    "content": "package moe.matsuri.nb4a.proxy.anytls\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.ktx.applyDefaultValues\nimport io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity\nimport moe.matsuri.nb4a.proxy.PreferenceBinding\nimport moe.matsuri.nb4a.proxy.PreferenceBindingManager\nimport moe.matsuri.nb4a.proxy.Type\n\nclass AnyTLSSettingsActivity : ProfileSettingsActivity<AnyTLSBean>() {\n    override fun createEntity() = AnyTLSBean().applyDefaultValues()\n\n    private val pbm = PreferenceBindingManager()\n    private val name = pbm.add(PreferenceBinding(Type.Text, \"name\"))\n    private val serverAddress = pbm.add(PreferenceBinding(Type.Text, \"serverAddress\"))\n    private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, \"serverPort\"))\n    private val password = pbm.add(PreferenceBinding(Type.Text, \"password\"))\n    private val sni = pbm.add(PreferenceBinding(Type.Text, \"sni\"))\n    private val alpn = pbm.add(PreferenceBinding(Type.Text, \"alpn\"))\n    private val certificates = pbm.add(PreferenceBinding(Type.Text, \"certificates\"))\n    private val allowInsecure = pbm.add(PreferenceBinding(Type.Bool, \"allowInsecure\"))\n    private val utlsFingerprint = pbm.add(PreferenceBinding(Type.Text, \"utlsFingerprint\"))\n\n    override fun AnyTLSBean.init() {\n        pbm.writeToCacheAll(this)\n\n    }\n\n    override fun AnyTLSBean.serialize() {\n        pbm.fromCacheAll(this)\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?\n    ) {\n        addPreferencesFromResource(R.xml.anytls_preferences)\n\n        findPreference<EditTextPreference>(Key.SERVER_PORT)!!.apply {\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        findPreference<EditTextPreference>(\"password\")!!.apply {\n            summaryProvider = PasswordSummaryProvider\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigBean.java",
    "content": "package moe.matsuri.nb4a.proxy.config;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\nimport com.google.gson.JsonObject;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.fmt.internal.InternalBean;\nimport moe.matsuri.nb4a.utils.JavaUtil;\n\npublic class ConfigBean extends InternalBean {\n\n    public Integer type; // 0=config 1=outbound\n    public String config;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (type == null) type = 0;\n        if (config == null) config = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeInt(type);\n        output.writeString(config);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        type = input.readInt();\n        config = input.readString();\n    }\n\n    @Override\n    public String displayName() {\n        if (JavaUtil.isNotBlank(name)) {\n            return name;\n        } else {\n            return \"Custom \" + Math.abs(hashCode());\n        }\n    }\n\n    public String displayType() {\n        if (type != null && type == 1 && JavaUtil.isNotBlank(config)) {\n            try {\n                JsonObject json = JavaUtil.gson.fromJson(config, JsonObject.class);\n                if (json != null && json.has(\"type\")) {\n                    return json.get(\"type\").getAsString() + \" (sing-box)\";\n                }\n            } catch (Exception ignored) {\n            }\n        }\n        return type != null && type == 0 ? \"sing-box config\" : \"sing-box outbound\";\n    }\n\n    @NotNull\n    @Override\n    public ConfigBean clone() {\n        return KryoConverters.deserialize(new ConfigBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<ConfigBean> CREATOR = new CREATOR<ConfigBean>() {\n        @NonNull\n        @Override\n        public ConfigBean newInstance() {\n            return new ConfigBean();\n        }\n\n        @Override\n        public ConfigBean[] newArray(int size) {\n            return new ConfigBean[size];\n        }\n    };\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/config/ConfigSettingActivity.kt",
    "content": "package moe.matsuri.nb4a.proxy.config\n\nimport android.os.Bundle\nimport androidx.preference.PreferenceDataStore\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.database.preference.OnPreferenceDataStoreChangeListener\nimport io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity\nimport moe.matsuri.nb4a.ui.EditConfigPreference\n\nclass ConfigSettingActivity :\n    ProfileSettingsActivity<ConfigBean>(),\n    OnPreferenceDataStoreChangeListener {\n\n    private val isOutboundOnlyKey = \"isOutboundOnly\"\n\n    override fun createEntity() = ConfigBean()\n\n    override fun ConfigBean.init() {\n        // CustomBean to input\n        DataStore.profileCacheStore.putBoolean(isOutboundOnlyKey, type == 1)\n        DataStore.profileName = name\n        DataStore.serverConfig = config\n    }\n\n    override fun ConfigBean.serialize() {\n        // CustomBean from input\n        type = if (DataStore.profileCacheStore.getBoolean(isOutboundOnlyKey, false)) 1 else 0\n        name = DataStore.profileName\n        config = DataStore.serverConfig\n    }\n\n    override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {\n        if (key != Key.PROFILE_DIRTY) {\n            DataStore.dirty = true\n        }\n    }\n\n    private lateinit var editConfigPreference: EditConfigPreference\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.config_preferences)\n\n        editConfigPreference = findPreference(Key.SERVER_CONFIG)!!\n    }\n\n    override fun onResume() {\n        super.onResume()\n\n        if (::editConfigPreference.isInitialized) {\n            editConfigPreference.notifyChanged()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/neko/NekoBean.java",
    "content": "package moe.matsuri.nb4a.proxy.neko;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\nimport org.json.JSONObject;\n\nimport io.nekohasekai.sagernet.fmt.AbstractBean;\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.ktx.Logs;\n\npublic class NekoBean extends AbstractBean {\n\n    public String plgId;\n    public String protocolId;\n    public JSONObject sharedStorage = new JSONObject();\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n        if (protocolId == null) protocolId = \"\";\n        if (plgId == null) plgId = \"moe.matsuri.plugin.donotexist\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeString(plgId);\n        output.writeString(protocolId);\n        output.writeString(sharedStorage.toString());\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version = input.readInt();\n        super.deserialize(input);\n        plgId = input.readString();\n        protocolId = input.readString();\n        sharedStorage = tryParseJSON(input.readString());\n    }\n\n    @NotNull\n    public static JSONObject tryParseJSON(String input) {\n        JSONObject ret;\n        try {\n            ret = new JSONObject(input);\n        } catch (Exception e) {\n            ret = new JSONObject();\n            Logs.INSTANCE.e(e);\n        }\n        return ret;\n    }\n\n    public String displayType() {\n        return \"invalid\";\n    }\n\n    @Override\n    public boolean canMapping() {\n        return false;\n    }\n\n    @Override\n    public boolean canICMPing() {\n        return false;\n    }\n\n    @Override\n    public boolean canTCPing() {\n        return false;\n    }\n\n    @NotNull\n    @Override\n    public NekoBean clone() {\n        return KryoConverters.deserialize(new NekoBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<NekoBean> CREATOR = new CREATOR<NekoBean>() {\n        @NonNull\n        @Override\n        public NekoBean newInstance() {\n            return new NekoBean();\n        }\n\n        @Override\n        public NekoBean[] newArray(int size) {\n            return new NekoBean[size];\n        }\n    };\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSBean.java",
    "content": "package moe.matsuri.nb4a.proxy.shadowtls;\n\nimport androidx.annotation.NonNull;\n\nimport com.esotericsoftware.kryo.io.ByteBufferInput;\nimport com.esotericsoftware.kryo.io.ByteBufferOutput;\n\nimport org.jetbrains.annotations.NotNull;\n\nimport io.nekohasekai.sagernet.fmt.KryoConverters;\nimport io.nekohasekai.sagernet.fmt.v2ray.StandardV2RayBean;\n\npublic class ShadowTLSBean extends StandardV2RayBean {\n\n    public Integer version;\n    public String password;\n\n    @Override\n    public void initializeDefaultValues() {\n        super.initializeDefaultValues();\n\n        security = \"tls\";\n        if (version == null) version = 3;\n        if (password == null) password = \"\";\n    }\n\n    @Override\n    public void serialize(ByteBufferOutput output) {\n        output.writeInt(0);\n        super.serialize(output);\n        output.writeInt(version);\n        output.writeString(password);\n    }\n\n    @Override\n    public void deserialize(ByteBufferInput input) {\n        int version_ = input.readInt();\n        super.deserialize(input);\n        version = input.readInt();\n        password = input.readString();\n    }\n\n    @NotNull\n    @Override\n    public ShadowTLSBean clone() {\n        return KryoConverters.deserialize(new ShadowTLSBean(), KryoConverters.serialize(this));\n    }\n\n    public static final Creator<ShadowTLSBean> CREATOR = new CREATOR<ShadowTLSBean>() {\n        @NonNull\n        @Override\n        public ShadowTLSBean newInstance() {\n            return new ShadowTLSBean();\n        }\n\n        @Override\n        public ShadowTLSBean[] newArray(int size) {\n            return new ShadowTLSBean[size];\n        }\n    };\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSFmt.kt",
    "content": "package moe.matsuri.nb4a.proxy.shadowtls\n\nimport io.nekohasekai.sagernet.fmt.v2ray.buildSingBoxOutboundTLS\nimport moe.matsuri.nb4a.SingBoxOptions\n\nfun buildSingBoxOutboundShadowTLSBean(bean: ShadowTLSBean): SingBoxOptions.Outbound_ShadowTLSOptions {\n    return SingBoxOptions.Outbound_ShadowTLSOptions().apply {\n        type = \"shadowtls\"\n        server = bean.serverAddress\n        server_port = bean.serverPort\n        version = bean.version\n        password = bean.password\n        tls = buildSingBoxOutboundTLS(bean)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/proxy/shadowtls/ShadowTLSSettingsActivity.kt",
    "content": "package moe.matsuri.nb4a.proxy.shadowtls\n\nimport android.os.Bundle\nimport androidx.preference.EditTextPreference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.preference.EditTextPreferenceModifiers\nimport io.nekohasekai.sagernet.ui.profile.ProfileSettingsActivity\nimport moe.matsuri.nb4a.proxy.PreferenceBinding\nimport moe.matsuri.nb4a.proxy.PreferenceBindingManager\nimport moe.matsuri.nb4a.proxy.Type\n\nclass ShadowTLSSettingsActivity : ProfileSettingsActivity<ShadowTLSBean>() {\n\n    override fun createEntity() = ShadowTLSBean()\n\n    private val pbm = PreferenceBindingManager()\n    private val name = pbm.add(PreferenceBinding(Type.Text, \"name\"))\n    private val serverAddress = pbm.add(PreferenceBinding(Type.Text, \"serverAddress\"))\n    private val serverPort = pbm.add(PreferenceBinding(Type.TextToInt, \"serverPort\"))\n    private val password = pbm.add(PreferenceBinding(Type.Text, \"password\"))\n    private val version = pbm.add(PreferenceBinding(Type.TextToInt, \"version\"))\n    private val sni = pbm.add(PreferenceBinding(Type.Text, \"sni\"))\n    private val alpn = pbm.add(PreferenceBinding(Type.Text, \"alpn\"))\n    private val certificates = pbm.add(PreferenceBinding(Type.Text, \"certificates\"))\n    private val allowInsecure = pbm.add(PreferenceBinding(Type.Bool, \"allowInsecure\"))\n    private val utlsFingerprint = pbm.add(PreferenceBinding(Type.Text, \"utlsFingerprint\"))\n\n    override fun ShadowTLSBean.init() {\n        pbm.writeToCacheAll(this)\n\n    }\n\n    override fun ShadowTLSBean.serialize() {\n        pbm.fromCacheAll(this)\n    }\n\n    override fun PreferenceFragmentCompat.createPreferences(\n        savedInstanceState: Bundle?,\n        rootKey: String?,\n    ) {\n        addPreferencesFromResource(R.xml.shadowtls_preferences)\n        pbm.setPreferenceFragment(this)\n\n        serverPort.preference.apply {\n            this as EditTextPreference\n            setOnBindEditTextListener(EditTextPreferenceModifiers.Port)\n        }\n        password.preference.apply {\n            this as EditTextPreference\n            summaryProvider = PasswordSummaryProvider\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/ColorPickerPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.content.res.Resources\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.GridLayout\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.core.content.res.TypedArrayUtils\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.view.setPadding\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceViewHolder\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.getColorAttr\nimport kotlin.math.roundToInt\n\nclass ColorPickerPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = TypedArrayUtils.getAttr(\n        context,\n        androidx.preference.R.attr.editTextPreferenceStyle,\n        android.R.attr.editTextPreferenceStyle\n    )\n) : Preference(\n    context, attrs, defStyle\n) {\n\n    var inited = false\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n\n        val widgetFrame = holder.findViewById(android.R.id.widget_frame) as LinearLayout\n\n        if (!inited) {\n            inited = true\n\n            widgetFrame.addView(\n                getNekoImageViewAtColor(\n                    context.getColorAttr(R.attr.colorPrimary),\n                    48,\n                    0\n                )\n            )\n            widgetFrame.visibility = View.VISIBLE\n        }\n    }\n\n    fun getNekoImageViewAtColor(color: Int, sizeDp: Int, paddingDp: Int): ImageView {\n        // dp to pixel\n        val factor = context.resources.displayMetrics.density\n        val size = (sizeDp * factor).roundToInt()\n        val paddingSize = (paddingDp * factor).roundToInt()\n\n        return ImageView(context).apply {\n            layoutParams = ViewGroup.LayoutParams(size, size)\n            setPadding(paddingSize)\n            setImageDrawable(getNekoAtColor(resources, color))\n        }\n    }\n\n    fun getNekoAtColor(res: Resources, color: Int): Drawable {\n        val neko = ResourcesCompat.getDrawable(\n            res,\n            R.drawable.ic_baseline_fiber_manual_record_24,\n            null\n        )!!\n        DrawableCompat.setTint(neko.mutate(), color)\n        return neko\n    }\n\n    override fun onClick() {\n        super.onClick()\n\n        lateinit var dialog: AlertDialog\n\n        val grid = GridLayout(context).apply {\n            columnCount = 4\n\n            val colors = context.resources.getIntArray(R.array.material_colors)\n            var i = 0\n\n            for (color in colors) {\n                i++ //Theme.kt\n\n                val themeId = i\n                val view = getNekoImageViewAtColor(color, 64, 0).apply {\n                    setOnClickListener {\n                        persistInt(themeId)\n                        dialog.dismiss()\n                        callChangeListener(themeId)\n                    }\n                }\n                addView(view)\n            }\n\n        }\n\n        dialog = MaterialAlertDialogBuilder(context).setTitle(title)\n            .setView(LinearLayout(context).apply {\n                gravity = Gravity.CENTER\n                layoutParams = ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT\n                )\n                addView(grid)\n            })\n            .setNegativeButton(android.R.string.cancel, null)\n            .show()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/ConnectionTestNotification.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport androidx.core.app.NotificationCompat\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\n\nclass ConnectionTestNotification(val context: Context, val title: String) {\n    private val channelId = \"connection-test\"\n    private val notificationId = 1001\n\n    fun updateNotification(progress: Int, max: Int, finished: Boolean) {\n        try {\n            if (finished) {\n                SagerNet.notification.cancel(notificationId)\n                return\n            }\n            val builder = NotificationCompat.Builder(context, channelId)\n                .setSmallIcon(R.drawable.ic_service_active)\n                .setContentTitle(title)\n                .setOnlyAlertOnce(true)\n                .setContentText(\"$progress / $max\").setProgress(max, progress, false)\n            SagerNet.notification.notify(notificationId, builder.build())\n        } catch (e: Exception) {\n            Logs.w(e)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/Dialogs.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.widget.TextView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.readableMessage\nimport io.nekohasekai.sagernet.ktx.runOnMainDispatcher\n\nobject Dialogs {\n    fun logExceptionAndShow(context: Context, e: Exception, callback: Runnable) {\n        Logs.e(e)\n        runOnMainDispatcher {\n            MaterialAlertDialogBuilder(context)\n                .setTitle(R.string.error_title)\n                .setMessage(e.readableMessage)\n                .setCancelable(false)\n                .setPositiveButton(android.R.string.ok) { _, _ ->\n                    callback.run()\n                }\n                .show()\n        }\n    }\n\n    fun message(context: Context, title: String, message: String) {\n        runOnMainDispatcher {\n            val dialog = MaterialAlertDialogBuilder(context)\n                .setTitle(title)\n                .setMessage(message)\n                .setCancelable(true)\n                .show()\n            dialog.findViewById<TextView>(android.R.id.message)?.apply {\n                setTextIsSelectable(true)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/EditConfigPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.content.Intent\nimport android.util.AttributeSet\nimport androidx.preference.Preference\nimport io.nekohasekai.sagernet.Key\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ui.profile.ConfigEditActivity\n\nclass EditConfigPreference : Preference {\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes)\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context, attrs, defStyleAttr\n    )\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context) : super(context)\n\n    init {\n        intent = Intent(context, ConfigEditActivity::class.java)\n    }\n\n    var configKey = Key.SERVER_CONFIG\n    var useConfigStore = false\n\n    fun useConfigStore(key: String) {\n        try {\n            this.configKey = key\n            useConfigStore = true\n            intent = intent!!.apply {\n                putExtra(\"useConfigStore\", \"1\")\n                putExtra(\"key\", key)\n            }\n        } catch (e: Exception) {\n            Logs.w(e)\n        }\n    }\n\n    override fun getSummary(): CharSequence {\n        val config =\n            (if (useConfigStore) DataStore.configurationStore.getString(configKey) else DataStore.serverConfig)\n                ?: \"\"\n        return if (config.isBlank()) {\n            return app.resources.getString(androidx.preference.R.string.not_set)\n        } else {\n            app.resources.getString(R.string.lines, config.split('\\n').size)\n        }\n    }\n\n    public override fun notifyChanged() {\n        super.notifyChanged()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/ExtendedKeyboard.kt",
    "content": "/*\n * Copyright 2021 Squircle IDE contributors.\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\npackage moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ListAdapter\nimport androidx.recyclerview.widget.RecyclerView\nimport io.nekohasekai.sagernet.databinding.ItemKeyboardKeyBinding\n\nclass ExtendedKeyboard @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : RecyclerView(context, attrs, defStyleAttr) {\n\n    private lateinit var keyAdapter: KeyAdapter\n\n    fun setKeyListener(keyListener: OnKeyListener) {\n        keyAdapter = KeyAdapter(keyListener)\n        adapter = keyAdapter\n    }\n\n    fun submitList(keys: List<String>) {\n        keyAdapter.submitList(keys)\n    }\n\n    private class KeyAdapter(\n        private val keyListener: OnKeyListener\n    ) : ListAdapter<String, KeyAdapter.KeyViewHolder>(diffCallback) {\n\n        companion object {\n            private val diffCallback = object : DiffUtil.ItemCallback<String>() {\n                override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {\n                    return oldItem == newItem\n                }\n\n                override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {\n                    return oldItem == newItem\n                }\n            }\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): KeyViewHolder {\n            return KeyViewHolder.create(parent, keyListener)\n        }\n\n        override fun onBindViewHolder(holder: KeyViewHolder, position: Int) {\n            holder.bind(getItem(position))\n        }\n\n        private class KeyViewHolder(\n            private val binding: ItemKeyboardKeyBinding,\n            private val keyListener: OnKeyListener\n        ) : ViewHolder(binding.root) {\n\n            companion object {\n                fun create(parent: ViewGroup, keyListener: OnKeyListener): KeyViewHolder {\n                    val inflater = LayoutInflater.from(parent.context)\n                    val binding = ItemKeyboardKeyBinding.inflate(inflater, parent, false)\n                    return KeyViewHolder(binding, keyListener)\n                }\n            }\n\n            private lateinit var char: String\n\n            init {\n                itemView.setOnClickListener {\n                    keyListener.onKey(char)\n                }\n            }\n\n            fun bind(item: String) {\n                char = item\n                binding.itemTitle.text = char\n                binding.itemTitle.setTextColor(Color.WHITE)\n            }\n        }\n    }\n\n    fun interface OnKeyListener {\n        fun onKey(char: String)\n    }\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/LongClickListPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.preference.ListPreference\nimport androidx.preference.PreferenceViewHolder\nimport io.nekohasekai.sagernet.R\n\nclass LongClickListPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle\n) : ListPreference(context, attrs, defStyle, 0) {\n    private var mLongClickListener: View.OnLongClickListener? = null\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val itemView: View = holder.itemView\n        itemView.setOnLongClickListener {\n            mLongClickListener?.onLongClick(it) ?: true\n        }\n    }\n\n    fun setOnLongClickListener(longClickListener: View.OnLongClickListener) {\n        this.mLongClickListener = longClickListener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/LongClickMenuPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.preference.PreferenceViewHolder\nimport io.nekohasekai.sagernet.R\n\nclass LongClickMenuPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle\n) : SimpleMenuPreference(context, attrs, defStyle, 0) {\n    private var mLongClickListener: View.OnLongClickListener? = null\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val itemView: View = holder.itemView\n        itemView.setOnLongClickListener {\n            mLongClickListener?.onLongClick(it) ?: true\n        }\n    }\n\n    fun setOnLongClickListener(longClickListener: View.OnLongClickListener) {\n        this.mLongClickListener = longClickListener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/LongClickSwitchPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.core.content.res.TypedArrayUtils\nimport androidx.preference.PreferenceViewHolder\nimport androidx.preference.R\nimport androidx.preference.SwitchPreference\n\nclass LongClickSwitchPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = TypedArrayUtils.getAttr(\n        context, R.attr.switchPreferenceStyle, android.R.attr.switchPreferenceStyle\n    ), defStyleRes: Int = 0\n) : SwitchPreference(\n    context, attrs, defStyleAttr, defStyleRes\n) {\n    private var mLongClickListener: View.OnLongClickListener? = null\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val itemView: View = holder.itemView\n        itemView.setOnLongClickListener {\n            mLongClickListener?.onLongClick(it) ?: true\n        }\n    }\n\n    fun setOnLongClickListener(longClickListener: View.OnLongClickListener) {\n        this.mLongClickListener = longClickListener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/MTUPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.inputmethod.EditorInfo\nimport android.widget.EditText\nimport androidx.preference.ListPreference\nimport androidx.preference.PreferenceViewHolder\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport io.nekohasekai.sagernet.R\n\nclass MTUPreference\n@JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null, defStyle: Int = R.attr.dropdownPreferenceStyle\n) : ListPreference(context, attrs, defStyle, 0) {\n\n    init {\n        setSummaryProvider {\n            value.toString()\n        }\n        dialogLayoutResource = R.layout.layout_mtu_help\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val itemView: View = holder.itemView\n        itemView.setOnLongClickListener {\n            val view = EditText(context).apply {\n                inputType = EditorInfo.TYPE_CLASS_NUMBER\n                setText(preferenceDataStore?.getString(key, \"\") ?: \"\")\n            }\n\n            MaterialAlertDialogBuilder(context).setTitle(\"MTU\")\n                .setView(view)\n                .setPositiveButton(android.R.string.ok) { _, _ ->\n                    val mtu = view.text.toString().toInt()\n                    if (mtu < 1000 || mtu > 10000) return@setPositiveButton\n                    value = mtu.toString()\n                }\n                .setNegativeButton(android.R.string.cancel, null)\n                .show()\n            true\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/SimpleMenuPreference.kt",
    "content": "/*\n * Copyright (C) 2020 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\npackage moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.Spinner\nimport androidx.core.content.ContextCompat\nimport androidx.preference.DropDownPreference\nimport androidx.preference.PreferenceViewHolder\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.ktx.getColorAttr\n\n/**\n * Bend [DropDownPreference] to support\n * [Simple Menus](https://material.google.com/components/menus.html#menus-behavior).\n */\n\n\nopen class SimpleMenuPreference\n@JvmOverloads constructor(\n    context: Context?,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = androidx.preference.R.attr.dropdownPreferenceStyle,\n    defStyleRes: Int = 0\n) : DropDownPreference(context!!, attrs, defStyleAttr, defStyleRes) {\n\n    private lateinit var mAdapter: SimpleMenuAdapter\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val mSpinner = holder.itemView.findViewById<Spinner>(R.id.spinner)\n        mSpinner.layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT\n    }\n\n    override fun createAdapter(): ArrayAdapter<CharSequence?> {\n        mAdapter = SimpleMenuAdapter(getContext(), R.layout.simple_menu_dropdown_item)\n        return mAdapter\n    }\n\n    override fun setValue(value: String?) {\n        super.setValue(value)\n        if (::mAdapter.isInitialized) {\n            mAdapter.currentPosition = entryValues.indexOf(value)\n            mAdapter.notifyDataSetChanged()\n        }\n    }\n\n    private class SimpleMenuAdapter(context: Context, resource: Int) :\n        ArrayAdapter<CharSequence?>(context, resource) {\n\n        var currentPosition = -1\n\n        override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup): View {\n            val view: View = super.getDropDownView(position, convertView, parent)\n            if (position == currentPosition) {\n                view.setBackgroundColor(context.getColorAttr(R.attr.colorMaterial100))\n            } else {\n                view.setBackgroundColor(\n                    ContextCompat.getColor(\n                        context,\n                        R.color.preference_simple_menu_background\n                    )\n                )\n            }\n            return view\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/ui/UrlTestPreference.kt",
    "content": "package moe.matsuri.nb4a.ui\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.EditText\nimport android.widget.LinearLayout\nimport androidx.core.content.res.TypedArrayUtils\nimport androidx.core.view.isVisible\nimport androidx.preference.EditTextPreference\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.database.DataStore\n\nclass UrlTestPreference\n@JvmOverloads\nconstructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = TypedArrayUtils.getAttr(\n        context, R.attr.editTextPreferenceStyle,\n        android.R.attr.editTextPreferenceStyle\n    ),\n    defStyleRes: Int = 0\n) : EditTextPreference(context, attrs, defStyleAttr, defStyleRes) {\n\n    var concurrent: EditText? = null\n\n    init {\n        dialogLayoutResource = R.layout.layout_urltest_preference_dialog\n\n        setOnBindEditTextListener {\n            concurrent = it.rootView.findViewById(R.id.edit_concurrent)\n            concurrent?.apply {\n                setText(DataStore.connectionTestConcurrent.toString())\n            }\n            it.rootView.findViewById<LinearLayout>(R.id.concurrent_layout)?.isVisible = true\n        }\n\n        setOnPreferenceChangeListener { _, _ ->\n            concurrent?.apply {\n                var newConcurrent = text?.toString()?.toIntOrNull()\n                if (newConcurrent == null || newConcurrent <= 0) {\n                    newConcurrent = 5\n                }\n                DataStore.connectionTestConcurrent = newConcurrent\n            }\n            true\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/JavaUtil.java",
    "content": "package moe.matsuri.nb4a.utils;\n\nimport android.annotation.SuppressLint;\nimport android.annotation.TargetApi;\nimport android.app.Application;\nimport android.content.Context;\nimport android.os.Build;\nimport android.text.TextUtils;\nimport android.webkit.WebView;\n\nimport com.google.gson.Gson;\nimport com.google.gson.GsonBuilder;\nimport com.google.gson.ToNumberPolicy;\n\nimport java.io.File;\nimport java.io.RandomAccessFile;\nimport java.lang.reflect.Method;\nimport java.nio.channels.FileLock;\nimport java.util.Arrays;\nimport java.util.HashSet;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport io.nekohasekai.sagernet.BuildConfig;\nimport io.nekohasekai.sagernet.ktx.Logs;\nimport kotlin.text.StringsKt;\n\npublic class JavaUtil {\n\n    // The encoded character of each character escape.\n    // This array functions as the keys of a sorted map, from encoded characters to decoded characters.\n    static final char[] ENCODED_ESCAPES = {'\\\"', '\\'', '\\\\', 'b', 'f', 'n', 'r', 't'};\n\n    // The decoded character of each character escape.\n    // This array functions as the values of a sorted map, from encoded characters to decoded characters.\n    static final char[] DECODED_ESCAPES = {'\\\"', '\\'', '\\\\', '\\b', '\\f', '\\n', '\\r', '\\t'};\n\n    // A pattern that matches an escape.\n    // What follows the escape indicator is captured by group 1=character 2=octal 3=Unicode.\n    static final Pattern PATTERN = Pattern.compile(\"\\\\\\\\(?:(b|t|n|f|r|\\\\\\\"|\\\\\\'|\\\\\\\\)|((?:[0-3]?[0-7])?[0-7])|u+(\\\\p{XDigit}{4}))\");\n\n    // Process the return of webView.evaluateJavascript\n    public static String unescapeString(CharSequence encodedString) {\n        Matcher matcher = PATTERN.matcher(encodedString);\n        StringBuffer decodedString = new StringBuffer();\n        // Find each escape of the encoded string in succession.\n        while (matcher.find()) {\n            char ch;\n            if (matcher.start(1) >= 0) {\n                // Decode a character escape.\n                ch = DECODED_ESCAPES[Arrays.binarySearch(ENCODED_ESCAPES, matcher.group(1).charAt(0))];\n            } else if (matcher.start(2) >= 0) {\n                // Decode an octal escape.\n                ch = (char) (Integer.parseInt(matcher.group(2), 8));\n            } else /* if (matcher.start(3) >= 0) */ {\n                // Decode a Unicode escape.\n                ch = (char) (Integer.parseInt(matcher.group(3), 16));\n            }\n            // Replace the escape with the decoded character.\n            matcher.appendReplacement(decodedString, Matcher.quoteReplacement(String.valueOf(ch)));\n        }\n        // Append the remainder of the encoded string to the decoded string.\n        // The remainder is the longest suffix of the encoded string such that the suffix contains no escapes.\n        matcher.appendTail(decodedString);\n        return new String(decodedString);\n    }\n\n    // Webview Utils\n\n    public static void handleWebviewDir(Context context) {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {\n            return;\n        }\n        try {\n            Set<String> pathSet = new HashSet<>();\n            String suffix;\n            String dataPath = context.getDataDir().getAbsolutePath();\n            String webViewDir = \"/app_webview\";\n            String huaweiWebViewDir = \"/app_hws_webview\";\n            String lockFile = \"/webview_data.lock\";\n            String processName = Application.getProcessName();\n            if (!BuildConfig.APPLICATION_ID.equals(processName)) {//判断不等于默认进程名称\n                suffix = TextUtils.isEmpty(processName) ? context.getPackageName() : processName;\n                WebView.setDataDirectorySuffix(suffix);\n                suffix = \"_\" + suffix;\n                pathSet.add(dataPath + webViewDir + suffix + lockFile);\n                if (checkIsHuaweiRom()) {\n                    pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile);\n                }\n            } else {\n                //主进程\n                suffix = \"_\" + processName;\n                pathSet.add(dataPath + webViewDir + lockFile);//默认未添加进程名后缀\n                pathSet.add(dataPath + webViewDir + suffix + lockFile);//系统自动添加了进程名后缀\n                if (checkIsHuaweiRom()) {//部分华为手机更改了webview目录名\n                    pathSet.add(dataPath + huaweiWebViewDir + lockFile);\n                    pathSet.add(dataPath + huaweiWebViewDir + suffix + lockFile);\n                }\n            }\n            for (String path : pathSet) {\n                File file = new File(path);\n                if (file.exists()) {\n                    tryLockOrRecreateFile(file);\n                    break;\n                }\n            }\n        } catch (Exception e) {\n            Logs.INSTANCE.e(e);\n        }\n    }\n\n    @TargetApi(Build.VERSION_CODES.P)\n    private static void tryLockOrRecreateFile(File file) {\n        try {\n            FileLock tryLock = new RandomAccessFile(file, \"rw\").getChannel().tryLock();\n            if (tryLock != null) {\n                tryLock.close();\n            } else {\n                createFile(file, file.delete());\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n            boolean deleted = false;\n            if (file.exists()) {\n                deleted = file.delete();\n            }\n            createFile(file, deleted);\n        }\n    }\n\n    private static void createFile(File file, boolean deleted) {\n        try {\n            if (deleted && !file.exists()) {\n                file.createNewFile();\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n    }\n\n    private static boolean checkIsHuaweiRom() {\n        return Build.MANUFACTURER.contains(\"HUAWEI\");\n    }\n\n    @SuppressLint(\"PrivateApi\")\n    public static String getProcessName() {\n        if (Build.VERSION.SDK_INT >= 28)\n            return Application.getProcessName();\n\n        // Using the same technique as Application.getProcessName() for older devices\n        // Using reflection since ActivityThread is an internal API\n\n        try {\n            Class<?> activityThread = Class.forName(\"android.app.ActivityThread\");\n            String methodName = \"currentProcessName\";\n            Method getProcessName = activityThread.getDeclaredMethod(methodName);\n            return (String) getProcessName.invoke(null);\n        } catch (Exception e) {\n            return BuildConfig.APPLICATION_ID;\n        }\n    }\n\n    // Old hutool Utils\n\n    public static boolean isNullOrBlank(String str) {\n        return str == null || StringsKt.isBlank(str);\n    }\n\n    public static boolean isNotBlank(String str) {\n        return !isNullOrBlank(str);\n    }\n\n    private static final char[] HEX_ARRAY = \"0123456789abcdef\".toCharArray();\n\n    public static String bytesToHex(byte[] bytes) {\n        char[] hexChars = new char[bytes.length * 2];\n        for (int j = 0; j < bytes.length; j++) {\n            int v = bytes[j] & 0xFF;\n            hexChars[j * 2] = HEX_ARRAY[v >>> 4];\n            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];\n        }\n        return new String(hexChars);\n    }\n\n    public static boolean isEmpty(byte[] array) {\n        return array == null || array.length == 0;\n    }\n\n    // gson\n\n    public static final Gson gson = new GsonBuilder()\n            .setPrettyPrinting()\n            .setNumberToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n            .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n            .setLenient()\n            .disableHtmlEscaping()\n            .create();\n\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/KotlinUtil.kt",
    "content": "package moe.matsuri.nb4a.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.appcompat.content.res.AppCompatResources\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\nimport java.io.File\n\n// SagerNet Class\n\nconst val KB = 1024L\nconst val MB = KB * 1024\nconst val GB = MB * 1024\n\nfun SagerNet.cleanWebview() {\n    var pathToClean = \"app_webview\"\n    if (isBgProcess) pathToClean += \"_$process\"\n    try {\n        val dataDir = filesDir.parentFile!!\n        File(dataDir, \"$pathToClean/BrowserMetrics\").recreate(true)\n        File(dataDir, \"$pathToClean/BrowserMetrics-spare.pma\").recreate(false)\n    } catch (e: Exception) {\n        Logs.e(e)\n    }\n}\n\nfun File.recreate(dir: Boolean) {\n    if (parentFile?.isDirectory != true) return\n    if (dir && !isFile) {\n        if (exists()) deleteRecursively()\n        createNewFile()\n    } else if (!dir && !isDirectory) {\n        if (exists()) delete()\n        mkdir()\n    }\n}\n\n// Context utils\n\n@SuppressLint(\"DiscouragedApi\")\nfun Context.getDrawableByName(name: String?): Drawable? {\n    val resourceId: Int = resources.getIdentifier(name, \"drawable\", packageName)\n    return AppCompatResources.getDrawable(this, resourceId)\n}\n\n// Traffic display\n\nfun Long.toBytesString(): String {\n    val size = this.toDouble()\n    return when {\n        this >= GB -> String.format(\"%.2f GiB\", size / GB)\n        this >= MB -> String.format(\"%.2f MiB\", size / MB)\n        this >= KB -> String.format(\"%.2f KiB\", size / KB)\n        else -> \"$this Bytes\"\n    }\n}\n\n// List\n\nfun String.listByLineOrComma(): List<String> {\n    return this.split(\",\",\"\\n\").map { it.trim() }.filter { it.isNotEmpty() }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/NGUtil.kt",
    "content": "package moe.matsuri.nb4a.utils\n\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.text.Editable\nimport android.util.Base64\nimport io.nekohasekai.sagernet.ktx.Logs\nimport java.net.URLDecoder\nimport java.net.URLEncoder\nimport java.util.*\n\n// Copy form v2rayNG to parse their stupid format\n\nobject NGUtil {\n\n    /**\n     * convert string to editalbe for kotlin\n     *\n     * @param text\n     * @return\n     */\n    fun getEditable(text: String): Editable {\n        return Editable.Factory.getInstance().newEditable(text)\n    }\n\n    /**\n     * find value in array position\n     */\n    fun arrayFind(array: Array<out String>, value: String): Int {\n        for (i in array.indices) {\n            if (array[i] == value) {\n                return i\n            }\n        }\n        return -1\n    }\n\n    /**\n     * parseInt\n     */\n    fun parseInt(str: String): Int {\n        return try {\n            Integer.parseInt(str)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            0\n        }\n    }\n\n    /**\n     * get text from clipboard\n     */\n    fun getClipboard(context: Context): String {\n        return try {\n            val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n            cmb.primaryClip?.getItemAt(0)?.text.toString()\n        } catch (e: Exception) {\n            e.printStackTrace()\n            \"\"\n        }\n    }\n\n    /**\n     * set text to clipboard\n     */\n    fun setClipboard(context: Context, content: String) {\n        try {\n            val cmb = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n            val clipData = ClipData.newPlainText(null, content)\n            cmb.setPrimaryClip(clipData)\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * base64 decode\n     */\n    fun decode(text: String): String {\n        tryDecodeBase64(text)?.let { return it }\n        if (text.endsWith('=')) {\n            // try again for some loosely formatted base64\n            tryDecodeBase64(text.trimEnd('='))?.let { return it }\n        }\n        return \"\"\n    }\n\n    fun tryDecodeBase64(text: String): String? {\n        try {\n            return Base64.decode(text, Base64.NO_WRAP).toString(charset(\"UTF-8\"))\n        } catch (e: Exception) {\n            Logs.i( \"Parse base64 standard failed $e\")\n        }\n        try {\n            return Base64.decode(text, Base64.NO_WRAP.or(Base64.URL_SAFE)).toString(charset(\"UTF-8\"))\n        } catch (e: Exception) {\n            Logs.i( \"Parse base64 url safe failed $e\")\n        }\n        return null\n    }\n\n    /**\n     * base64 encode\n     */\n    fun encode(text: String): String {\n        return try {\n            Base64.encodeToString(text.toByteArray(charset(\"UTF-8\")), Base64.NO_WRAP)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            \"\"\n        }\n    }\n\n    /**\n     * is ip address\n     */\n    fun isIpAddress(value: String): Boolean {\n        try {\n            var addr = value\n            if (addr.isEmpty() || addr.isBlank()) {\n                return false\n            }\n            //CIDR\n            if (addr.indexOf(\"/\") > 0) {\n                val arr = addr.split(\"/\")\n                if (arr.count() == 2 && Integer.parseInt(arr[1]) > 0) {\n                    addr = arr[0]\n                }\n            }\n\n            // \"::ffff:192.168.173.22\"\n            // \"[::ffff:192.168.173.22]:80\"\n            if (addr.startsWith(\"::ffff:\") && '.' in addr) {\n                addr = addr.drop(7)\n            } else if (addr.startsWith(\"[::ffff:\") && '.' in addr) {\n                addr = addr.drop(8).replace(\"]\", \"\")\n            }\n\n            // addr = addr.toLowerCase()\n            val octets = addr.split('.').toTypedArray()\n            if (octets.size == 4) {\n                if(octets[3].indexOf(\":\") > 0) {\n                    addr = addr.substring(0, addr.indexOf(\":\"))\n                }\n                return isIpv4Address(addr)\n            }\n\n            // Ipv6addr [2001:abc::123]:8080\n            return isIpv6Address(addr)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            return false\n        }\n    }\n\n    fun isPureIpAddress(value: String): Boolean {\n        return (isIpv4Address(value) || isIpv6Address(value))\n    }\n\n    fun isIpv4Address(value: String): Boolean {\n        val regV4 = Regex(\"^([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\\\\.([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$\")\n        return regV4.matches(value)\n    }\n\n    fun isIpv6Address(value: String): Boolean {\n        var addr = value\n        if (addr.indexOf(\"[\") == 0 && addr.lastIndexOf(\"]\") > 0) {\n            addr = addr.drop(1)\n            addr = addr.dropLast(addr.count() - addr.lastIndexOf(\"]\"))\n        }\n        val regV6 = Regex(\"^((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*::((?:[0-9A-Fa-f]{1,4}))?((?::[0-9A-Fa-f]{1,4}))*|((?:[0-9A-Fa-f]{1,4}))((?::[0-9A-Fa-f]{1,4})){7}$\")\n        return regV6.matches(addr)\n    }\n\n    private fun isCoreDNSAddress(s: String): Boolean {\n        return s.startsWith(\"https\") || s.startsWith(\"tcp\") || s.startsWith(\"quic\")\n    }\n\n    fun openUri(context: Context, uriString: String) {\n        val uri = Uri.parse(uriString)\n        context.startActivity(Intent(Intent.ACTION_VIEW, uri))\n    }\n\n    /**\n     * uuid\n     */\n    fun getUuid(): String {\n        return try {\n            UUID.randomUUID().toString().replace(\"-\", \"\")\n        } catch (e: Exception) {\n            e.printStackTrace()\n            \"\"\n        }\n    }\n\n    fun urlDecode(url: String): String {\n        return try {\n            URLDecoder.decode(url, \"UTF-8\")\n        } catch (e: Exception) {\n            url\n        }\n    }\n\n    fun urlEncode(url: String): String {\n        return try {\n            URLEncoder.encode(url, \"UTF-8\")\n        } catch (e: Exception) {\n            e.printStackTrace()\n            url\n        }\n    }\n\n    /**\n     * package path\n     */\n    fun packagePath(context: Context): String {\n        var path = context.filesDir.toString()\n        path = path.replace(\"files\", \"\")\n        //path += \"tun2socks\"\n\n        return path\n    }\n\n    /**\n     * readTextFromAssets\n     */\n    fun readTextFromAssets(context: Context, fileName: String): String {\n        val content = context.assets.open(fileName).bufferedReader().use {\n            it.readText()\n        }\n        return content\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/SendLog.kt",
    "content": "package moe.matsuri.nb4a.utils\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.content.FileProvider\nimport io.nekohasekai.sagernet.BuildConfig\nimport io.nekohasekai.sagernet.R\nimport io.nekohasekai.sagernet.SagerNet\nimport io.nekohasekai.sagernet.ktx.Logs\nimport io.nekohasekai.sagernet.ktx.app\nimport io.nekohasekai.sagernet.ktx.use\nimport io.nekohasekai.sagernet.utils.CrashHandler\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.IOException\n\nobject SendLog {\n    // Create full log and send\n    fun sendLog(context: Context, title: String) {\n        val logFile = File.createTempFile(\n            \"$title \",\n            \".log\",\n            File(app.cacheDir, \"log\").also { it.mkdirs() })\n\n        var report = CrashHandler.buildReportHeader()\n\n        report += \"Logcat: \\n\\n\"\n\n        logFile.writeText(report)\n\n        try {\n            Runtime.getRuntime().exec(arrayOf(\"logcat\", \"-d\")).inputStream.use(\n                FileOutputStream(\n                    logFile, true\n                )\n            )\n            logFile.appendText(\"\\n\")\n        } catch (e: IOException) {\n            Logs.w(e)\n            logFile.appendText(\"Export logcat error: \" + CrashHandler.formatThrowable(e))\n        }\n\n        logFile.appendText(\"\\n\")\n        logFile.appendBytes(getNekoLog(0))\n\n        context.startActivity(\n            Intent.createChooser(\n                Intent(Intent.ACTION_SEND).setType(\"text/x-log\")\n                    .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n                    .putExtra(\n                        Intent.EXTRA_STREAM, FileProvider.getUriForFile(\n                            context, BuildConfig.APPLICATION_ID + \".cache\", logFile\n                        )\n                    ), context.getString(R.string.abc_shareactionprovider_share_with)\n            )\n        )\n    }\n\n    // Get log bytes from neko.log\n    fun getNekoLog(max: Long): ByteArray {\n        return try {\n            val file = File(\n                SagerNet.application.cacheDir,\n                \"neko.log\"\n            )\n            val len = file.length()\n            val stream = FileInputStream(file)\n            if (max in 1 until len) {\n                stream.skip(len - max) // TODO string?\n            }\n            stream.use { it.readBytes() }\n        } catch (e: Exception) {\n            e.stackTraceToString().toByteArray()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/Util.kt",
    "content": "package moe.matsuri.nb4a.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.Base64\nimport libcore.StringBox\nimport java.io.ByteArrayOutputStream\nimport java.net.URLDecoder\nimport java.nio.charset.StandardCharsets\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.zip.Deflater\nimport java.util.zip.Inflater\n\nobject Util {\n\n    /**\n     * 取两个文本之间的文本值\n     *\n     * @param text  源文本 比如：欲取全文本为 12345\n     * @param left  文本前面\n     * @param right 后面文本\n     * @return 返回 String\n     */\n    fun getSubString(text: String, left: String?, right: String?): String {\n        var zLen: Int\n        if (left.isNullOrEmpty()) {\n            zLen = 0\n        } else {\n            zLen = text.indexOf(left)\n            if (zLen > -1) {\n                zLen += left.length\n            } else {\n                zLen = 0\n            }\n        }\n        var yLen = if (right == null) -1 else text.indexOf(right, zLen)\n        if (yLen < 0 || right.isNullOrEmpty()) {\n            yLen = text.length\n        }\n        return text.substring(zLen, yLen)\n    }\n\n    // Base64 for all\n\n    fun b64EncodeUrlSafe(s: String): String {\n        return b64EncodeUrlSafe(s.toByteArray())\n    }\n\n    fun b64EncodeUrlSafe(b: ByteArray): String {\n        return String(Base64.encode(b, Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE))\n    }\n\n    // v2rayN Style\n    fun b64EncodeOneLine(b: ByteArray): String {\n        return String(Base64.encode(b, Base64.NO_WRAP))\n    }\n\n    fun b64EncodeDefault(b: ByteArray): String {\n        return String(Base64.encode(b, Base64.DEFAULT))\n    }\n\n    fun b64Decode(b: String): ByteArray {\n        var ret: ByteArray? = null\n\n        // padding 自动处理，不用理\n        // URLSafe 需要替换这两个，不要用 URL_SAFE 否则处理非 Safe 的时候会乱码\n        val str = b.replace(\"-\", \"+\").replace(\"_\", \"/\")\n\n        val flags = listOf(\n            Base64.DEFAULT, // 多行\n            Base64.NO_WRAP, // 单行\n        )\n\n        for (flag in flags) {\n            try {\n                ret = Base64.decode(str, flag)\n            } catch (_: Exception) {\n            }\n            if (ret != null) return ret\n        }\n\n        throw IllegalStateException(\"Cannot decode base64\")\n    }\n\n    fun zlibCompress(input: ByteArray, level: Int): ByteArray {\n        // Compress the bytes\n        // 1 to 4 bytes/char for UTF-8\n        val output = ByteArray(input.size * 4)\n        val compressor = Deflater(level).apply {\n            setInput(input)\n            finish()\n        }\n        val compressedDataLength: Int = compressor.deflate(output)\n        compressor.end()\n        return output.copyOfRange(0, compressedDataLength)\n    }\n\n    fun zlibDecompress(input: ByteArray): ByteArray {\n        val inflater = Inflater()\n        val outputStream = ByteArrayOutputStream()\n\n        return outputStream.use {\n            val buffer = ByteArray(1024)\n\n            inflater.setInput(input)\n\n            var count = -1\n            while (count != 0) {\n                count = inflater.inflate(buffer)\n                outputStream.write(buffer, 0, count)\n            }\n\n            inflater.end()\n            outputStream.toByteArray()\n        }\n    }\n\n    fun map2StringMap(m: Map<*, *>): MutableMap<String, Any?> {\n        val o = mutableMapOf<String, Any?>()\n        m.forEach {\n            if (it.key is String) {\n                o[it.key as String] = it.value as Any\n            }\n        }\n        return o\n    }\n\n    fun mergeMap(dst: MutableMap<String, Any?>, src: Map<String, Any?>): MutableMap<String, Any?> {\n        src.forEach { (k, v) ->\n            if (v is Map<*, *> && dst[k] is Map<*, *>) {\n                val currentMap = (dst[k] as Map<*, *>).toMutableMap()\n                dst[k] = mergeMap(map2StringMap(currentMap), map2StringMap(v))\n            } else if (v is List<*>) {\n                if (k.startsWith(\"+\")) {  // prepend\n                    val dstKey = k.removePrefix(\"+\")\n                    var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()\n                    currentList = (v + currentList).toMutableList()\n                    dst[dstKey] = currentList\n                } else if (k.endsWith(\"+\")) {  // append\n                    val dstKey = k.removeSuffix(\"+\")\n                    var currentList = (dst[dstKey] as? List<*>)?.toMutableList() ?: mutableListOf()\n                    currentList = (currentList + v).toMutableList()\n                    dst[dstKey] = currentList\n                } else {\n                    dst[k] = v\n                }\n            } else {\n                dst[k] = v\n            }\n        }\n        return dst\n    }\n\n    fun mergeJSON(dst: MutableMap<String, Any?>, j: String) {\n        if (j.isBlank()) return\n        val src = JavaUtil.gson.fromJson(j, dst.javaClass)\n        mergeMap(dst, src)\n    }\n\n    // Format Time\n\n    @SuppressLint(\"SimpleDateFormat\")\n    val sdf1 = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")\n\n    fun timeStamp2Text(t: Long): String {\n        return sdf1.format(Date(t))\n    }\n\n    fun tryToSetField(o: Any, name: String, value: Any) {\n        try {\n            o.javaClass.getField(name).set(o, value)\n        } catch (_: Exception) {\n        }\n    }\n\n    @SuppressLint(\"WrongConstant\")\n    fun collapseStatusBar(context: Context) {\n        try {\n            val statusBarManager = context.getSystemService(\"statusbar\")\n            val collapse = statusBarManager.javaClass.getMethod(\"collapsePanels\")\n            collapse.invoke(statusBarManager)\n        } catch (_: Exception) {\n        }\n    }\n\n    fun getStringBox(b: StringBox?): String {\n        if (b != null && b.value != null) {\n            return b.value\n        }\n        return \"\"\n    }\n\n    fun decodeFilename(headerValue: String): String {\n        val regex = Regex(\"filename\\\\*=[^']*''(.+)\")\n        val match = regex.find(headerValue)\n        val encoded = match?.groupValues?.get(1) ?: \"\"\n        return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name())\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/moe/matsuri/nb4a/utils/WebViewUtil.kt",
    "content": "package moe.matsuri.nb4a.utils\n\nimport android.os.Build\nimport android.webkit.WebResourceError\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebResourceResponse\nimport android.webkit.WebView\nimport io.nekohasekai.sagernet.ktx.Logs\nimport java.io.ByteArrayInputStream\nimport java.io.InputStream\n\nobject WebViewUtil {\n    fun onReceivedError(\n        view: WebView?, request: WebResourceRequest?, error: WebResourceError?\n    ) {\n        if (Build.VERSION.SDK_INT >= 23 && error != null) {\n            Logs.e(\"WebView error description: ${error.description}\")\n        }\n        Logs.e(\"WebView error: ${error.toString()}\")\n    }\n\n    fun interceptRequest(\n        res: (String) -> InputStream?, view: WebView?, request: WebResourceRequest?\n    ): WebResourceResponse {\n        val path = request?.url?.path ?: \"404\"\n        val input = res(path)\n        var mime = \"text/plain\"\n        if (path.endsWith(\".js\")) mime = \"application/javascript\"\n        if (path.endsWith(\".html\")) mime = \"text/html\"\n        return if (input != null) {\n            WebResourceResponse(mime, \"UTF-8\", input)\n        } else {\n            WebResourceResponse(\n                \"text/plain\", \"UTF-8\", ByteArrayInputStream(\"\".toByteArray())\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/color/chip_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!-- 24% opacity -->\n    <item android:alpha=\"0.24\" android:color=\"?attr/primaryOrTextPrimary\" android:state_enabled=\"true\" android:state_selected=\"true\" />\n    <item android:alpha=\"0.24\" android:color=\"?attr/primaryOrTextPrimary\" android:state_checked=\"true\" android:state_enabled=\"true\" />\n    <!-- 12% of 87% opacity -->\n    <item android:alpha=\"0.10\" android:color=\"?attr/colorOnSurface\" android:state_enabled=\"true\" />\n    <item android:alpha=\"0.12\" android:color=\"?attr/colorOnSurface\" />\n\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/chip_ripple_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:alpha=\"@dimen/mtrl_low_ripple_pressed_alpha\" android:color=\"?attr/primaryOrTextPrimary\" android:state_pressed=\"true\" />\n    <item android:alpha=\"@dimen/mtrl_low_ripple_focused_alpha\" android:color=\"?attr/colorOnSurface\" android:state_focused=\"true\" android:state_hovered=\"true\" />\n    <item android:alpha=\"@dimen/mtrl_low_ripple_focused_alpha\" android:color=\"?attr/colorOnSurface\" android:state_focused=\"true\" />\n    <item android:alpha=\"@dimen/mtrl_low_ripple_hovered_alpha\" android:color=\"?attr/colorOnSurface\" android:state_hovered=\"true\" />\n    <item android:alpha=\"@dimen/mtrl_low_ripple_default_alpha\" android:color=\"?attr/colorOnSurface\" />\n\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/chip_text_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item android:color=\"?attr/primaryOrTextPrimary\" android:state_enabled=\"true\" android:state_selected=\"true\" />\n    <item android:color=\"?attr/primaryOrTextPrimary\" android:state_checked=\"true\" android:state_enabled=\"true\" />\n    <!-- 87% opacity. -->\n    <item android:alpha=\"0.87\" android:color=\"?attr/colorOnSurface\" android:state_enabled=\"true\" />\n    <!-- 38% of 87% opacity. -->\n    <item android:alpha=\"0.33\" android:color=\"?attr/colorOnSurface\" />\n\n</selector>\n"
  },
  {
    "path": "app/src/main/res/color/navigation_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?attr/primaryOrTextSecondary\" android:state_checked=\"true\" />\n    <item android:color=\"?android:textColorPrimary\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/color/navigation_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?itemShapeFillColor\" android:state_checked=\"true\" />\n    <item android:color=\"@android:color/transparent\" android:state_checked=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/baseline_arrow_back_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_construction_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M13.7827,15.1719l2.1213,-2.1213l5.9962,5.9962l-2.1213,2.1213z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M17.5,10c1.93,0 3.5,-1.57 3.5,-3.5c0,-0.58 -0.16,-1.12 -0.41,-1.6l-2.7,2.7L16.4,6.11l2.7,-2.7C18.62,3.16 18.08,3 17.5,3C15.57,3 14,4.57 14,6.5c0,0.41 0.08,0.8 0.21,1.16l-1.85,1.85l-1.78,-1.78l0.71,-0.71L9.88,5.61L12,3.49c-1.17,-1.17 -3.07,-1.17 -4.24,0L4.22,7.03l1.41,1.41H2.81L2.1,9.15l3.54,3.54l0.71,-0.71V9.15l1.41,1.41l0.71,-0.71l1.78,1.78l-7.41,7.41l2.12,2.12L16.34,9.79C16.7,9.92 17.09,10 17.5,10z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_delete_sweep_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M15,16h4v2h-4zM15,8h7v2h-7zM15,12h6v2h-6zM3,18c0,1.1 0.9,2 2,2h6c1.1,0 2,-0.9 2,-2L13,8L3,8v10zM14,5h-3l-1,-1L6,4L5,5L2,5v2h12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_developer_board_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M22,9L22,7h-2L20,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2v-2h-2L20,9h2zM18,19L4,19L4,5h14v14zM6,13h5v4L6,17zM12,7h4v3h-4zM6,7h5v5L6,12zM12,11h4v6h-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_flight_takeoff_24.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M2.5,19h19v2h-19V19zM22.07,9.64c-0.21,-0.8 -1.04,-1.28 -1.84,-1.06L14.92,10l-6.9,-6.43L6.09,4.08l4.14,7.17l-4.97,1.33l-1.97,-1.54l-1.45,0.39l2.59,4.49c0,0 7.12,-1.9 16.57,-4.43C21.81,11.26 22.28,10.44 22.07,9.64z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_keyboard_tab_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M11.59,7.41L15.17,11H1v2h14.17l-3.59,3.59L13,18l6,-6 -6,-6 -1.41,1.41zM20,6v12h2V6h-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_public_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_redo_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_save_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_send_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_translate_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12.87,15.07l-2.54,-2.51 0.03,-0.03c1.74,-1.94 2.98,-4.17 3.71,-6.53L17,6L17,4h-7L10,2L8,2v2L1,4v1.99h11.17C11.5,7.92 10.44,9.75 9,11.35 8.07,10.32 7.3,9.19 6.69,8h-2c0.73,1.63 1.73,3.17 2.98,4.56l-5.09,5.02L4,19l5,-5 3.11,3.11 0.76,-2.04zM18.5,10h-2L12,22h2l1.12,-3h4.75L21,22h2l-4.5,-12zM15.88,17l1.62,-4.33L19.12,17h-3.24z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_undo_24.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"#000000\" android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_widgets_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M13,13v8h8v-8h-8zM3,21h8v-8L3,13v8zM3,3v8h8L11,3L3,3zM16.66,1.69L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/baseline_wrap_text_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4,19h6v-2L4,17v2zM20,5L4,5v2h16L20,5zM17,11L4,11v2h13.25c1.1,0 2,0.9 2,2s-0.9,2 -2,2L15,17v-2l-3,3 3,3v-2h2c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_copyright.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M10.08,10.86c0.05,-0.33 0.16,-0.62 0.3,-0.87s0.34,-0.46 0.59,-0.62c0.24,-0.15 0.54,-0.22 0.91,-0.23 0.23,0.01 0.44,0.05 0.63,0.13 0.2,0.09 0.38,0.21 0.52,0.36s0.25,0.33 0.34,0.53 0.13,0.42 0.14,0.64h1.79c-0.02,-0.47 -0.11,-0.9 -0.28,-1.29s-0.4,-0.73 -0.7,-1.01 -0.66,-0.5 -1.08,-0.66 -0.88,-0.23 -1.39,-0.23c-0.65,0 -1.22,0.11 -1.7,0.34s-0.88,0.53 -1.2,0.92 -0.56,0.84 -0.71,1.36S8,11.29 8,11.87v0.27c0,0.58 0.08,1.12 0.23,1.64s0.39,0.97 0.71,1.35 0.72,0.69 1.2,0.91 1.05,0.34 1.7,0.34c0.47,0 0.91,-0.08 1.32,-0.23s0.77,-0.36 1.08,-0.63 0.56,-0.58 0.74,-0.94 0.29,-0.74 0.3,-1.15h-1.79c-0.01,0.21 -0.06,0.4 -0.15,0.58s-0.21,0.33 -0.36,0.46 -0.32,0.23 -0.52,0.3c-0.19,0.07 -0.39,0.09 -0.6,0.1 -0.36,-0.01 -0.66,-0.08 -0.89,-0.23 -0.25,-0.16 -0.45,-0.37 -0.59,-0.62s-0.25,-0.55 -0.3,-0.88 -0.08,-0.67 -0.08,-1v-0.27c0,-0.35 0.03,-0.68 0.08,-1.01zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_delete.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n            android:fillColor=\"#FFFFFFFF\"\n            android:pathData=\"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_description.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.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,18L8,18v-2h8v2zM16,14L8,14v-2h8v2zM13,9L13,3.5L18.5,9L13,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_dns.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,13H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-6c0,-0.55 -0.45,-1 -1,-1zM7,19c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM20,3H4c-0.55,0 -1,0.45 -1,1v6c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1V4c0,-0.55 -0.45,-1 -1,-1zM7,9c-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/src/main/res/drawable/ic_action_done.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_lock.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_lock_open.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M12,17c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6h1.9c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM18,20L6,20L6,10h12v10z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_note_add.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.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,16h-3v3h-2v-3L8,16v-2h3v-3h2v3h3v2zM13,9L13,3.5L18.5,9L13,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_action_settings.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_app_shortcut_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!--\n  Copyright 2017 Google Inc.\n\n  Licensed under the Apache License, Version 2.0 (the \"License\"); you may not use this file except\n  in compliance with the License. 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 distributed under the License\n  is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express\n  or implied. See the License for the specific language governing permissions and limitations under\n  the License.\n  -->\n\n<shape\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"@color/material_grey_100\" />\n    <size android:width=\"44dp\" android:height=\"44dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/ic_av_playlist_add.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.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M14,10L2,10v2h12v-2zM14,6L2,6v2h12L14,6zM18,14v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zM2,16h8v-2L2,14v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_add_road_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,18l0,-3l-2,0l0,3l-3,0l0,2l3,0l0,3l2,0l0,-3l3,0l0,-2z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M18,4h2v9h-2z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4,4h2v16h-2z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M11,4h2v4h-2z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M11,10h2v4h-2z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M11,16h2v4h-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_airplanemode_active_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M22,16v-2l-8.5,-5V3.5C13.5,2.67 12.83,2 12,2s-1.5,0.67 -1.5,1.5V9L2,14v2l8.5,-2.5V19L8,20.5L8,22l4,-1l4,1l0,-1.5L13.5,19v-5.5L22,16z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_android_24.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:pathData=\"M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z\"\n        android:fillColor=\"@android:color/white\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_bug_report_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\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/src/main/res/drawable/ic_baseline_camera_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_card_giftcard_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,6h-2.18c0.11,-0.31 0.18,-0.65 0.18,-1 0,-1.66 -1.34,-3 -3,-3 -1.05,0 -1.96,0.54 -2.5,1.35l-0.5,0.67 -0.5,-0.68C10.96,2.54 10.05,2 9,2 7.34,2 6,3.34 6,5c0,0.35 0.07,0.69 0.18,1L4,6c-1.11,0 -1.99,0.89 -1.99,2L2,19c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM15,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM9,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM20,19L4,19v-2h16v2zM20,14L4,14L4,8h5.08L7,10.83 8.62,12 11,8.76l1,-1.36 1,1.36L15.38,12 17,10.83 14.92,8L20,8v6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_cast_connected_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_center_focus_weak_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M5,15L3,15v4c0,1.1 0.9,2 2,2h4v-2L5,19v-4zM5,5h4L9,3L5,3c-1.1,0 -2,0.9 -2,2v4h2L5,5zM19,3h-4v2h4v4h2L21,5c0,-1.1 -0.9,-2 -2,-2zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2v4zM12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-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/src/main/res/drawable/ic_baseline_color_lens_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9c0.83,0 1.5,-0.67 1.5,-1.5 0,-0.39 -0.15,-0.74 -0.39,-1.01 -0.23,-0.26 -0.38,-0.61 -0.38,-0.99 0,-0.83 0.67,-1.5 1.5,-1.5L16,16c2.76,0 5,-2.24 5,-5 0,-4.42 -4.03,-8 -9,-8zM6.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,9 6.5,9 8,9.67 8,10.5 7.33,12 6.5,12zM9.5,8C8.67,8 8,7.33 8,6.5S8.67,5 9.5,5s1.5,0.67 1.5,1.5S10.33,8 9.5,8zM14.5,8c-0.83,0 -1.5,-0.67 -1.5,-1.5S13.67,5 14.5,5s1.5,0.67 1.5,1.5S15.33,8 14.5,8zM17.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S16.67,9 17.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_compare_arrows_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M9.01,14H2v2h7.01v3L13,15l-3.99,-4V14zM14.99,13v-3H22V8h-7.01V5L11,9L14.99,13z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_domain_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,7V3H2v18h20V7H12zM6,19H4v-2h2V19zM6,15H4v-2h2V15zM6,11H4V9h2V11zM6,7H4V5h2V7zM10,19H8v-2h2V19zM10,15H8v-2h2V15zM10,11H8V9h2V11zM10,7H8V5h2V7zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2V9h8V19zM18,11h-2v2h2V11zM18,15h-2v2h2V15z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_download_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_emoji_emotions_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12c0,5.52 4.47,10 9.99,10C17.52,22 22,17.52 22,12C22,6.48 17.52,2 11.99,2zM8.5,8C9.33,8 10,8.67 10,9.5S9.33,11 8.5,11S7,10.33 7,9.5S7.67,8 8.5,8zM12,18c-2.28,0 -4.22,-1.66 -5,-4h10C16.22,16.34 14.28,18 12,18zM15.5,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S14.67,8 15.5,8S17,8.67 17,9.5S16.33,11 15.5,11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_fast_forward_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_fiber_manual_record_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_fingerprint_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\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/src/main/res/drawable/ic_baseline_flip_camera_android_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M9,12c0,1.66 1.34,3 3,3s3,-1.34 3,-3s-1.34,-3 -3,-3S9,10.34 9,12z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M8,10V8H5.09C6.47,5.61 9.05,4 12,4c3.72,0 6.85,2.56 7.74,6h2.06c-0.93,-4.56 -4.96,-8 -9.8,-8C8.73,2 5.82,3.58 4,6.01V4H2v6H8z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16,14v2h2.91c-1.38,2.39 -3.96,4 -6.91,4c-3.72,0 -6.85,-2.56 -7.74,-6H2.2c0.93,4.56 4.96,8 9.8,8c3.27,0 6.18,-1.58 8,-4.01V20h2v-6H16z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_format_align_left_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_grid_3x3_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,10V8h-4V4h-2v4h-4V4H8v4H4v2h4v4H4v2h4v4h2v-4h4v4h2v-4h4v-2h-4v-4H20zM14,14h-4v-4h4V14z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_home_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_http_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4.5,11h-2L2.5,9L1,9v6h1.5v-2.5h2L4.5,15L6,15L6,9L4.5,9v2zM7,10.5h1.5L8.5,15L10,15v-4.5h1.5L11.5,9L7,9v1.5zM12.5,10.5L14,10.5L14,15h1.5v-4.5L17,10.5L17,9h-4.5v1.5zM21.5,9L18,9v6h1.5v-2h2c0.8,0 1.5,-0.7 1.5,-1.5v-1c0,-0.8 -0.7,-1.5 -1.5,-1.5zM21.5,11.5h-2v-1h2v1z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_https_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_import_contacts_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M17.5,4.5c-1.95,0 -4.05,0.4 -5.5,1.5c-1.45,-1.1 -3.55,-1.5 -5.5,-1.5S2.45,4.9 1,6v14.65c0,0.65 0.73,0.45 0.75,0.45C3.1,20.45 5.05,20 6.5,20c1.95,0 4.05,0.4 5.5,1.5c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.65,0 3.35,0.3 4.75,1.05C22.66,21.26 23,20.86 23,20.6V6C21.51,4.88 19.37,4.5 17.5,4.5zM21,18.5c-1.1,-0.35 -2.3,-0.5 -3.5,-0.5c-1.7,0 -4.15,0.65 -5.5,1.5V8c1.35,-0.85 3.8,-1.5 5.5,-1.5c1.2,0 2.4,0.15 3.5,0.5V18.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_info_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_layers_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_legend_toggle_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,15H4v-2h16V15zM20,17H4v2h16V17zM15,11l5,-3.55L20,5l-5,3.55L10,5L4,8.66L4,11l5.92,-3.61L15,11z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_link_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M3.9,12c0,-1.71 1.39,-3.1 3.1,-3.1h4L11,7L7,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5h4v-1.9L7,15.1c-1.71,0 -3.1,-1.39 -3.1,-3.1zM8,13h8v-2L8,11v2zM17,7h-4v1.9h4c1.71,0 3.1,1.39 3.1,3.1s-1.39,3.1 -3.1,3.1h-4L13,17h4c2.76,0 5,-2.24 5,-5s-2.24,-5 -5,-5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_local_bar_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M21,5V3H3v2l8,9v5H6v2h12v-2h-5v-5l8,-9zM7.43,7L5.66,5h12.69l-1.78,2H7.43z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_location_on_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_lock_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_low_priority_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M14,5h8v2h-8zM14,10.5h8v2h-8zM14,16h8v2h-8zM2,11.5C2,15.08 4.92,18 8.5,18L9,18v2l3,-3 -3,-3v2h-0.5C6.02,16 4,13.98 4,11.5S6.02,7 8.5,7L12,7L12,5L8.5,5C4.92,5 2,7.92 2,11.5z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_manage_search_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M7,9H2V7h5V9zM7,12H2v2h5V12zM20.59,19l-3.83,-3.83C15.96,15.69 15.02,16 14,16c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5s5,2.24 5,5c0,1.02 -0.31,1.96 -0.83,2.75L22,17.59L20.59,19zM17,11c0,-1.65 -1.35,-3 -3,-3s-3,1.35 -3,3s1.35,3 3,3S17,12.65 17,11zM2,19h10v-2H2V19z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_more_vert_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,8c1.1,0 2,-0.9 2,-2s-0.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,-2zM12,16c-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/src/main/res/drawable/ic_baseline_multiline_chart_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M22,6.92l-1.41,-1.41 -2.85,3.21C15.68,6.4 12.83,5 9.61,5 6.72,5 4.07,6.16 2,8l1.42,1.42C5.12,7.93 7.27,7 9.61,7c2.74,0 5.09,1.26 6.77,3.24l-2.88,3.24 -4,-4L2,16.99l1.5,1.5 6,-6.01 4,4 4.05,-4.55c0.75,1.35 1.25,2.9 1.44,4.55H21c-0.22,-2.3 -0.95,-4.39 -2.04,-6.14L22,6.92z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_multiple_stop_24.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M17,4l4,4l-4,4V9h-4V7h4V4zM10,7C9.45,7 9,7.45 9,8s0.45,1 1,1s1,-0.45 1,-1S10.55,7 10,7zM6,7C5.45,7 5,7.45 5,8s0.45,1 1,1s1,-0.45 1,-1S6.55,7 6,7zM7,17h4v-2H7v-3l-4,4l4,4V17zM14,17c0.55,0 1,-0.45 1,-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1C13,16.55 13.45,17 14,17zM18,17c0.55,0 1,-0.45 1,-1c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1C17,16.55 17.45,17 18,17z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_nat_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M6.82,13H11v-2H6.82C6.4,9.84 5.3,9 4,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3C5.3,15 6.4,14.16 6.82,13zM4,13c-0.55,0 -1,-0.45 -1,-1c0,-0.55 0.45,-1 1,-1s1,0.45 1,1C5,12.55 4.55,13 4,13z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M23,12l-4,-3v2h-4.05C14.45,5.95 10.19,2 5,2v2c4.42,0 8,3.58 8,8s-3.58,8 -8,8v2c5.19,0 9.45,-3.95 9.95,-9H19v2L23,12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_nfc_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,2L4,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,20L4,20L4,4h16v16zM18,6h-5c-1.1,0 -2,0.9 -2,2v2.28c-0.6,0.35 -1,0.98 -1,1.72 0,1.1 0.9,2 2,2s2,-0.9 2,-2c0,-0.74 -0.4,-1.38 -1,-1.72L13,8h3v8L8,16L8,8h2L10,6L6,6v12h12L18,6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_no_encryption_gmailerrorred_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2h-4.27L20,17.17V10c0,-1.1 -0.9,-2 -2,-2h-1V6c0,-2.76 -2.24,-5 -5,-5C9.79,1 7.93,2.45 7.27,4.44L8.9,6.07V6zM2.1,2.1L0.69,3.51L5.3,8.13C4.55,8.42 4,9.15 4,10v10c0,1.1 0.9,2 2,2h12c0.34,0 0.65,-0.09 0.93,-0.24l1.56,1.56l1.41,-1.41L2.1,2.1zM12,17c-1.1,0 -2,-0.9 -2,-2c0,-0.59 0.27,-1.12 0.68,-1.49l2.81,2.81C13.12,16.73 12.59,17 12,17z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_person_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_push_pin_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16,9V4l1,0c0.55,0 1,-0.45 1,-1v0c0,-0.55 -0.45,-1 -1,-1H7C6.45,2 6,2.45 6,3v0c0,0.55 0.45,1 1,1l1,0v5c0,1.66 -1.34,3 -3,3h0v2h5.97v7l1,1l1,-1v-7H19v-2h0C17.34,12 16,10.66 16,9z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_refresh_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_rule_folder_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,6h-8l-2,-2H4C2.9,4 2.01,4.9 2.01,6L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8C22,6.9 21.1,6 20,6zM7.83,16L5,13.17l1.41,-1.41l1.41,1.41l3.54,-3.54l1.41,1.41L7.83,16zM17.41,13L19,14.59L17.59,16L16,14.41L14.41,16L13,14.59L14.59,13L13,11.41L14.41,10L16,11.59L17.59,10L19,11.41L17.41,13z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_running_with_errors_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M22,10v8h-2v-8H22zM20,20v2h2v-2H20zM18,17.29C16.53,18.95 14.39,20 12,20c-4.41,0 -8,-3.59 -8,-8c0,-4.41 3.59,-8 8,-8v9l7.55,-7.55C17.72,3.34 15.02,2 12,2C6.48,2 2,6.48 2,12c0,5.52 4.48,10 10,10c2.25,0 4.33,-0.74 6,-2V17.29z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_sanitizer_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M15.5,6.5C15.5,5.66 17,4 17,4s1.5,1.66 1.5,2.5C18.5,7.33 17.83,8 17,8S15.5,7.33 15.5,6.5zM19.5,15c1.38,0 2.5,-1.12 2.5,-2.5c0,-1.67 -2.5,-4.5 -2.5,-4.5S17,10.83 17,12.5C17,13.88 18.12,15 19.5,15zM13,14h-2v-2H9v2H7v2h2v2h2v-2h2V14zM16,12v10H4V12c0,-2.97 2.16,-5.43 5,-5.91V4H7V2h6c1.13,0 2.15,0.39 2.99,1.01l-1.43,1.43C14.1,4.17 13.57,4 13,4h-2v2.09C13.84,6.57 16,9.03 16,12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_security_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,1L3,5v6c0,5.55 3.84,10.74 9,12 5.16,-1.26 9,-6.45 9,-12L21,5l-9,-4zM12,11.99h7c-0.53,4.12 -3.28,7.79 -7,8.94L12,12L5,12L5,6.3l7,-3.11v8.8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_shuffle_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M10.59,9.17L5.41,4 4,5.41l5.17,5.17 1.42,-1.41zM14.5,4l2.04,2.04L4,18.59 5.41,20 17.96,7.46 20,9.5L20,4h-5.5zM14.83,13.41l-1.41,1.41 3.13,3.13L14.5,20L20,20v-5.5l-2.04,2.04 -3.13,-3.13z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_shutter_speed_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M15,1L9,1v2h6L15,1zM19.03,7.39l1.42,-1.42c-0.43,-0.51 -0.9,-0.99 -1.41,-1.41l-1.42,1.42C16.07,4.74 14.12,4 12,4c-4.97,0 -9,4.03 -9,9s4.02,9 9,9 9,-4.03 9,-9c0,-2.12 -0.74,-4.07 -1.97,-5.61zM12,20c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7zM11.68,15L6.35,15c0.57,1.62 1.82,2.92 3.41,3.56l-0.11,-0.06 2.03,-3.5zM17.65,11c-0.57,-1.6 -1.78,-2.89 -3.34,-3.54L12.26,11h5.39zM10.61,18.83c0.45,0.11 0.91,0.17 1.39,0.17 1.34,0 2.57,-0.45 3.57,-1.19l-2.11,-3.9 -2.85,4.92zM7.55,8.99C6.59,10.05 6,11.46 6,13c0,0.34 0.04,0.67 0.09,1h4.72L7.55,8.99zM16.34,17.13C17.37,16.06 18,14.6 18,13c0,-0.34 -0.04,-0.67 -0.09,-1h-4.34l2.77,5.13zM13.33,7.15C12.9,7.06 12.46,7 12,7c-1.4,0 -2.69,0.49 -3.71,1.29l2.32,3.56 2.72,-4.7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_speed_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20.38,8.57l-1.23,1.85a8,8 0,0 1,-0.22 7.58L5.07,18A8,8 0,0 1,15.58 6.85l1.85,-1.23A10,10 0,0 0,3.35 19a2,2 0,0 0,1.72 1h13.85a2,2 0,0 0,1.74 -1,10 10,0 0,0 -0.27,-10.44zM10.59,15.41a2,2 0,0 0,2.83 0l5.66,-8.49 -8.49,5.66a2,2 0,0 0,0 2.83z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_stream_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,12m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4,12m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,20m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M10.05,8.59L6.03,4.55h-0.01l-0.31,-0.32 -1.42,1.41 4.02,4.05 0.01,-0.01 0.31,0.32zM13.943,8.617l4.405,-4.392L19.76,5.64l-4.405,4.393zM10.01,15.36l-1.42,-1.41 -4.03,4.01 -0.32,0.33 1.41,1.41 4.03,-4.02zM19.76,18.3l-3.99,-4.01 -0.36,-0.35L14,15.35l3.99,4.01 0.35,0.35z\"/>\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,4m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_texture_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M19.51,3.08L3.08,19.51c0.09,0.34 0.27,0.65 0.51,0.9 0.25,0.24 0.56,0.42 0.9,0.51L20.93,4.49c-0.19,-0.69 -0.73,-1.23 -1.42,-1.41zM11.88,3L3,11.88v2.83L14.71,3h-2.83zM5,3c-1.1,0 -2,0.9 -2,2v2l4,-4L5,3zM19,21c0.55,0 1.05,-0.22 1.41,-0.59 0.37,-0.36 0.59,-0.86 0.59,-1.41v-2l-4,4h2zM9.29,21h2.83L21,12.12L21,9.29L9.29,21z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_timelapse_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16.24,7.76C15.07,6.59 13.54,6 12,6v6l-4.24,4.24c2.34,2.34 6.14,2.34 8.49,0 2.34,-2.34 2.34,-6.14 -0.01,-8.48zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_transform_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M22,18v-2H8V4h2L7,1 4,4h2v2H2v2h4v8c0,1.1 0.9,2 2,2h8v2h-2l3,3 3,-3h-2v-2h4zM10,8h6v6h2V8c0,-1.1 -0.9,-2 -2,-2h-6v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_transgender_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M12,8c1.93,0 3.5,1.57 3.5,3.5S13.93,15 12,15s-3.5,-1.57 -3.5,-3.5S10.07,8 12,8zM16.53,8.38l3.97,-3.96V7h2V1h-6v2h2.58l-3.97,3.97C14.23,6.36 13.16,6 12,6c-1.16,0 -2.23,0.36 -3.11,0.97L8.24,6.32l1.41,-1.41L8.24,3.49L6.82,4.9L4.92,3H7.5V1h-6v6h2V4.42l1.91,1.9L3.99,7.74l1.41,1.41l1.41,-1.41l0.65,0.65C6.86,9.27 6.5,10.34 6.5,11.5c0,2.7 1.94,4.94 4.5,5.41L11,19H9v2h2v2h2v-2h2v-2h-2l0,-2.09c2.56,-0.47 4.5,-2.71 4.5,-5.41C17.5,10.34 17.14,9.27 16.53,8.38z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_update_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1c-2.73,2.71 -2.73,7.08 0,9.79s7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29c-3.51,3.48 -9.21,3.48 -12.72,0c-3.5,-3.47 -3.53,-9.11 -0.02,-12.58s9.14,-3.47 12.65,0L21,3V10.12zM12.5,8v4.25l3.5,2.08l-0.72,1.21L11,13V8H12.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_view_list_24.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    android:tint=\"?attr/colorControlNormal\"\n    android:autoMirrored=\"true\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M4,14h4v-4L4,10v4zM4,19h4v-4L4,15v4zM4,9h4L8,5L4,5v4zM9,14h12v-4L9,10v4zM9,19h12v-4L9,15v4zM9,5v4h12L21,5L9,5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_vpn_key_24.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12.65,10C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H17v4h4v-4h2v-4H12.65zM7,14c-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/src/main/res/drawable/ic_baseline_warning_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M1,21h22L12,2 1,21zM13,18h-2v-2h2v2zM13,14h-2v-4h2v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_wb_sunny_24.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M6.76,4.84l-1.8,-1.79 -1.41,1.41 1.79,1.79 1.42,-1.41zM4,10.5L1,10.5v2h3v-2zM13,0.55h-2L11,3.5h2L13,0.55zM20.45,4.46l-1.41,-1.41 -1.79,1.79 1.41,1.41 1.79,-1.79zM17.24,18.16l1.79,1.8 1.41,-1.41 -1.8,-1.79 -1.4,1.4zM20,10.5v2h3v-2h-3zM12,5.5c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM11,22.45h2L13,19.5h-2v2.95zM3.55,18.54l1.41,1.41 1.79,-1.8 -1.41,-1.41 -1.79,1.8z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_communication_phonelink_ring.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M20.1,7.7l-1,1c1.8,1.8 1.8,4.6 0,6.5l1,1c2.5,-2.3 2.5,-6.1 0,-8.5zM18,9.8l-1,1c0.5,0.7 0.5,1.6 0,2.3l1,1c1.2,-1.2 1.2,-3 0,-4.3zM14,1L4,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L16,3c0,-1.1 -0.9,-2 -2,-2zM14,20L4,20L4,4h10v16z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_device_data_usage.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_device_developer_mode.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M7,5h10v2h2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99L7,1c-1.1,0 -2,0.9 -2,2v4h2L7,5zM15.41,16.59L20,12l-4.59,-4.59L14,8.83 17.17,12 14,15.17l1.41,1.42zM10,15.17L6.83,12 10,8.83 8.59,7.41 4,12l4.59,4.59L10,15.17zM17,19L7,19v-2L5,17v4c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2v-4h-2v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_file_cloud_queue.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M19.35,10.04C18.67,6.59 15.64,4 12,4 9.11,4 6.6,5.64 5.35,8.04 2.34,8.36 0,10.91 0,14c0,3.31 2.69,6 6,6h13c2.76,0 5,-2.24 5,-5 0,-2.64 -2.05,-4.78 -4.65,-4.96zM19,18H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h0.71C7.37,7.69 9.48,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3s-1.34,3 -3,3z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_file_file_upload.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_hardware_router.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M20.2,5.9l0.8,-0.8C19.6,3.7 17.8,3 16,3s-3.6,0.7 -5,2.1l0.8,0.8C13,4.8 14.5,4.2 16,4.2s3,0.6 4.2,1.7zM19.3,6.7c-0.9,-0.9 -2.1,-1.4 -3.3,-1.4s-2.4,0.5 -3.3,1.4l0.8,0.8c0.7,-0.7 1.6,-1 2.5,-1 0.9,0 1.8,0.3 2.5,1l0.8,-0.8zM19,13h-2L17,9h-2v4L5,13c-1.1,0 -2,0.9 -2,2v4c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-4c0,-1.1 -0.9,-2 -2,-2zM8,18L6,18v-2h2v2zM11.5,18h-2v-2h2v2zM15,18h-2v-2h2v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_image_camera_alt.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=\"#000000\"\n        android:pathData=\"M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0\"/>\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_image_edit.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_image_looks_6.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M11,15h2v-2h-2v2zM19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM15,9h-4v2h2c1.1,0 2,0.89 2,2v2c0,1.11 -0.9,2 -2,2h-2c-1.1,0 -2,-0.89 -2,-2L9,9c0,-1.11 0.9,-2 2,-2h4v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_image_photo.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_maps_360.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    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,7C6.48,7 2,9.24 2,12c0,2.24 2.94,4.13 7,4.77V20l4,-4 -4,-4v2.73c-3.15,-0.56 -5,-1.9 -5,-2.73 0,-1.06 3.04,-3 8,-3s8,1.94 8,3c0,0.73 -1.46,1.89 -4,2.53v2.05c3.53,-0.77 6,-2.53 6,-4.58 0,-2.76 -4.48,-5 -10,-5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_maps_directions.xml",
    "content": "<vector android:autoMirrored=\"true\" android:height=\"24dp\"\n    android:viewportHeight=\"24.0\" android:viewportWidth=\"24.0\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M21.71,11.29l-9,-9c-0.39,-0.39 -1.02,-0.39 -1.41,0l-9,9c-0.39,0.39 -0.39,1.02 0,1.41l9,9c0.39,0.39 1.02,0.39 1.41,0l9,-9c0.39,-0.38 0.39,-1.01 0,-1.41zM14,14.5V12h-4v3H8v-4c0,-0.55 0.45,-1 1,-1h5V7.5l3.5,3.5 -3.5,3.5z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_maps_directions_boat.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,21c-1.39,0 -2.78,-0.47 -4,-1.32 -2.44,1.71 -5.56,1.71 -8,0C6.78,20.53 5.39,21 4,21H2v2h2c1.38,0 2.74,-0.35 4,-0.99 2.52,1.29 5.48,1.29 8,0 1.26,0.65 2.62,0.99 4,0.99h2v-2h-2zM3.95,19H4c1.6,0 3.02,-0.88 4,-2 0.98,1.12 2.4,2 4,2s3.02,-0.88 4,-2c0.98,1.12 2.4,2 4,2h0.05l1.89,-6.68c0.08,-0.26 0.06,-0.54 -0.06,-0.78s-0.34,-0.42 -0.6,-0.5L20,10.62V6c0,-1.1 -0.9,-2 -2,-2h-3V1H9v3H6c-1.1,0 -2,0.9 -2,2v4.62l-1.29,0.42c-0.26,0.08 -0.48,0.26 -0.6,0.5s-0.15,0.52 -0.06,0.78L3.95,19zM6,6h12v3.97L12,8 6,9.97V6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_navigation_apps.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    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_navigation_close.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_navigation_menu.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_notification_enhanced_encryption.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        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M18,8h-1V6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2H6c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V10c0,-1.1 -0.9,-2 -2,-2zM8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1s3.1,1.39 3.1,3.1v2H8.9V6zM16,16h-3v3h-2v-3H8v-2h3v-3h2v3h3v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_qu_camera_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:drawable=\"@drawable/ic_app_shortcut_background\"\n        android:left=\"2dp\"\n        android:top=\"2dp\"\n        android:right=\"2dp\"\n        android:bottom=\"2dp\" />\n\n    <item\n        android:drawable=\"@drawable/ic_image_camera_alt\"\n        android:left=\"12dp\"\n        android:top=\"12dp\"\n        android:right=\"12dp\"\n        android:bottom=\"12dp\" />\n\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/ic_qu_shadowsocks_foreground.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    <path\n        android:name=\"path\"\n        android:fillColor=\"#000000\"\n        android:pathData=\"M 21.25 2.28 L 17.55 18.55 L 9.26 15.89 L 16.58 7.16 L 6.83 15.37 L 0 12.8 L 21.25 2.28 ZM 9.45 17.56 L 12.09 18.41 L 9.46 22 L 9.45 17.56 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_qu_shadowsocks_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:drawable=\"@drawable/ic_app_shortcut_background\"\n        android:left=\"2dp\"\n        android:top=\"2dp\"\n        android:right=\"2dp\"\n        android:bottom=\"2dp\" />\n\n    <item\n        android:drawable=\"@drawable/ic_qu_shadowsocks_foreground\"\n        android:left=\"12dp\"\n        android:top=\"12dp\"\n        android:right=\"12dp\"\n        android:bottom=\"12dp\" />\n\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_active.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"128dp\"\n    android:height=\"128dp\"\n    android:viewportWidth=\"128\"\n    android:viewportHeight=\"128\">\n  <path\n      android:pathData=\"M43.667,125.333C43.3,124.967 43,123.218 43,121.448 43,118.448 42.645,118.116 37.75,116.536 27.256,113.149 19.944,107.167 15.413,98.261 14.361,96.192 13.381,94.306 13.236,94.07 13.091,93.833 11.421,95.333 9.525,97.403 6.202,101.031 4,101.446 4,98.444 4,97.653 5.507,95.317 7.349,93.253L10.699,89.5 10.251,75.5c-0.256,-8.003 0.038,-16.999 0.685,-21 0.623,-3.85 1.563,-12.985 2.089,-20.299 0.526,-7.314 1.427,-14.177 2.001,-15.25 1.705,-3.186 6.067,-2.483 10.028,1.615 1.895,1.961 4.84,4.43 6.543,5.487l3.097,1.921 5.403,-2.905C46.109,21.837 54.064,19.386 61.25,18.552 75.611,16.884 74.892,17.13 76.92,13.187 79.634,7.911 84.125,4.016 88.27,3.344 93.218,2.541 95.95,5.521 98.924,14.964c1.438,4.565 3.239,8.163 4.638,9.263 1.258,0.989 4.641,4.757 7.518,8.373l5.232,6.575 2.417,-3.088C120.058,34.389 121.787,33 122.572,33c2.344,0 1.628,4.181 -1.225,7.159l-2.653,2.769 2.903,6.286C124.347,55.169 124.5,56.211 124.5,69c0,12.627 -0.177,13.879 -2.741,19.351 -1.747,3.729 -4.985,8.013 -8.929,11.812l-6.188,5.962 1.736,2.94c0.955,1.617 1.459,3.389 1.12,3.938 -1.173,1.898 -3.453,0.986 -4.996,-1.997l-1.549,-2.995 -5.227,2.907C86.635,117.086 76.718,119.174 59.25,119.021L47,118.913v3.544c0,3.335 -1.543,4.667 -3.333,2.877zM82.168,113.374c2.933,-0.862 8.366,-3.152 12.075,-5.09l6.743,-3.524 -0.62,-3.88c-0.49,-3.064 -0.304,-3.88 0.885,-3.88 0.828,0 2.194,1.013 3.035,2.25l1.529,2.25 4.646,-4.5c2.555,-2.475 6.085,-7.416 7.843,-10.979 2.992,-6.064 3.194,-7.152 3.165,-17 -0.026,-8.607 -0.453,-11.58 -2.347,-16.338 -2.152,-5.408 -2.437,-5.707 -4.047,-4.25C114.122,49.295 112.59,50 111.671,50 109.577,50 109.464,46.69 111.5,45 112.325,44.315 113,43.245 113,42.62 113,40.752 104.839,31.394 99.762,27.441 95.841,24.387 95.127,23.342 95.621,21.375 96.267,18.802 93.774,11.549 91.295,8.787 89.924,7.26 89.458,7.229 87,8.5 85.484,9.284 83.304,11.405 82.155,13.213L80.067,16.5l2.211,0.544c1.216,0.299 2.359,1.312 2.54,2.25C85.108,20.801 84.085,21 76.031,21 60.747,21 48.739,24.178 39.748,30.604 37.134,32.472 34.801,34 34.563,34c-0.238,0 -3.875,-2.736 -8.084,-6.081 -4.208,-3.344 -7.813,-5.919 -8.01,-5.722 -0.197,0.197 -0.858,4.379 -1.469,9.294 -0.847,6.818 -0.827,9.465 0.084,11.168 0.963,1.8 0.884,2.886 -0.409,5.611 -0.882,1.859 -2.12,6.709 -2.751,10.778 -1.158,7.465 -0.904,24.306 0.386,25.596 0.383,0.383 1.737,-0.28 3.008,-1.473C19.977,80.673 23,80.309 23,82.487c0,0.818 -1.629,2.979 -3.62,4.804l-3.62,3.318 2.213,4.338c3.507,6.874 10.352,13.477 17.119,16.515 5.848,2.624 7.408,2.671 7.408,0.22 0,-1.798 3.446,-2.017 4.003,-0.254 1.654,5.238 0.468,4.926 15.941,4.195 8.049,-0.38 16.742,-1.37 19.724,-2.247zM37,66c-2.465,-2.465 -2.71,-9.541 -0.443,-12.777 2.015,-2.877 8.208,-3.043 11.583,-0.311 3.073,2.488 3.33,9.807 0.456,12.983C46.05,68.708 39.764,68.764 37,66ZM44.8,62.8C47.604,59.996 44.952,54.037 41.49,55.365 39.254,56.224 38.577,58.776 39.847,61.564 41.056,64.217 42.918,64.682 44.8,62.8ZM56.75,61.92C54.568,60.649 54.432,57 56.566,57c0.861,0 2.49,0.495 3.62,1.099 1.653,0.885 2.608,0.69 4.894,-1C68.627,54.477 71,54.408 71,56.927c0,4.538 -9.385,7.826 -14.25,4.992zM76.571,52.662C73.499,49.59 73.123,44.294 75.75,41.08 78.154,38.139 84.231,37.266 87.481,39.395 89.654,40.819 90,41.782 90,46.401c0,6.156 -0.891,7.239 -6.729,8.172 -3.581,0.573 -4.471,0.319 -6.7,-1.911zM85.5,46.5c0,-2.482 -0.446,-3.052 -2.586,-3.301 -3.498,-0.407 -5.433,1.963 -3.917,4.797 0.81,1.513 1.908,2.035 3.819,1.814C85.063,49.551 85.5,49.011 85.5,46.5Z\"\n      android:strokeWidth=\"3\"\n      android:fillColor=\"#fb7299\"\n      android:strokeColor=\"#fb7299\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_busy.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    <path\n        android:name=\"path\"\n        android:fillColor=\"#fff\"\n        android:pathData=\"M17.68,9l-1.59,7L12.7,14.89l5-5.93M10,10.08l-3.57,3L5,12.55l5-2.47M21.25,2.28L0,12.8l6.83,2.57,9.76-8.21L9.26,15.89l8.29,2.67,3.7-16.27h0ZM 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_connected.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:drawable=\"@drawable/ic_service_busy\">\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 17.68 9 L 16.09 16 L 12.7 14.89 L 17.7 8.96 M 10 10.08 L 6.43 13.08 L 5 12.55 L 10 10.08 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\"\n                android:valueTo=\"M 15.5 13.28 L 15.5 13.28 L 15.5 13.28 L 15.5 13.28 M 7.14 11.9 L 7.14 11.9 L 7.14 11.9 L 7.14 11.9 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_connecting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:drawable=\"@drawable/ic_service_idle\">\n    <target android:name=\"strike_thru_path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z\"\n                android:valueTo=\"M 2 4.27 L 3.27 3 L 3.27 3 L 2 4.27 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n    <target android:name=\"strike_thru_mask\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z\"\n                android:valueTo=\"M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 3.27 3 L 4.54 1.73 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_idle.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:name=\"strike_thru_path\"\n        android:pathData=\"M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z\"\n        android:fillColor=\"#fff\"\n        android:strokeWidth=\"1\"/>\n    <clip-path\n        android:name=\"strike_thru_mask\"\n        android:pathData=\"M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z\"/>\n    <path\n        android:name=\"holey_icon\"\n        android:pathData=\"M17.68,9l-1.59,7L12.7,14.89l5-5.93M10,10.08l-3.57,3L5,12.55l5-2.47M21.25,2.28L0,12.8l6.83,2.57,9.76-8.21L9.26,15.89l8.29,2.67,3.7-16.27h0ZM 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\"\n        android:fillColor=\"#fff\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_stopped.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:drawable=\"@drawable/ic_service_idle\">\n    <target android:name=\"strike_thru_path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 2 4.27 L 3.27 3 L 3.27 3 L 2 4.27 Z\"\n                android:valueTo=\"M 19.73 22 L 21 20.73 L 3.27 3 L 2 4.27 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n    <target android:name=\"strike_thru_mask\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 3.27 3 L 4.54 1.73 Z\"\n                android:valueTo=\"M 0 0 L 24 0 L 24 24 L 0 24 L 0 0 Z M 4.54 1.73 L 3.27 3 L 21 20.73 L 22.27 19.46 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_service_stopping.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<animated-vector\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:drawable=\"@drawable/ic_service_busy\">\n    <target android:name=\"path\">\n        <aapt:attr name=\"android:animation\">\n            <objectAnimator\n                android:propertyName=\"pathData\"\n                android:duration=\"@android:integer/config_mediumAnimTime\"\n                android:valueFrom=\"M 15.5 13.28 L 15.5 13.28 L 15.5 13.28 L 15.5 13.28 M 7.14 11.9 L 7.14 11.9 L 7.14 11.9 L 7.14 11.9 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\"\n                android:valueTo=\"M 17.68 9 L 16.09 16 L 12.7 14.89 L 17.7 8.96 M 10 10.08 L 6.43 13.08 L 5 12.55 L 10 10.08 M 21.25 2.28 L 0 12.8 L 6.83 15.37 L 16.59 7.16 L 9.26 15.89 L 17.55 18.56 L 21.25 2.29 L 21.25 2.29 Z M 9.45 17.56 L 9.46 22 L 12.09 18.41 L 9.45 17.56 L 9.45 17.56 Z\"\n                android:valueType=\"pathType\"\n                android:interpolator=\"@android:interpolator/fast_out_slow_in\"/>\n        </aapt:attr>\n    </target>\n</animated-vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_settings_password.xml",
    "content": "<!-- https://android.googlesource.com/platform/packages/apps/Settings/+/7961482/res/drawable/ic_password.xml -->\n<!--\n    Copyright (C) 2017 The Android Open Source Project\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         http://www.apache.org/licenses/LICENSE-2.0\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<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=\"?attr/colorControlNormal\"\n        android:pathData=\"M21.5,9.39l-1.63,0l0.81,-1.42l-0.86,-0.5l-0.82,1.42l-0.82,-1.42l-0.86,0.5l0.81,1.42l-1.63,0l0,1l1.63,0l-0.81,1.41l0.86,0.5l0.82,-1.41l0.82,1.41l0.86,-0.5l-0.81,-1.41l1.63,0z\" />\n    <path\n        android:fillColor=\"?attr/colorControlNormal\"\n        android:pathData=\"M13.68,7.97l-0.86,-0.5l-0.82,1.42l-0.82,-1.42l-0.86,0.5l0.81,1.42l-1.63,0l0,1l1.63,0l-0.81,1.41l0.86,0.5l0.82,-1.41l0.82,1.41l0.86,-0.5l-0.81,-1.41l1.63,0l0,-1l-1.63,0z\" />\n    <path\n        android:fillColor=\"?attr/colorControlNormal\"\n        android:pathData=\"M6.68,7.97l-0.86,-0.5l-0.82,1.42l-0.82,-1.42l-0.86,0.5l0.81,1.42l-1.63,0l0,1l1.63,0l-0.81,1.41l0.86,0.5l0.82,-1.41l0.82,1.41l0.86,-0.5l-0.81,-1.41l1.63,0l0,-1l-1.63,0z\" />\n    <path\n        android:fillColor=\"?attr/colorControlNormal\"\n        android:pathData=\"M21,17.89H3c-0.28,0 -0.5,-0.22 -0.5,-0.5v-1c0,-0.28 0.22,-0.5 0.5,-0.5h18c0.28,0 0.5,0.22 0.5,0.5v1C21.5,17.66 21.28,17.89 21,17.89z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_social_emoji_symbols.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    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M3,2h8v2h-8z\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M6,11l2,0l0,-4l3,0l0,-2l-8,0l0,2l3,0z\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12.4036,20.1819l7.7781,-7.7781l1.4142,1.4142l-7.7781,7.7781z\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M14.5,14.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M19.5,19.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M15.5,11c1.38,0 2.5,-1.12 2.5,-2.5V4h3V2h-4v4.51C16.58,6.19 16.07,6 15.5,6C14.12,6 13,7.12 13,8.5C13,9.88 14.12,11 15.5,11z\"/>\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9.74,15.96l-1.41,1.41l-0.71,-0.71l0.35,-0.35c0.98,-0.98 0.98,-2.56 0,-3.54c-0.49,-0.49 -1.13,-0.73 -1.77,-0.73c-0.64,0 -1.28,0.24 -1.77,0.73c-0.98,0.98 -0.98,2.56 0,3.54l0.35,0.35l-1.06,1.06c-0.98,0.98 -0.98,2.56 0,3.54C4.22,21.76 4.86,22 5.5,22s1.28,-0.24 1.77,-0.73l1.06,-1.06l1.41,1.41l1.41,-1.41l-1.41,-1.41l1.41,-1.41L9.74,15.96zM5.85,14.2c0.12,-0.12 0.26,-0.15 0.35,-0.15s0.23,0.03 0.35,0.15c0.19,0.2 0.19,0.51 0,0.71l-0.35,0.35L5.85,14.9C5.66,14.71 5.66,14.39 5.85,14.2zM5.85,19.85C5.73,19.97 5.59,20 5.5,20s-0.23,-0.03 -0.35,-0.15c-0.19,-0.19 -0.19,-0.51 0,-0.71l1.06,-1.06l0.71,0.71L5.85,19.85z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_social_share.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.0\"\n        android:viewportHeight=\"24.0\"\n        android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/terminal_scroll_shape.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n\n    <solid android:color=\"?colorPrimary\" />\n\n    <size android:width=\"4dp\" />\n\n    <!--\n        <gradient\n        android:angle=\"45\"\n        android:centerColor=\"#66ff5c33\"\n        android:endColor=\"#66FF3401\"\n        android:startColor=\"#66FF3401\" />\n    \n    <corners android:radius=\"8dp\" />\n\n    <padding\n        android:left=\"0.5dp\"\n        android:right=\"0.5dp\" />\n    -->\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable-v26/ic_qu_camera_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/material_grey_100\"/>\n    <foreground>\n        <!-- 44dp icon scaled to 52dp in 72dp, padding = (1-52/44*24/72)/2 -->\n        <inset\n            android:drawable=\"@drawable/ic_image_camera_alt\"\n            android:inset=\"30.303%\"/>\n    </foreground>\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/drawable-v26/ic_qu_shadowsocks_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/material_grey_100\"/>\n    <foreground>\n        <!-- 44dp icon scaled to 52dp in 72dp, padding = (1-52/44*24/72)/2 -->\n        <inset\n            android:drawable=\"@drawable/ic_qu_shadowsocks_foreground\"\n            android:inset=\"30.303%\"/>\n    </foreground>\n</adaptive-icon>\n"
  },
  {
    "path": "app/src/main/res/layout/item_keyboard_key.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2021 Squircle IDE contributors.\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\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/item_title\"\n    android:layout_width=\"36dp\"\n    android:layout_height=\"36dp\"\n    android:background=\"?selectableItemBackground\"\n    android:gravity=\"center\"\n    android:paddingBottom=\"2dp\"\n    android:textSize=\"16sp\"\n    android:typeface=\"monospace\"\n    tools:text=\"{\" />"
  },
  {
    "path": "app/src/main/res/layout/layout_about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n\n            <com.google.android.material.card.MaterialCardView\n                android:id=\"@+id/title_card\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"8dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginRight=\"8dp\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                app:cardElevation=\"2dp\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"16dp\">\n\n                    <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"@string/app_name_long\"\n                        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n                    <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"16dp\"\n                        android:text=\"@string/app_desc\"\n                        android:textAppearance=\"?attr/textAppearanceBody2\"\n                        android:textColor=\"?android:textColorSecondary\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n            <androidx.coordinatorlayout.widget.CoordinatorLayout\n                android:id=\"@+id/about_fragment_holder\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\" />\n\n            <com.google.android.material.card.MaterialCardView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"8dp\"\n                android:layout_marginRight=\"8dp\"\n                android:layout_marginBottom=\"8dp\"\n                app:cardElevation=\"2dp\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:padding=\"16dp\">\n\n                    <TextView\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:text=\"@string/license\"\n                        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n                    <TextView\n                        android:id=\"@+id/license\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"16dp\"\n                        android:textAppearance=\"?attr/textAppearanceBody2\"\n                        android:textColor=\"?android:textColorSecondary\"\n                        android:textIsSelectable=\"true\"\n                        android:textSize=\"12sp\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_add_entity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"\n            android:textColor=\"?android:attr/textColorPrimary\"\n            android:textStyle=\"bold\"\n            android:text=\"@string/add_profile\"/>\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_app_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:duplicateParentState=\"false\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/colorPrimary\">\n\n        <com.google.android.material.appbar.CollapsingToolbarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_scrollFlags=\"scroll|enterAlways\"\n            app:titleEnabled=\"false\">\n\n            <com.google.android.material.appbar.MaterialToolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:background=\"?attr/colorPrimary\"\n                android:elevation=\"4dp\"\n                android:theme=\"?actionBarTheme\"\n                android:touchscreenBlocksFocus=\"false\"\n                app:layout_collapseMode=\"pin\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.DayNight\"\n                app:title=\"@string/app_name\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"56dp\">\n\n                <LinearLayout\n                    android:id=\"@+id/search_layout\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\">\n\n                    <com.google.android.material.card.MaterialCardView\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_margin=\"4dp\"\n                        app:cardElevation=\"2dp\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"vertical\"\n                            android:padding=\"8dp\">\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/show_system_apps\"\n                                style=\"@style/Widget.MaterialComponents.Chip.Filter\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginBottom=\"4dp\"\n                                android:text=\"@string/show_system_apps\" />\n\n                            <com.google.android.material.textfield.TextInputLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:hint=\"@string/search_apps\">\n\n                                <com.google.android.material.textfield.TextInputEditText\n                                    android:id=\"@+id/search\"\n                                    android:layout_width=\"match_parent\"\n                                    android:layout_height=\"wrap_content\" />\n\n                            </com.google.android.material.textfield.TextInputLayout>\n\n\n                        </LinearLayout>\n\n                    </com.google.android.material.card.MaterialCardView>\n\n                </LinearLayout>\n\n            </LinearLayout>\n\n        </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <LinearLayout\n        android:id=\"@+id/loading\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.progressindicator.CircularProgressIndicator\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\" />\n\n    </LinearLayout>\n\n    <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\n        android:id=\"@+id/list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:scrollbarSize=\"0dp\"\n        android:visibility=\"gone\"\n        app:fastScrollAutoHide=\"true\"\n        app:fastScrollPopupTextColor=\"?colorPrimary\"\n        app:fastScrollThumbColor=\"?colorPrimary\"\n        app:fastScrollThumbInactiveColor=\"?colorPrimary\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n        tools:listitem=\"@layout/layout_apps_item\" />\n\n    <include\n        android:id=\"@+id/app_placeholder\"\n        layout=\"@layout/layout_app_placeholder\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_app_placeholder.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    android:padding=\"32dp\"\n    android:visibility=\"gone\">\n\n    <TextView\n        android:id=\"@+id/empty_message\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:paddingBottom=\"16dp\"\n        android:text=\"@string/app_list_permission_denied\"\n        android:textColor=\"?attr/colorOnBackground\"\n        android:textSize=\"16sp\" />\n\n    <com.google.android.material.button.MaterialButton\n        android:id=\"@+id/open_settings\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/open_app_settings\" />\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_appbar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.appbar.AppBarLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/appbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"?attr/actionBarSize\"\n    android:background=\"?attr/colorPrimary\"\n    android:elevation=\"4dp\">\n\n    <com.google.android.material.appbar.MaterialToolbar\n        android:id=\"@+id/toolbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"?attr/colorPrimary\"\n        android:theme=\"?actionBarTheme\"\n        android:touchscreenBlocksFocus=\"false\"\n        app:popupTheme=\"@style/ThemeOverlay.AppCompat.DayNight\"\n        app:title=\"@string/app_name\" />\n</com.google.android.material.appbar.AppBarLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_apps.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:duplicateParentState=\"false\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:id=\"@+id/appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/colorPrimary\">\n\n        <com.google.android.material.appbar.CollapsingToolbarLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_scrollFlags=\"scroll|enterAlways\"\n            app:titleEnabled=\"false\">\n\n            <com.google.android.material.appbar.MaterialToolbar\n                android:id=\"@+id/toolbar\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"?attr/actionBarSize\"\n                android:background=\"?attr/colorPrimary\"\n                android:elevation=\"4dp\"\n                android:theme=\"?actionBarTheme\"\n                android:touchscreenBlocksFocus=\"false\"\n                app:layout_collapseMode=\"pin\"\n                app:popupTheme=\"@style/ThemeOverlay.AppCompat.DayNight\"\n                app:title=\"@string/app_name\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:paddingTop=\"56dp\">\n\n                <com.google.android.material.card.MaterialCardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_margin=\"4dp\"\n                    app:cardElevation=\"2dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"8dp\">\n\n                        <com.google.android.material.chip.ChipGroup\n                            android:id=\"@+id/bypassGroup\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginTop=\"4dp\"\n                            app:selectionRequired=\"true\"\n                            app:singleLine=\"true\"\n                            app:singleSelection=\"true\">\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/appProxyModeDisable\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:text=\"@string/off\" />\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/appProxyModeOn\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:text=\"@string/route_proxy\" />\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/appProxyModeBypass\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:checked=\"true\"\n                                android:text=\"@string/bypass_apps\" />\n\n                        </com.google.android.material.chip.ChipGroup>\n\n                        <LinearLayout\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"horizontal\">\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/show_system_apps\"\n                                style=\"@style/Widget.MaterialComponents.Chip.Filter\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginEnd=\"8dp\"\n                                android:layout_marginBottom=\"4dp\"\n                                android:text=\"@string/show_system_apps\" />\n\n                            <com.google.android.material.chip.Chip\n                                android:id=\"@+id/autoSelectProxyApps\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:checkable=\"false\"\n                                android:text=\"@string/auto_select_proxy_apps\" />\n\n                        </LinearLayout>\n\n                        <com.google.android.material.textfield.TextInputLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:hint=\"@string/search_apps\">\n\n                            <com.google.android.material.textfield.TextInputEditText\n                                android:id=\"@+id/search\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\" />\n\n                        </com.google.android.material.textfield.TextInputLayout>\n\n\n                    </LinearLayout>\n\n                </com.google.android.material.card.MaterialCardView>\n\n            </LinearLayout>\n\n        </com.google.android.material.appbar.CollapsingToolbarLayout>\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <LinearLayout\n        android:id=\"@+id/loading\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.progressindicator.CircularProgressIndicator\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\" />\n\n    </LinearLayout>\n\n    <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\n        android:id=\"@+id/list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:scrollbarSize=\"0dp\"\n        android:visibility=\"gone\"\n        app:fastScrollAutoHide=\"true\"\n        app:fastScrollPopupTextColor=\"?colorPrimary\"\n        app:fastScrollThumbColor=\"?colorPrimary\"\n        app:fastScrollThumbInactiveColor=\"?colorPrimary\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\"\n        tools:listitem=\"@layout/layout_apps_item\" />\n\n    <include\n        android:id=\"@+id/app_placeholder\"\n        layout=\"@layout/layout_app_placeholder\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_apps_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView 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/content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    app:cardElevation=\"2dp\"\n    tools:ignore=\"SelectableText\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:layout_marginEnd=\"8dp\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:padding=\"8dp\">\n\n        <ImageView\n            android:id=\"@+id/itemicon\"\n            android:layout_width=\"40dp\"\n            android:layout_height=\"40dp\"\n            android:layout_marginStart=\"8dp\"\n            android:importantForAccessibility=\"no\"\n            android:scaleType=\"fitCenter\"\n            tools:src=\"@android:drawable/sym_def_app_icon\" />\n\n        <LinearLayout\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"4dp\"\n            android:layout_marginEnd=\"8dp\"\n            android:layout_marginBottom=\"4dp\"\n            android:layout_weight=\"1\"\n            android:orientation=\"vertical\">\n\n            <TextView\n                android:id=\"@+id/title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:ellipsize=\"end\"\n                android:textColor=\"?android:attr/textColorSecondary\"\n                android:textSize=\"18sp\"\n                tools:text=\"Package label\" />\n\n            <TextView\n                android:id=\"@+id/desc\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:breakStrategy=\"high_quality\"\n                android:ellipsize=\"end\"\n                android:textColor=\"?android:attr/textColorTertiary\"\n                android:textSize=\"12sp\"\n                tools:text=\"com.package.name (10000)\" />\n\n        </LinearLayout>\n\n        <androidx.appcompat.widget.SwitchCompat\n            android:id=\"@+id/itemcheck\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:clickable=\"false\"\n            android:focusable=\"false\"\n            android:focusableInTouchMode=\"false\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_asset_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_marginLeft=\"8dp\"\n    android:layout_marginTop=\"8dp\"\n    android:layout_marginRight=\"8dp\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"bottom\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.progressindicator.LinearProgressIndicator\n            android:id=\"@+id/subscription_update_progress\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\"\n            android:visibility=\"invisible\"\n            app:indicatorColor=\"?selectedColorPrimary\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:baselineAligned=\"false\">\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:paddingStart=\"15dp\"\n                android:paddingTop=\"15dp\"\n                android:paddingEnd=\"8dp\"\n                android:paddingBottom=\"12dp\">\n\n                <TextView\n                    android:id=\"@+id/asset_name\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceMedium\"\n                    android:textColor=\"?android:attr/textColorPrimary\"\n                    android:textStyle=\"bold\" />\n            </LinearLayout>\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginRight=\"4dp\"\n            android:layout_marginBottom=\"4dp\"\n            android:gravity=\"center_vertical\">\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_marginBottom=\"8dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"bottom\"\n                android:paddingStart=\"12.5dp\">\n\n                <TextView\n                    android:id=\"@+id/asset_status\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\" />\n\n            </LinearLayout>\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/rules_update\"\n                style=\"?attr/borderlessButtonStyle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"8dp\"\n                android:text=\"@string/group_update\"\n                android:textColor=\"?accentOrTextSecondary\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_assets.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/coordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            layout=\"@layout/layout_appbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n            android:id=\"@+id/refresh_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <androidx.recyclerview.widget.RecyclerView\n                android:id=\"@+id/recycler_view\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:clipToPadding=\"false\" />\n\n        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n    </LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_backup.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <Button\n        android:id=\"@+id/reset_settings\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/reset_settings\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n    <CheckBox\n        android:id=\"@+id/backup_configurations\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_groups_and_configurations\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n    <CheckBox\n        android:id=\"@+id/backup_rules\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_rules\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n\n    <CheckBox\n        android:id=\"@+id/backup_settings\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_settings\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n    <Button\n        android:id=\"@+id/action_export\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/action_export\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n    <Button\n        android:id=\"@+id/action_share\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/share\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"@string/backup_summary\"\n        android:textColor=\"?android:textColorTertiary\" />\n\n    <Button\n        android:id=\"@+id/action_import_file\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"@string/action_import_file\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_chain_settings.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/settings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <LinearLayout\n        android:id=\"@+id/list_cell\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@android:color/darker_gray\"\n        android:orientation=\"horizontal\" />\n\n    <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\n        android:id=\"@+id/configuration_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:scrollbarSize=\"0dp\"\n        android:padding=\"4dp\"\n        android:overScrollMode=\"never\"\n        app:fastScrollAutoHide=\"true\"\n        app:fastScrollPopupTextColor=\"?colorPrimary\"\n        app:fastScrollThumbColor=\"?colorPrimary\"\n        app:fastScrollThumbInactiveColor=\"?colorPrimary\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\">\n\n    </com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_config_settings.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/settings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <Button\n        android:id=\"@+id/debug_crash\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Crash From Kt\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n    <Button\n        android:id=\"@+id/reset_settings\"\n        style=\"@style/Widget.AppCompat.Button.Colored\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Reset Settings\"\n        android:textColor=\"?whiteOrTextPrimary\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_edit_config.xml",
    "content": "<LinearLayout 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:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\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=\"0.5dp\"\n        android:background=\"@android:color/darker_gray\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <com.blacksquircle.ui.editorkit.widget.TextProcessor\n            android:id=\"@+id/editor\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"@null\"\n            android:completionThreshold=\"2\"\n            android:importantForAutofill=\"no\"\n            android:scrollbars=\"none\" />\n\n        <com.blacksquircle.ui.editorkit.widget.TextScroller\n            android:id=\"@+id/scroller\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"end\"\n            app:thumbTint=\"?colorPrimary\" />\n\n    </FrameLayout>\n\n    <LinearLayout\n        android:id=\"@+id/keyboard_container\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:elevation=\"2dp\"\n        android:orientation=\"horizontal\">\n\n        <ImageView\n            android:id=\"@+id/action_tab\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?primaryOrTextPrimary\"\n            android:padding=\"8dp\"\n            android:src=\"@drawable/baseline_keyboard_tab_24\"\n            app:tint=\"?colorOnSurface\" />\n\n        <moe.matsuri.nb4a.ui.ExtendedKeyboard\n            android:id=\"@+id/extended_keyboard\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?colorSecondaryVariant\"\n            android:orientation=\"horizontal\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:itemCount=\"6\"\n            tools:listitem=\"@layout/item_keyboard_key\" />\n\n        <ImageView\n            android:id=\"@+id/action_undo\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?primaryOrTextPrimary\"\n            android:padding=\"8dp\"\n            android:src=\"@drawable/baseline_undo_24\"\n            app:tint=\"?colorOnSurface\" />\n\n        <ImageView\n            android:id=\"@+id/action_redo\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?primaryOrTextPrimary\"\n            android:padding=\"8dp\"\n            android:src=\"@drawable/baseline_redo_24\"\n            app:tint=\"?colorOnSurface\" />\n\n        <ImageView\n            android:id=\"@+id/action_format\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?primaryOrTextPrimary\"\n            android:padding=\"8dp\"\n            android:src=\"@drawable/ic_baseline_format_align_left_24\"\n            app:tint=\"?colorOnSurface\" />\n\n        <ImageView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?primaryOrTextPrimary\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_edit_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"12dp\">\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\">\n\n            <com.google.android.material.textfield.TextInputLayout\n                android:id=\"@+id/group_name_layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"4dp\"\n                android:layout_marginRight=\"4dp\"\n                android:layout_marginBottom=\"4dp\">\n\n                <com.google.android.material.textfield.TextInputEditText\n                    android:id=\"@+id/group_name\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/group_name\"\n                    android:inputType=\"textNoSuggestions\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <com.google.android.material.textfield.TextInputLayout\n                android:id=\"@+id/group_links_layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"4dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginRight=\"4dp\"\n                android:layout_marginBottom=\"4dp\">\n\n                <com.google.android.material.textfield.TextInputEditText\n                    android:id=\"@+id/group_subscription_link\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/group_subscription_link\"\n                    android:inputType=\"textNoSuggestions|textMultiLine\" />\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <com.google.android.material.card.MaterialCardView\n                android:id=\"@+id/deduplication_card\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"4dp\"\n                android:layout_marginTop=\"8dp\"\n                android:layout_marginRight=\"4dp\"\n                android:layout_marginBottom=\"4dp\"\n                app:cardElevation=\"2dp\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:orientation=\"horizontal\"\n                    android:padding=\"4dp\">\n\n                    <TextView\n                        android:layout_width=\"0dp\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginStart=\"12dp\"\n                        android:layout_weight=\"1\"\n                        android:text=\"@string/deduplication\"\n                        android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                        android:textStyle=\"bold\" />\n\n                    <com.google.android.material.checkbox.MaterialCheckBox\n                        android:id=\"@+id/deduplication\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\" />\n\n                </LinearLayout>\n\n            </com.google.android.material.card.MaterialCardView>\n\n        </LinearLayout>\n    </androidx.core.widget.NestedScrollView>\n\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_empty.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fragment_holder\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n/>"
  },
  {
    "path": "app/src/main/res/layout/layout_empty_route.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"?android:attr/textAppearanceSmall\"\n            android:textColor=\"?android:attr/textColorPrimary\"\n            android:textStyle=\"bold\"\n            android:text=\"@string/route_warn\"/>\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            layout=\"@layout/layout_appbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\n            android:id=\"@+id/group_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:padding=\"4dp\"\n            android:scrollbarSize=\"0dp\"\n            app:fastScrollAutoHide=\"true\"\n            app:fastScrollThumbColor=\"?colorPrimary\"\n            app:fastScrollThumbInactiveColor=\"?colorPrimary\">\n\n        </com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView>\n\n    </LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_group_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView 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/content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"bottom\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.progressindicator.LinearProgressIndicator\n            android:id=\"@+id/subscription_update_progress\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:indeterminate=\"true\"\n            android:visibility=\"gone\"\n            app:indicatorColor=\"?selectedColorPrimary\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\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:paddingStart=\"15dp\"\n                android:paddingTop=\"15dp\"\n                android:paddingEnd=\"8dp\"\n                android:paddingBottom=\"12dp\">\n\n                <TextView\n                    android:id=\"@+id/group_name\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:textAppearance=\"?android:attr/textAppearanceMedium\"\n                    android:textColor=\"?android:attr/textColorPrimary\"\n                    android:textStyle=\"bold\"\n                    tools:text=\"@string/group_name\" />\n\n                <TextView\n                    android:id=\"@+id/group_user\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?android:attr/textColorSecondary\"\n                    tools:text=\"user name\" />\n            </LinearLayout>\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/edit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/edit\"\n                android:focusable=\"true\"\n                android:padding=\"12dp\"\n                app:srcCompat=\"@drawable/ic_image_edit\" />\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/options\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:focusable=\"true\"\n                android:nextFocusLeft=\"@+id/container\"\n                android:padding=\"12dp\"\n                app:srcCompat=\"@drawable/ic_baseline_more_vert_24\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_marginLeft=\"4dp\"\n            android:layout_marginRight=\"4dp\"\n            android:layout_marginBottom=\"4dp\"\n            android:gravity=\"bottom\">\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginBottom=\"8dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"bottom\"\n                android:orientation=\"vertical\"\n                android:paddingStart=\"12.5dp\"\n                android:paddingTop=\"8dp\">\n\n                <TextView\n                    android:id=\"@+id/group_traffic\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\"\n                    android:visibility=\"gone\"\n                    tools:text=\"@string/subscription_traffic\" />\n\n                <TextView\n                    android:id=\"@+id/group_status\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\"\n                    tools:text=\"@string/group_status_empty\" />\n\n            </LinearLayout>\n\n            <com.google.android.material.button.MaterialButton\n                android:id=\"@+id/group_update\"\n                style=\"?attr/borderlessButtonStyle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"8dp\"\n                android:text=\"@string/group_update\"\n                android:textColor=\"?accentOrTextSecondary\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_group_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.ConfigurationFragment\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/group_tab\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?colorPrimary\"\n        android:elevation=\"4dp\"\n        android:visibility=\"gone\"\n        app:tabIndicatorColor=\"?tabIndicatorColor\"\n        app:tabIndicatorFullWidth=\"false\"\n        app:tabMode=\"scrollable\"\n        app:tabPaddingStart=\"16dp\"\n        app:tabRippleColor=\"?tabRippleColor\"\n        app:tabSelectedTextColor=\"?tabSelectedTextColor\"\n        app:tabTextColor=\"?tabTextColor\"\n        app:tabUnboundedRipple=\"false\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/group_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_icon_list_item_2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:paddingStart=\"12dp\"\n    android:paddingEnd=\"12dp\"\n    android:focusable=\"true\">\n    <ImageView android:id=\"@android:id/icon\"\n               android:layout_width=\"52dp\"\n               android:layout_height=\"52dp\"\n               android:importantForAccessibility=\"no\"/>\n    <Space android:layout_width=\"4dp\"\n           android:layout_height=\"wrap_content\"/>\n    <LinearLayout android:orientation=\"vertical\"\n                  android:layout_width=\"0dp\"\n                  android:layout_height=\"wrap_content\"\n                  android:layout_weight=\"1\"\n                  android:layout_gravity=\"center_vertical\">\n        <TextView android:id=\"@android:id/text1\"\n                  android:textAppearance=\"?android:attr/textAppearance\"\n                  android:layout_width=\"match_parent\"\n                  android:layout_height=\"wrap_content\"\n                  android:maxLines=\"2\"\n                  android:ellipsize=\"end\"/>\n        <TextView android:id=\"@android:id/text2\"\n                  android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                  android:layout_width=\"match_parent\"\n                  android:layout_height=\"wrap_content\"\n                  android:maxLines=\"2\"\n                  android:ellipsize=\"end\"/>\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_import.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <CheckBox\n        android:id=\"@+id/backup_configurations\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_groups_and_configurations\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n    <CheckBox\n        android:id=\"@+id/backup_rules\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_rules\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n    <CheckBox\n        android:id=\"@+id/backup_settings\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:checked=\"true\"\n        android:text=\"@string/backup_settings\"\n        android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:textColor=\"@color/material_red_accent_200\"\n        android:text=\"@string/backup_import_summary\">\n\n    </TextView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.progressindicator.LinearProgressIndicator\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        app:indicatorColor=\"?selectedColorPrimary\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:padding=\"16dp\">\n\n        <TextView\n            android:id=\"@+id/loadingText\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"Loading...\"\n            android:textAppearance=\"?textAppearanceSubtitle1\" />\n\n    </androidx.core.widget.NestedScrollView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_logcat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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    android:orientation=\"vertical\"\n    tools:context=\".ui.ToolsFragment\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\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=\"match_parent\"\n        android:padding=\"8dp\">\n\n        <ScrollView\n            android:id=\"@+id/scroolview\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <TextView\n                android:id=\"@+id/textview\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@null\"\n                android:ems=\"10\"\n                android:focusable=\"true\"\n                android:gravity=\"start|top\"\n                android:textIsSelectable=\"true\"\n                tools:ignore=\"LabelFor\" />\n        </ScrollView>\n    </LinearLayout>\n\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_loglevel_help.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/help\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/log_level_help\"\n    android:textAlignment=\"center\">\n\n</TextView>"
  },
  {
    "path": "app/src/main/res/layout/layout_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:openDrawer=\"start\">\n\n    <androidx.coordinatorlayout.widget.CoordinatorLayout\n        android:id=\"@+id/coordinator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipChildren=\"false\">\n\n        <androidx.coordinatorlayout.widget.CoordinatorLayout\n            android:id=\"@+id/fragment_holder\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <!-- We double trackThickness as half of it will be invisible -->\n        <com.google.android.material.progressindicator.CircularProgressIndicator\n            android:id=\"@+id/fabProgress\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:max=\"1\"\n            android:visibility=\"invisible\"\n            app:indicatorColor=\"?colorMaterial100\"\n            app:layout_anchor=\"@+id/fab\"\n            app:layout_anchorGravity=\"center\"\n            app:layout_behavior=\"io.nekohasekai.sagernet.widget.FabProgressBehavior\"\n            app:trackCornerRadius=\"@dimen/mtrl_progress_track_thickness\"\n            app:trackThickness=\"8dp\" />\n\n        <io.nekohasekai.sagernet.widget.ServiceButton\n            android:id=\"@+id/fab\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:elevation=\"6dp\"\n            android:nextFocusDown=\"@+id/stats\"\n            app:backgroundTint=\"?fabColorBackground\"\n            app:layout_anchor=\"@id/stats\"\n            app:pressedTranslationZ=\"6dp\"\n            app:srcCompat=\"@drawable/ic_service_idle\" />\n\n        <io.nekohasekai.sagernet.widget.StatsBar\n            android:id=\"@+id/stats\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"bottom\"\n            android:nextFocusUp=\"@+id/fab\"\n            app:backgroundTint=\"?colorMaterial300\"\n            app:contentInsetStart=\"0dp\"\n            app:hideOnScroll=\"true\"\n            app:layout_scrollFlags=\"enterAlways|scroll\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?android:attr/selectableItemBackground\"\n                android:orientation=\"vertical\"\n                android:padding=\"16dp\">\n\n                <TextView\n                    android:id=\"@+id/tx\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_gravity=\"fill_horizontal\"\n                    android:ellipsize=\"marquee\"\n                    android:singleLine=\"true\"\n                    android:textColor=\"?whiteOrTextPrimary\"\n                    android:textSize=\"14sp\" />\n\n                <TextView\n                    android:id=\"@+id/rx\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_columnWeight=\"1\"\n                    android:layout_gravity=\"fill_horizontal\"\n                    android:ellipsize=\"marquee\"\n                    android:singleLine=\"true\"\n                    android:textColor=\"?whiteOrTextPrimary\"\n                    android:textSize=\"14sp\" />\n\n                <TextView\n                    android:id=\"@+id/status\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"fill_horizontal\"\n                    android:ellipsize=\"end\"\n                    android:maxLines=\"1\"\n                    android:textColor=\"?whiteOrTextPrimary\"\n                    android:textSize=\"16sp\"\n                    tools:text=\"@string/connection_test_available\" />\n\n            </LinearLayout>\n\n        </io.nekohasekai.sagernet.widget.StatsBar>\n\n    </androidx.coordinatorlayout.widget.CoordinatorLayout>\n\n    <com.google.android.material.navigation.NavigationView\n        android:id=\"@+id/nav_view\"\n        style=\"@style/Widget.Design.NavigationView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:itemIconTint=\"@color/navigation_icon\"\n        app:itemTextColor=\"@color/navigation_icon\"\n        app:menu=\"@menu/main_drawer_menu\" />\n\n    <com.google.android.material.navigation.NavigationView\n        android:id=\"@+id/nav_view_black\"\n        style=\"@style/Widget.Design.NavigationView\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"start\"\n        app:itemIconTint=\"@color/navigation_icon\"\n        app:itemShapeAppearance=\"@style/ShapeAppearance.MaterialComponents.MediumComponent\"\n        app:itemShapeFillColor=\"@color/navigation_item\"\n        app:itemTextColor=\"@color/navigation_icon\"\n        app:menu=\"@menu/main_drawer_menu\" />\n\n</androidx.drawerlayout.widget.DrawerLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_mtu_help.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/help\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:text=\"@string/mtu_help\"\n    android:textAlignment=\"center\">\n\n</TextView>"
  },
  {
    "path": "app/src/main/res/layout/layout_network.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.card.MaterialCardView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"8dp\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        app:cardElevation=\"2dp\">\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=\"vertical\"\n                android:padding=\"16dp\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/stun_test\"\n                    android:textAppearance=\"?attr/textAppearanceHeadline6\" />\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"16dp\"\n                    android:text=\"@string/stun_test_summary\"\n                    android:textAppearance=\"?attr/textAppearanceBody2\"\n                    android:textColor=\"?android:attr/textColorSecondary\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"8dp\"\n                android:gravity=\"end\"\n                android:orientation=\"horizontal\">\n\n                <Button\n                    android:id=\"@+id/stunTest\"\n                    style=\"@style/Widget.AppCompat.Button.Borderless\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginEnd=\"8dp\"\n                    android:text=\"@string/start\"\n                    android:textColor=\"?primaryOrTextPrimary\" />\n            </LinearLayout>\n\n        </LinearLayout>\n    </com.google.android.material.card.MaterialCardView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_password_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Based on: https://android.googlesource.com/platform/frameworks/support/+/b4cd329/preference/preference/res/layout/preference_dialog_edittext.xml -->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:layout_marginTop=\"48dp\"\n    android:layout_marginBottom=\"48dp\"\n    android:overScrollMode=\"ifContentScrolls\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@android:id/message\"\n            style=\"?android:attr/textAppearanceSmall\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"24dp\"\n            android:layout_marginLeft=\"24dp\"\n            android:layout_marginEnd=\"24dp\"\n            android:layout_marginRight=\"24dp\"\n            android:layout_marginBottom=\"48dp\"\n            android:textColor=\"?android:attr/textColorSecondary\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginLeft=\"20dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:layout_marginRight=\"20dp\"\n            android:orientation=\"vertical\"\n            app:passwordToggleEnabled=\"true\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@android:id/edit\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textPassword\"\n                android:minHeight=\"48dp\"\n                android:paddingTop=\"12dp\"\n                android:singleLine=\"true\"\n                android:typeface=\"monospace\" />\n        </com.google.android.material.textfield.TextInputLayout>\n    </LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_profile.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Based on: https://github.com/android/platform_frameworks_base/blob/505e3ab/core/res/res/layout/simple_list_item_2.xml -->\n<com.google.android.material.card.MaterialCardView 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/content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:id=\"@+id/content_lin\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:focusable=\"true\"\n        android:orientation=\"horizontal\">\n\n        <LinearLayout\n            android:id=\"@+id/selected_view\"\n            android:layout_width=\"4dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?selectedColorPrimary\"\n            android:orientation=\"horizontal\"\n            android:visibility=\"invisible\"\n            tools:visibility=\"visible\" />\n\n        <LinearLayout\n\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:nextFocusRight=\"@+id/edit\"\n            android:orientation=\"vertical\">\n\n            <LinearLayout\n                android:id=\"@+id/container\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <LinearLayout\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_vertical\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center_vertical\"\n                    android:paddingLeft=\"12dp\"\n                    android:paddingTop=\"8dp\"\n                    android:paddingBottom=\"8dp\">\n\n                    <TextView\n                        android:id=\"@+id/profile_name\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                        android:textColor=\"?android:attr/textColorPrimary\"\n                        android:textStyle=\"bold\"\n                        tools:text=\"@string/profile_name\" />\n                </LinearLayout>\n\n                <androidx.appcompat.widget.AppCompatImageView\n                    android:id=\"@+id/edit\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"top\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:contentDescription=\"@string/edit\"\n                    android:focusable=\"true\"\n                    android:padding=\"12dp\"\n                    app:srcCompat=\"@drawable/ic_image_edit\" />\n\n                <LinearLayout\n                    android:id=\"@+id/share\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"top\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:contentDescription=\"@string/share\"\n                    android:focusable=\"true\"\n                    android:nextFocusLeft=\"@+id/container\">\n\n                    <LinearLayout\n                        android:id=\"@+id/share_layer\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:padding=\"12dp\">\n\n                        <androidx.appcompat.widget.AppCompatImageView\n                            android:id=\"@+id/shareIcon\"\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            app:srcCompat=\"@drawable/ic_social_share\" />\n                    </LinearLayout>\n\n                </LinearLayout>\n\n                <androidx.appcompat.widget.AppCompatImageView\n                    android:id=\"@+id/remove\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"top\"\n                    android:background=\"?attr/selectableItemBackgroundBorderless\"\n                    android:contentDescription=\"@string/delete\"\n                    android:focusable=\"true\"\n                    android:padding=\"12dp\"\n                    android:visibility=\"gone\"\n                    app:srcCompat=\"@drawable/ic_action_delete\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"12dp\"\n                android:paddingEnd=\"8dp\"\n                android:paddingBottom=\"4dp\"\n                android:visibility=\"gone\"\n                tools:visibility=\"visible\">\n\n                <TextView\n                    android:id=\"@+id/profile_address\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/server_address\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?android:attr/textColorSecondary\" />\n\n                <LinearLayout\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\" />\n\n                <TextView\n                    android:id=\"@+id/traffic_text\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/traffic\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?android:attr/textColorSecondary\" />\n\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"12dp\"\n                android:paddingEnd=\"8dp\"\n                android:paddingBottom=\"12dp\">\n\n                <TextView\n                    android:id=\"@+id/profile_type\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\" />\n\n                <LinearLayout\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\" />\n\n                <TextView\n                    android:id=\"@+id/profile_status\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"4dp\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\" />\n\n            </LinearLayout>\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_profile_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView 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/configuration_list\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:clipChildren=\"false\"\n    android:clipToPadding=\"false\"\n    android:paddingLeft=\"4dp\"\n    android:paddingTop=\"4dp\"\n    android:paddingRight=\"4dp\"\n    android:paddingBottom=\"48dp\"\n    android:scrollbarSize=\"0dp\"\n    app:fastScrollAutoHide=\"true\"\n    app:fastScrollThumbColor=\"?colorPrimary\"\n    app:fastScrollThumbInactiveColor=\"?colorPrimary\"\n    tools:listitem=\"@layout/layout_profile\" />"
  },
  {
    "path": "app/src/main/res/layout/layout_progress.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"16dp\">\n\n    <com.google.android.material.progressindicator.CircularProgressIndicator\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        app:indicatorColor=\"?selectedColorPrimary\" />\n\n    <TextView\n        android:id=\"@+id/content\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"16dp\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_progress_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <com.google.android.material.progressindicator.CircularProgressIndicator\n        android:id=\"@+id/progress_circular\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:indeterminate=\"true\"\n        app:indicatorColor=\"?selectedColorPrimary\" />\n\n    <TextView\n        android:id=\"@+id/now_testing\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textAlignment=\"center\" />\n\n    <TextView\n        android:id=\"@+id/progress\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textAlignment=\"center\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_route.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            layout=\"@layout/layout_appbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView\n            android:id=\"@+id/route_list\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:padding=\"4dp\"\n            android:scrollbarSize=\"0dp\"\n            app:fastScrollAutoHide=\"true\"\n            app:fastScrollThumbColor=\"?colorPrimary\"\n            app:fastScrollThumbInactiveColor=\"?colorPrimary\">\n\n        </com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView>\n\n    </LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_route_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Based on: https://github.com/android/platform_frameworks_base/blob/505e3ab/core/res/res/layout/simple_list_item_2.xml -->\n<com.google.android.material.card.MaterialCardView 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/content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"4dp\"\n    app:cardElevation=\"2dp\">\n\n    <LinearLayout\n        android:id=\"@+id/content_lin\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:focusable=\"true\"\n        android:orientation=\"vertical\">\n\n\n        <LinearLayout\n            android:id=\"@+id/container\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\"\n                android:gravity=\"center_vertical\"\n                android:paddingStart=\"12dp\"\n                android:paddingTop=\"8dp\"\n                android:paddingBottom=\"8dp\">\n\n                <TextView\n                    android:id=\"@+id/profile_name\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?android:attr/textColorPrimary\"\n                    android:textStyle=\"bold\"\n                    tools:text=\"@string/profile_name\" />\n            </LinearLayout>\n\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/subscription\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/subscriptions\"\n                android:focusable=\"true\"\n                android:padding=\"12dp\"\n                android:visibility=\"gone\"\n                app:srcCompat=\"@drawable/ic_file_cloud_queue\" />\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/edit\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/edit\"\n                android:focusable=\"true\"\n                android:padding=\"12dp\"\n                app:srcCompat=\"@drawable/ic_image_edit\" />\n\n            <LinearLayout\n                android:id=\"@+id/share\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"top\"\n                android:background=\"?attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/share\"\n                android:focusable=\"true\"\n                android:nextFocusLeft=\"@+id/container\"\n                android:visibility=\"gone\">\n\n                <LinearLayout\n                    android:id=\"@+id/share_layer\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:padding=\"12dp\">\n\n                    <androidx.appcompat.widget.AppCompatImageView\n                        android:id=\"@+id/shareIcon\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        app:srcCompat=\"@drawable/ic_social_share\" />\n                </LinearLayout>\n\n\n            </LinearLayout>\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"bottom\"\n            android:orientation=\"horizontal\"\n            android:paddingStart=\"12dp\"\n            android:paddingEnd=\"8dp\">\n\n            <LinearLayout\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:orientation=\"vertical\">\n\n                <TextView\n                    android:id=\"@+id/profile_type\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\" />\n\n                <TextView\n                    android:layout_marginTop=\"4dp\"\n                    android:layout_marginBottom=\"12dp\"\n                    android:id=\"@+id/route_outbound\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:textAppearance=\"?android:attr/textAppearanceSmall\"\n                    android:textColor=\"?attr/accentOrTextSecondary\" />\n\n            </LinearLayout>\n\n            <androidx.appcompat.widget.SwitchCompat\n                android:layout_marginBottom=\"2dp\"\n                android:id=\"@+id/enable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "app/src/main/res/layout/layout_scanner.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.camera.view.PreviewView\n        android:id=\"@+id/previewView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <com.king.zxing.ViewfinderView\n        android:id=\"@+id/viewfinderView\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <ImageView\n        android:id=\"@+id/ivFlashlight\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"@dimen/zxl_flashlight_margin_top\"\n        android:src=\"@drawable/zxl_flashlight_selector\" />\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_settings_activity.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.fragment.app.FragmentContainerView\n        android:id=\"@+id/settings\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/configuration_list\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_stun.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/coordinator\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <include\n            layout=\"@layout/layout_appbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n\n        <LinearLayout\n            android:id=\"@+id/wait_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:visibility=\"gone\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:text=\"@string/stun_attest_loading\"\n                android:textAppearance=\"@style/AppearanceFoundation.Title\" />\n        </LinearLayout>\n\n\n        <androidx.core.widget.NestedScrollView\n            android:id=\"@+id/main_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:orientation=\"vertical\">\n\n                <com.google.android.material.card.MaterialCardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginRight=\"16dp\"\n                    android:clickable=\"true\"\n                    android:focusable=\"true\"\n                    app:cardElevation=\"2dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:text=\"@string/nat_stun_server_hint\"\n                            android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n                        <EditText\n                            android:id=\"@+id/nat_stun_server\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:ems=\"10\"\n                            android:inputType=\"text\"\n                            android:text=\"stun.voipgate.com:3478\" />\n\n                    </LinearLayout>\n                </com.google.android.material.card.MaterialCardView>\n\n                <com.google.android.material.card.MaterialCardView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"16dp\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginRight=\"16dp\"\n                    android:clickable=\"true\"\n                    android:focusable=\"true\"\n                    app:cardElevation=\"2dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:padding=\"16dp\">\n\n                        <TextView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:text=\"@string/nat_result_hint\"\n                            android:textAppearance=\"?attr/textAppearanceSubtitle2\" />\n\n                        <TextView\n                            android:id=\"@+id/nat_result\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\" />\n\n                    </LinearLayout>\n\n                </com.google.android.material.card.MaterialCardView>\n\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginTop=\"16dp\"\n                    android:layout_marginRight=\"16dp\"\n                    android:gravity=\"end\"\n                    android:orientation=\"horizontal\">\n\n                    <Button\n                        android:id=\"@+id/stunTest\"\n                        style=\"@style/Widget.AppCompat.Button.Borderless\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginEnd=\"8dp\"\n                        android:text=\"@string/start\"\n                        android:textColor=\"?primaryOrTextPrimary\" />\n                </LinearLayout>\n\n            </LinearLayout>\n        </androidx.core.widget.NestedScrollView>\n    </LinearLayout>\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_tools.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout 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:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.ToolsFragment\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tools_tab\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?colorPrimary\"\n        android:elevation=\"4dp\"\n        app:tabIndicatorColor=\"?tabIndicatorColor\"\n        app:tabIndicatorFullWidth=\"false\"\n        app:tabMode=\"fixed\"\n        app:tabPaddingStart=\"16dp\"\n        app:tabRippleColor=\"?tabRippleColor\"\n        app:tabSelectedTextColor=\"?tabSelectedTextColor\"\n        app:tabTextColor=\"?tabTextColor\"\n        app:tabUnboundedRipple=\"false\" />\n\n    <androidx.viewpager2.widget.ViewPager2\n        android:id=\"@+id/tools_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/layout_urltest_preference_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!-- Based on: https://android.googlesource.com/platform/frameworks/support/+/b4cd329/preference/preference/res/layout/preference_dialog_edittext.xml -->\n<ScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"ifContentScrolls\"\n    android:paddingTop=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:id=\"@+id/input_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"20dp\"\n            android:layout_marginLeft=\"20dp\"\n            android:layout_marginEnd=\"20dp\"\n            android:layout_marginRight=\"20dp\"\n            android:orientation=\"vertical\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@android:id/edit\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"textUri|textMultiLine\"\n                android:minHeight=\"48dp\"\n                android:paddingTop=\"12dp\"\n                android:typeface=\"monospace\" />\n\n            <LinearLayout\n                android:id=\"@+id/concurrent_layout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:visibility=\"gone\">\n\n                <TextView\n                    style=\"?android:attr/textAppearanceSmall\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/test_concurrency\"\n                    android:textColor=\"?android:attr/textColorSecondary\" />\n\n                <com.google.android.material.textfield.TextInputEditText\n                    android:id=\"@+id/edit_concurrent\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    android:minHeight=\"48dp\"\n                    android:paddingTop=\"12dp\"\n                    android:singleLine=\"true\"\n                    android:typeface=\"monospace\" />\n\n            </LinearLayout>\n\n        </com.google.android.material.textfield.TextInputLayout>\n    </LinearLayout>\n</ScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/layout_webview.xml",
    "content": "<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <include\n        layout=\"@layout/layout_appbar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <WebView\n        android:id=\"@+id/webview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/simple_menu_dropdown_item.xml",
    "content": "<!-- Copyright (C) 2016 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\n<CheckedTextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    style=\"?android:attr/spinnerDropDownItemStyle\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"48dp\"\n    android:ellipsize=\"marquee\"\n    android:gravity=\"center\"\n    android:minWidth=\"112dp\"\n    android:paddingEnd=\"16dp\"\n    android:paddingStart=\"16dp\"\n    android:singleLine=\"true\" />\n"
  },
  {
    "path": "app/src/main/res/menu/add_group_menu.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_update_all\"\n        android:icon=\"@drawable/ic_baseline_update_24\"\n        android:title=\"@string/update_all_subscription\"\n        app:showAsAction=\"always\" />\n    <item\n        android:id=\"@+id/action_new_group\"\n        android:icon=\"@drawable/ic_av_playlist_add\"\n        android:title=\"@string/group_create\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/add_profile_menu.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:title=\"@android:string/search_go\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/action_add\"\n        android:icon=\"@drawable/ic_action_note_add\"\n        android:title=\"@string/add_profile\"\n        app:showAsAction=\"ifRoom\">\n        <menu>\n            <item\n                android:id=\"@+id/action_scan_qr_code\"\n                android:title=\"@string/add_profile_methods_scan_qr_code\" />\n            <item\n                android:id=\"@+id/action_import_clipboard\"\n                android:title=\"@string/action_import\" />\n            <item\n                android:id=\"@+id/action_import_file\"\n                android:title=\"@string/action_import_file\" />\n            <item android:title=\"@string/add_profile_methods_manual_settings\">\n                <menu>\n                    <item\n                        android:id=\"@+id/action_new_socks\"\n                        android:title=\"@string/action_socks\" />\n                    <item\n                        android:id=\"@+id/action_new_http\"\n                        android:title=\"@string/action_http\" />\n                    <item\n                        android:id=\"@+id/action_new_ss\"\n                        android:title=\"@string/action_shadowsocks\" />\n                    <item\n                        android:id=\"@+id/action_new_vmess\"\n                        android:title=\"@string/action_vmess\" />\n                    <item\n                        android:id=\"@+id/action_new_vless\"\n                        android:title=\"VLESS\" />\n                    <item\n                        android:id=\"@+id/action_new_trojan\"\n                        android:title=\"@string/action_trojan\" />\n                    <item\n                        android:id=\"@+id/action_new_trojan_go\"\n                        android:title=\"@string/action_trojan_go\" />\n                    <item\n                        android:id=\"@+id/action_new_mieru\"\n                        android:title=\"@string/action_mieru\" />\n                    <item\n                        android:id=\"@+id/action_new_naive\"\n                        android:title=\"@string/action_naive\" />\n                    <item\n                        android:id=\"@+id/action_new_hysteria\"\n                        android:title=\"@string/action_hysteria\" />\n                    <item\n                        android:id=\"@+id/action_new_tuic\"\n                        android:title=\"@string/action_tuic\" />\n                    <item\n                        android:id=\"@+id/action_new_shadowtls\"\n                        android:title=\"@string/action_shadowtls\" />\n                    <item\n                        android:id=\"@+id/action_new_anytls\"\n                        android:title=\"@string/action_anytls\" />\n                    <item\n                        android:id=\"@+id/action_new_ssh\"\n                        android:title=\"@string/action_ssh\" />\n                    <item\n                        android:id=\"@+id/action_new_wg\"\n                        android:title=\"@string/action_wireguard\" />\n                    <item\n                        android:id=\"@+id/action_new_config\"\n                        android:title=\"@string/custom_config\" />\n                    <item\n                        android:id=\"@+id/action_new_chain\"\n                        android:title=\"@string/proxy_chain\" />\n                </menu>\n            </item>\n        </menu>\n    </item>\n\n    <item\n        android:id=\"@+id/action_misc\"\n        android:icon=\"@drawable/ic_baseline_more_vert_24\"\n        android:title=\"\"\n        app:showAsAction=\"always\">\n        <menu>\n            <item\n                android:id=\"@+id/action_update_subscription\"\n                android:title=\"@string/update_current_subscription\" />\n            <item\n                android:id=\"@+id/action_clear_traffic_statistics\"\n                android:title=\"@string/clear_traffic_statistics\" />\n            <item\n                android:id=\"@+id/action_remove_duplicate\"\n                android:title=\"@string/remove_duplicate\" />\n            <item\n                android:id=\"@+id/action_connection_tcp_ping\"\n                android:title=\"@string/connection_test_tcp_ping\" />\n            <item\n                android:id=\"@+id/action_connection_url_test\"\n                android:title=\"@string/connection_test_url_test\" />\n            <item\n                android:id=\"@+id/action_connection_test_clear_results\"\n                android:title=\"@string/connection_test_clear_results\" />\n            <item\n                android:id=\"@+id/action_connection_test_delete_unavailable\"\n                android:title=\"@string/connection_test_delete_unavailable\" />\n            <item\n                android:id=\"@+id/action_order\"\n                android:title=\"@string/group_order\">\n                <menu>\n                    <group android:checkableBehavior=\"single\">\n                        <item\n                            android:id=\"@+id/action_order_origin\"\n                            android:title=\"@string/group_order_origin\" />\n                        <item\n                            android:id=\"@+id/action_order_by_name\"\n                            android:title=\"@string/group_order_by_name\" />\n                        <item\n                            android:id=\"@+id/action_order_by_delay\"\n                            android:title=\"@string/group_order_by_delay\" />\n                    </group>\n                </menu>\n            </item>\n        </menu>\n    </item>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/add_route_menu.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_new_route\"\n        android:icon=\"@drawable/ic_baseline_add_road_24\"\n        android:title=\"@string/route_add\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_reset_route\"\n        android:title=\"@string/route_reset\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/action_manage_assets\"\n        android:title=\"@string/route_manage_assets\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/app_list_menu.xml",
    "content": "<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_invert_selections\"\n        android:title=\"@string/invert_selections\" />\n    <item\n        android:id=\"@+id/action_clear_selections\"\n        android:title=\"@string/clear_selections\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/action_export_clipboard\"\n        android:title=\"@string/action_export_clipboard\" />\n    <item\n        android:id=\"@+id/action_import_clipboard\"\n        android:title=\"@string/action_import\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/group_action_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@+id/action_share_subscription\"\n        android:title=\"@string/share_subscription\">\n        <menu>\n            <item\n                android:id=\"@+id/action_universal_clipboard\"\n                android:title=\"@string/action_export_clipboard\" />\n            <item\n                android:id=\"@+id/action_universal_qr\"\n                android:title=\"@string/share_qr_nfc\" />\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/action_export\"\n        android:title=\"@string/action_export\">\n        <menu>\n            <item\n                android:id=\"@+id/action_export_clipboard\"\n                android:title=\"@string/action_export_clipboard\" />\n            <item\n                android:id=\"@+id/action_export_file\"\n                android:title=\"@string/action_export_file\" />\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/action_clear\"\n        android:title=\"@string/clear_profiles\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/import_asset_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\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_import_file\"\n        android:icon=\"@drawable/ic_action_note_add\"\n        android:title=\"@string/action_import_file\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/logcat_menu.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_refresh\"\n        android:icon=\"@drawable/ic_baseline_refresh_24\"\n        android:title=\"@string/group_update\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_send_logcat\"\n        android:icon=\"@drawable/baseline_send_24\"\n        android:title=\"@string/logcat\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_clear_logcat\"\n        android:icon=\"@drawable/baseline_delete_sweep_24\"\n        android:title=\"@string/clear_logcat\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/main_drawer_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <group android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_configuration\"\n            android:icon=\"@drawable/ic_action_description\"\n            android:title=\"@string/menu_configuration\" />\n\n        <item\n            android:id=\"@+id/nav_group\"\n            android:icon=\"@drawable/ic_baseline_view_list_24\"\n            android:title=\"@string/menu_group\" />\n\n        <item\n            android:id=\"@+id/nav_route\"\n            android:icon=\"@drawable/ic_maps_directions\"\n            android:title=\"@string/menu_route\" />\n\n        <item\n            android:id=\"@+id/nav_settings\"\n            android:icon=\"@drawable/ic_action_settings\"\n            android:title=\"@string/settings\" />\n    </group>\n\n    <group\n        android:id=\"@+id/misc\"\n        android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_logcat\"\n            android:icon=\"@drawable/ic_baseline_bug_report_24\"\n            android:title=\"@string/menu_log\" />\n        <item\n            android:id=\"@+id/nav_traffic\"\n            android:icon=\"@drawable/ic_baseline_transform_24\"\n            android:title=\"@string/menu_dashboard\" />\n        <item\n            android:id=\"@+id/nav_tools\"\n            android:icon=\"@drawable/baseline_construction_24\"\n            android:title=\"@string/menu_tools\" />\n    </group>\n\n    <group\n        android:id=\"@+id/about\"\n        android:checkableBehavior=\"single\">\n        <item\n            android:id=\"@+id/nav_tuiguang\"\n            android:icon=\"@drawable/ic_social_share\"\n            android:title=\"@string/ads\" />\n        <item\n            android:id=\"@+id/nav_faq\"\n            android:icon=\"@drawable/ic_device_data_usage\"\n            android:title=\"@string/document\" />\n        <item\n            android:id=\"@+id/nav_about\"\n            android:icon=\"@drawable/ic_baseline_info_24\"\n            android:title=\"@string/menu_about\" />\n    </group>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/per_app_proxy_menu.xml",
    "content": "<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_invert_selections\"\n        android:title=\"@string/invert_selections\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/action_clear_selections\"\n        android:title=\"@string/clear_selections\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/action_export_clipboard\"\n        android:icon=\"?attr/actionModeCopyDrawable\"\n        android:title=\"@string/action_export_clipboard\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_import_clipboard\"\n        android:icon=\"?attr/actionModePasteDrawable\"\n        android:title=\"@string/action_import\"\n        app:showAsAction=\"always\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/profile_apply_menu.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 android:id=\"@+id/action_apply\"\n          android:title=\"@string/apply\"\n          android:icon=\"@drawable/ic_action_done\"\n          android:alphabeticShortcut=\"s\"\n          app:showAsAction=\"always\"/>\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/profile_config_menu.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_delete\"\n        android:alphabeticShortcut=\"d\"\n        android:icon=\"@drawable/ic_action_delete\"\n        android:title=\"@string/delete\"\n        app:showAsAction=\"ifRoom\" />\n    <item\n        android:id=\"@+id/action_apply\"\n        android:alphabeticShortcut=\"s\"\n        android:icon=\"@drawable/ic_action_done\"\n        android:title=\"@string/apply\"\n        app:showAsAction=\"always\" />\n    <item\n        android:id=\"@+id/action_create_shortcut\"\n        android:title=\"@string/create_shortcut\"\n        android:visible=\"false\" />\n    <item\n        android:id=\"@+id/action_move\"\n        android:title=\"@string/move\"\n        android:visible=\"false\" />\n    <item\n        android:id=\"@+id/action_custom_outbound_json\"\n        android:title=\"@string/custom_outbound_json\"\n        android:visible=\"false\" />\n    <item\n        android:id=\"@+id/action_custom_config_json\"\n        android:title=\"@string/custom_config_json\"\n        android:visible=\"false\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/profile_share_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@+id/action_group_qr\"\n        android:title=\"@string/share_qr_nfc\">\n        <menu>\n            <item\n                android:id=\"@+id/action_standard_qr\"\n                android:title=\"@string/standard\" />\n            <item\n                android:id=\"@+id/action_universal_qr\"\n                android:title=\"SN Link\" />\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/action_group_clipboard\"\n        android:title=\"@string/action_export_clipboard\">\n        <menu>\n            <item\n                android:id=\"@+id/action_standard_clipboard\"\n                android:title=\"@string/standard\" />\n            <item\n                android:id=\"@+id/action_universal_clipboard\"\n                android:title=\"SN Link\" />\n        </menu>\n    </item>\n    <item\n        android:id=\"@+id/action_group_configuration\"\n\n        android:title=\"@string/menu_configuration\">\n        <menu>\n            <item\n                android:id=\"@+id/action_config_export_clipboard\"\n                android:title=\"@string/action_export_clipboard\" />\n            <item\n                android:id=\"@+id/action_config_export_file\"\n                android:title=\"@string/action_export_file\" />\n        </menu>\n    </item>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/scanner_menu.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 android:id=\"@+id/action_import_file\"\n          android:title=\"@string/action_import_file\"\n          android:icon=\"@drawable/ic_image_photo\"\n          android:alphabeticShortcut=\"o\"\n          app:showAsAction=\"always\"/>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/traffic_item_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:title=\"@string/action_copy\">\n        <menu>\n            <item\n                android:id=\"@+id/copy_label\"\n                android:title=\"@string/copy_label\" />\n            <item\n                android:id=\"@+id/copy_package_name\"\n                android:title=\"@string/copy_package_name\" />\n        </menu>\n    </item>\n    <item android:title=\"@string/action_open\">\n        <menu>\n            <item\n                android:id=\"@+id/open_app\"\n                android:title=\"@string/open_app\" />\n            <item\n                android:id=\"@+id/open_settings\"\n                android:title=\"@string/open_settings\" />\n            <item\n                android:id=\"@+id/open_market\"\n                android:title=\"@string/open_market\" />\n        </menu>\n    </item>\n\n    <item\n        android:id=\"@+id/create_rule\"\n        android:title=\"@string/create_rule\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/traffic_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@+id/action_clear_traffic_statistics\"\n        android:title=\"@string/clear_traffic_statistics\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/yacd_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item\n        android:id=\"@+id/action_set_url\"\n        android:title=\"@string/set_panel_url\" />\n    <item\n        android:id=\"@+id/close\"\n        android:title=\"@string/mal_close\" />\n</menu>\n"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-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=\"@mipmap/ic_launcher_foreground\"/>\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/raw/insecure.txt",
    "content": "The configuration (insecure) can be detected and identified, the transmission is fully visible to the censor and is not resistant to man-in-the-middle tampering with the content of the communication."
  },
  {
    "path": "app/src/main/res/raw/not_encrypted.txt",
    "content": "This configuration (not encrypted) is extremely easy to detect and identify, the transmission is fully visible to the censor, and there is no resistance to man-in-the-middle tampering with the content of the communication."
  },
  {
    "path": "app/src/main/res/raw/shadowsocks_stream_cipher.txt",
    "content": "This configuration (Shadowsocks streaming cipher) can be accurately proactively detected and decrypted by censors without requiring a password, and cannot be mitigated by turning on IV replay filters on the server side.\n\nLearn more: https://github.com/net4people/bbs/issues/24"
  },
  {
    "path": "app/src/main/res/raw/vmess_md5_auth.txt",
    "content": "This configuration (VMess MD5 authentication) has been deprecated by upstream because of its questionable resistance to tampering and concealment.\n\nAs of January 1, 2022, compatibility with MD5 authentication information will be disabled on the server side by default. Any client using MD5 authentication information will not be able to connect to a server with VMess MD5 authentication information disabled."
  },
  {
    "path": "app/src/main/res/raw-zh-rCN/insecure.txt",
    "content": "该配置 (不安全) 能够被检测识别，传输的内容对审查者完全可见，并且无法抵抗中间人篡改通讯内容."
  },
  {
    "path": "app/src/main/res/raw-zh-rCN/not_encrypted.txt",
    "content": "该配置 (未加密) 极易被检测识别，传输的内容对审查者完全可见，并且无法抵抗中间人篡改通讯内容."
  },
  {
    "path": "app/src/main/res/raw-zh-rCN/shadowsocks_stream_cipher.txt",
    "content": "该配置 (Shadowsocks 流式密码) 可以被准确地主动探测、在不需要密码的情况下被审查者解密流量, 且服务端开启 IV 重放过滤器也无法缓解.\n\n了解更多: https://github.com/net4people/bbs/issues/24"
  },
  {
    "path": "app/src/main/res/raw-zh-rCN/vmess_md5_auth.txt",
    "content": "该配置 (VMess MD5 认证) 抗篡改能力存疑, 隐蔽性存疑, 已被上游废弃.\n\n自 2022 年 1 月 1 日起, 服务器端将默认禁用对于 MD5 认证信息 的兼容. 任何使用 MD5 认证信息的客户端将无法连接到禁用 VMess MD5 认证信息的服务器端."
  },
  {
    "path": "app/src/main/res/resources.properties",
    "content": "unqualifiedResLocale=en-US\n"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string-array name=\"int_array_2\">\n        <item>0</item>\n        <item>1</item>\n    </string-array>\n\n    <string-array name=\"int_array_3\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n    </string-array>\n\n    <string-array name=\"int_array_4\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"int_array_5\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n        <item>4</item>\n    </string-array>\n\n    <string-array name=\"bypass_private_route\">\n        <item>1.0.0.0/8</item>\n        <item>2.0.0.0/7</item>\n        <item>4.0.0.0/6</item>\n        <item>8.0.0.0/7</item>\n        <item>11.0.0.0/8</item>\n        <item>12.0.0.0/6</item>\n        <item>16.0.0.0/4</item>\n        <item>32.0.0.0/3</item>\n        <item>64.0.0.0/3</item>\n        <item>96.0.0.0/6</item>\n        <item>100.0.0.0/10</item>\n        <item>100.128.0.0/9</item>\n        <item>101.0.0.0/8</item>\n        <item>102.0.0.0/7</item>\n        <item>104.0.0.0/5</item>\n        <item>112.0.0.0/10</item>\n        <item>112.64.0.0/11</item>\n        <item>112.96.0.0/12</item>\n        <item>112.112.0.0/13</item>\n        <item>112.120.0.0/14</item>\n        <item>112.124.0.0/19</item>\n        <item>112.124.32.0/21</item>\n        <item>112.124.40.0/22</item>\n        <item>112.124.44.0/23</item>\n        <item>112.124.46.0/24</item>\n        <item>112.124.48.0/20</item>\n        <item>112.124.64.0/18</item>\n        <item>112.124.128.0/17</item>\n        <item>112.125.0.0/16</item>\n        <item>112.126.0.0/15</item>\n        <item>112.128.0.0/9</item>\n        <item>113.0.0.0/8</item>\n        <item>114.0.0.0/10</item>\n        <item>114.64.0.0/11</item>\n        <item>114.96.0.0/12</item>\n        <item>114.112.0.0/15</item>\n        <item>114.114.0.0/18</item>\n        <item>114.114.64.0/19</item>\n        <item>114.114.96.0/20</item>\n        <item>114.114.112.0/23</item>\n        <item>114.114.115.0/24</item>\n        <item>114.114.116.0/22</item>\n        <item>114.114.120.0/21</item>\n        <item>114.114.128.0/17</item>\n        <item>114.115.0.0/16</item>\n        <item>114.116.0.0/14</item>\n        <item>114.120.0.0/13</item>\n        <item>114.128.0.0/9</item>\n        <item>115.0.0.0/8</item>\n        <item>116.0.0.0/6</item>\n        <item>120.0.0.0/6</item>\n        <item>124.0.0.0/7</item>\n        <item>126.0.0.0/8</item>\n        <item>128.0.0.0/3</item>\n        <item>160.0.0.0/5</item>\n        <item>168.0.0.0/8</item>\n        <item>169.0.0.0/9</item>\n        <item>169.128.0.0/10</item>\n        <item>169.192.0.0/11</item>\n        <item>169.224.0.0/12</item>\n        <item>169.240.0.0/13</item>\n        <item>169.248.0.0/14</item>\n        <item>169.252.0.0/15</item>\n        <item>169.255.0.0/16</item>\n        <item>170.0.0.0/7</item>\n        <item>172.0.0.0/12</item>\n        <item>172.32.0.0/11</item>\n        <item>172.64.0.0/10</item>\n        <item>172.128.0.0/9</item>\n        <item>173.0.0.0/8</item>\n        <item>174.0.0.0/7</item>\n        <item>176.0.0.0/4</item>\n        <item>192.0.0.8/29</item>\n        <item>192.0.0.16/28</item>\n        <item>192.0.0.32/27</item>\n        <item>192.0.0.64/26</item>\n        <item>192.0.0.128/25</item>\n        <item>192.0.1.0/24</item>\n        <item>192.0.3.0/24</item>\n        <item>192.0.4.0/22</item>\n        <item>192.0.8.0/21</item>\n        <item>192.0.16.0/20</item>\n        <item>192.0.32.0/19</item>\n        <item>192.0.64.0/18</item>\n        <item>192.0.128.0/17</item>\n        <item>192.1.0.0/16</item>\n        <item>192.2.0.0/15</item>\n        <item>192.4.0.0/14</item>\n        <item>192.8.0.0/13</item>\n        <item>192.16.0.0/12</item>\n        <item>192.32.0.0/11</item>\n        <item>192.64.0.0/12</item>\n        <item>192.80.0.0/13</item>\n        <item>192.88.0.0/18</item>\n        <item>192.88.64.0/19</item>\n        <item>192.88.96.0/23</item>\n        <item>192.88.98.0/24</item>\n        <item>192.88.100.0/22</item>\n        <item>192.88.104.0/21</item>\n        <item>192.88.112.0/20</item>\n        <item>192.88.128.0/17</item>\n        <item>192.89.0.0/16</item>\n        <item>192.90.0.0/15</item>\n        <item>192.92.0.0/14</item>\n        <item>192.96.0.0/11</item>\n        <item>192.128.0.0/11</item>\n        <item>192.160.0.0/13</item>\n        <item>192.169.0.0/16</item>\n        <item>192.170.0.0/15</item>\n        <item>192.172.0.0/14</item>\n        <item>192.176.0.0/12</item>\n        <item>192.192.0.0/10</item>\n        <item>193.0.0.0/8</item>\n        <item>194.0.0.0/7</item>\n        <item>196.0.0.0/7</item>\n        <item>198.0.0.0/12</item>\n        <item>198.16.0.0/15</item>\n        <item>198.20.0.0/14</item>\n        <item>198.24.0.0/13</item>\n        <item>198.32.0.0/12</item>\n        <item>198.48.0.0/15</item>\n        <item>198.50.0.0/16</item>\n        <item>198.51.0.0/18</item>\n        <item>198.51.64.0/19</item>\n        <item>198.51.96.0/22</item>\n        <item>198.51.101.0/24</item>\n        <item>198.51.102.0/23</item>\n        <item>198.51.104.0/21</item>\n        <item>198.51.112.0/20</item>\n        <item>198.51.128.0/17</item>\n        <item>198.52.0.0/14</item>\n        <item>198.56.0.0/13</item>\n        <item>198.64.0.0/10</item>\n        <item>198.128.0.0/9</item>\n        <item>199.0.0.0/8</item>\n        <item>200.0.0.0/7</item>\n        <item>202.0.0.0/8</item>\n        <item>203.0.0.0/18</item>\n        <item>203.0.64.0/19</item>\n        <item>203.0.96.0/20</item>\n        <item>203.0.112.0/24</item>\n        <item>203.0.114.0/23</item>\n        <item>203.0.116.0/22</item>\n        <item>203.0.120.0/21</item>\n        <item>203.0.128.0/17</item>\n        <item>203.1.0.0/16</item>\n        <item>203.2.0.0/15</item>\n        <item>203.4.0.0/14</item>\n        <item>203.8.0.0/13</item>\n        <item>203.16.0.0/12</item>\n        <item>203.32.0.0/11</item>\n        <item>203.64.0.0/10</item>\n        <item>203.128.0.0/9</item>\n        <item>204.0.0.0/6</item>\n        <item>208.0.0.0/4</item>\n    </string-array>\n\n    <string-array name=\"ss_enc_method_value\">\n        <item>2022-blake3-aes-128-gcm</item>\n        <item>2022-blake3-aes-256-gcm</item>\n        <item>2022-blake3-chacha20-poly1305</item>\n        <item>none</item>\n        <item>aes-128-gcm</item>\n        <item>aes-192-gcm</item>\n        <item>aes-256-gcm</item>\n        <item>chacha20-ietf-poly1305</item>\n        <item>xchacha20-ietf-poly1305</item>\n        <item>aes-128-ctr</item>\n        <item>aes-192-ctr</item>\n        <item>aes-256-ctr</item>\n        <item>aes-128-cfb</item>\n        <item>aes-192-cfb</item>\n        <item>aes-256-cfb</item>\n        <item>rc4-md5</item>\n        <item>chacha20-ietf</item>\n        <item>xchacha20</item>\n    </string-array>\n\n    <string-array name=\"service_modes\">\n        <item>@string/service_mode_vpn</item>\n        <item>@string/service_mode_proxy</item>\n        <!--\n                <item>@string/service_mode_transproxy</item>\n        -->\n    </string-array>\n    <string-array name=\"service_mode_values\">\n        <item>vpn</item>\n        <item>proxy</item>\n        <!--\n                <item>transproxy</item>\n        -->\n    </string-array>\n\n    <string-array name=\"route_protocol_entry\">\n        <item>TCP and UDP</item>\n        <item>TCP</item>\n        <item>UDP</item>\n    </string-array>\n\n    <string-array name=\"route_protocol_value\">\n        <item />\n        <item>tcp</item>\n        <item>udp</item>\n    </string-array>\n\n    <string-array name=\"outbound_entry\">\n        <item>@string/route_proxy</item>\n        <item>@string/route_bypass</item>\n        <item>@string/route_block</item>\n        <item>@string/route_profile</item>\n    </string-array>\n\n    <string-array name=\"outbound_value\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"front_proxy_entry\">\n        <item>@string/ssh_auth_type_none</item>\n        <item>@string/route_profile</item>\n    </string-array>\n\n    <string-array name=\"front_proxy_value\">\n        <item>0</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"networks_value\">\n        <item>tcp</item>\n        <item>ws</item>\n        <item>http</item>\n        <item>quic</item>\n        <item>grpc</item>\n        <item>httpupgrade</item>\n    </string-array>\n\n    <string-array name=\"trojan_go_networks_entry\">\n        <item>NONE</item>\n        <item>WEBSOCKET</item>\n    </string-array>\n\n    <string-array name=\"trojan_go_networks_value\">\n        <item>none</item>\n        <item>ws</item>\n    </string-array>\n\n    <string-array name=\"trojan_go_security_entry\">\n        <item>NONE</item>\n        <item>SHADOWSOCKS</item>\n    </string-array>\n\n    <string-array name=\"trojan_go_security_value\">\n        <item>none</item>\n        <item>ss</item>\n    </string-array>\n\n    <string-array name=\"trojan_go_methods\">\n        <item>AES-128-GCM</item>\n        <item>AES-256-GCM</item>\n        <item>CHACHA20-IETF-POLY1305</item>\n    </string-array>\n\n    <string-array name=\"vmess_encryption_value\">\n        <item>chacha20-poly1305</item>\n        <item>aes-128-gcm</item>\n        <item>auto</item>\n        <item>none</item>\n        <item>zero</item>\n    </string-array>\n\n    <string-array name=\"transport_layer_encryption_value\">\n        <item>none</item>\n        <item>tls</item>\n    </string-array>\n\n    <string-array name=\"notification_entry\">\n        <item>@string/disable</item>\n        <item>500ms</item>\n        <item>1s</item>\n        <item>3s</item>\n        <item>10s</item>\n    </string-array>\n\n    <string-array name=\"notification_value\">\n        <item>0</item>\n        <item>500</item>\n        <item>1000</item>\n        <item>3000</item>\n        <item>10000</item>\n    </string-array>\n\n    <string-array name=\"domain_strategy\">\n        <item>AsIs</item>\n        <item>IPIfNonMatch</item>\n        <item>IPOnDemand</item>\n    </string-array>\n\n    <string-array name=\"transproxy_mode\">\n        <item>REDIRECT</item>\n        <item>TPROXY</item>\n    </string-array>\n\n    <string-array name=\"mieru_protocol\">\n        <item>TCP</item>\n        <item>UDP</item>\n    </string-array>\n\n    <string-array name=\"naive_proto_entry\">\n        <item>HTTPS</item>\n        <item>QUIC</item>\n    </string-array>\n\n    <string-array name=\"naive_proto_value\">\n        <item>https</item>\n        <item>quic</item>\n    </string-array>\n\n    <string-array name=\"night_mode\">\n        <item>@string/follow_system</item>\n        <item>@string/enable</item>\n        <item>@string/disable</item>\n        <item>@string/auto</item>\n    </string-array>\n    <string-array name=\"ipv6_mode\">\n        <item>@string/disable</item>\n        <item>@string/enable</item>\n        <item>@string/prefer</item>\n        <item>@string/only</item>\n    </string-array>\n\n    <string-array name=\"rules_dat_provider\">\n        <item>@string/route_rules_official</item>\n        <item>Loyalsoldier (soffchen/sing-geosite)</item>\n        <item>Chocolate4U/Iran-sing-box-rules</item>\n        <item>L11R/Antizapret-sing-box-geo</item>\n    </string-array>\n\n    <string-array name=\"balancer_type\">\n        <item>@string/list</item>\n        <item>@string/menu_group</item>\n    </string-array>\n\n    <string-array name=\"balancer_strategy_entry\">\n        <item>@string/random</item>\n        <item>@string/leastPing</item>\n    </string-array>\n    <string-array name=\"balancer_strategy_value\">\n        <item />\n        <item>leastPing</item>\n    </string-array>\n\n\n    <string-array name=\"group_types\">\n        <item>@string/group_basic</item>\n        <item>@string/subscription</item>\n    </string-array>\n\n    <string-array name=\"hysteria_auth_type\">\n        <item>@string/plugin_disabled</item>\n        <item>STRING</item>\n        <item>BASE64</item>\n    </string-array>\n\n    <string-array name=\"hysteria_version\">\n        <item>1</item>\n        <item>2</item>\n    </string-array>\n\n    <string-array name=\"tuic_version\">\n        <item>5</item>\n        <item>4</item>\n    </string-array>\n\n    <string-array name=\"socks_versions\">\n        <item>SOCKS4</item>\n        <item>SOCKS4A</item>\n        <item>SOCKS5</item>\n    </string-array>\n\n    <string-array name=\"simple_obfs\">\n        <item>http</item>\n        <item>tls</item>\n    </string-array>\n\n    <string-array name=\"group_orders\">\n        <item>@string/group_order_origin</item>\n        <item>@string/group_order_by_name</item>\n        <item>@string/group_order_by_delay</item>\n    </string-array>\n\n    <string-array name=\"ssh_auth_type\">\n        <item>@string/ssh_auth_type_none</item>\n        <item>@string/password</item>\n        <item>@string/ssh_public_key</item>\n    </string-array>\n\n    <string-array name=\"tun_implementation\">\n        <item>gVisor</item>\n        <item>System</item>\n        <item>Mixed</item>\n    </string-array>\n\n    <string-array name=\"hysteria_protocol\">\n        <item>UDP</item>\n        <item>FakeTCP (Root Required)</item>\n        <item>WeChat Video</item>\n    </string-array>\n\n    <string-array name=\"packet_encoding_entry\">\n        <item>none</item>\n        <item>packet</item>\n        <item>xudp</item>\n    </string-array>\n\n    <string-array name=\"mtu_select\">\n        <item>1500</item>\n        <item>9000</item>\n    </string-array>\n\n    <string-array name=\"mux_select_init\" />\n\n    <string-array name=\"mux_type\">\n        <item>h2mux</item>\n        <item>smux</item>\n        <item>yamux</item>\n    </string-array>\n\n    <string-array name=\"dns_network_select\">\n        <item>auto</item>\n        <item>prefer_ipv6</item>\n        <item>prefer_ipv4</item>\n        <item>ipv4_only</item>\n        <item>ipv6_only</item>\n    </string-array>\n\n    <string-array name=\"exe_prefer_provider\">\n        <item>Matsuri</item>\n        <item>SagerNet</item>\n    </string-array>\n\n    <string-array name=\"app_tls_version\">\n        <item>1.2</item>\n        <item>1.3</item>\n    </string-array>\n\n    <string-array name=\"utls_fingerprint_entry\">\n        <item></item>\n        <item>chrome</item>\n        <item>firefox</item>\n        <item>edge</item>\n        <item>safari</item>\n        <item>360</item>\n        <item>qq</item>\n        <item>ios</item>\n        <item>android</item>\n        <item>random</item>\n        <item>randomized</item>\n    </string-array>\n\n    <string-array name=\"tuic_udp_relay_mode_value\">\n        <item>native</item>\n        <item>quic</item>\n    </string-array>\n\n    <string-array name=\"tuic_congestion_controller_value\">\n        <item>cubic</item>\n        <item>new_reno</item>\n        <item>bbr</item>\n    </string-array>\n\n    <string-array name=\"xtls_flow_value\" translatable=\"false\">\n        <item></item>\n        <item>xtls-rprx-vision</item>\n    </string-array>\n\n    <string-array name=\"box_shadowsocks_plugins\" translatable=\"false\">\n        <item></item>\n        <item>obfs-local</item>\n        <item>v2ray-plugin</item>\n    </string-array>\n\n    <string-array name=\"log_level\" translatable=\"false\">\n        <item>none</item>\n        <item>warn</item>\n        <item>info</item>\n        <item>debug</item>\n        <item>trace</item>\n    </string-array>\n\n    <string-array name=\"shadowtls_version_value\">\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"traffic_sniffing_values\">\n        <item>@string/off</item>\n        <item>@string/sniff_routing</item>\n        <item>@string/sniff_override</item>\n    </string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n    <attr name=\"colorMaterial100\" format=\"color\" />\n    <attr name=\"colorMaterial300\" format=\"color\" />\n    <attr name=\"accentOrTextSecondary\" format=\"color\" />\n    <attr name=\"accentOrTextPrimary\" format=\"color\" />\n    <attr name=\"primaryOrTextSecondary\" format=\"color\" />\n    <attr name=\"primaryOrTextPrimary\" format=\"color\" />\n    <attr name=\"fabColorBackground\" format=\"color\" />\n    <attr name=\"selectedColorPrimary\" format=\"color\" />\n    <attr name=\"whiteOrTextPrimary\" format=\"color\" />\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <integer-array name=\"material_colors\">\n        <item>@color/material_red_500</item>\n        <item>@color/color_pink_ssr</item>\n        <item>@color/material_pink_500</item>\n        <item>@color/material_purple_500</item>\n        <item>@color/material_deep_purple_500</item>\n        <item>@color/material_indigo_500</item>\n        <item>@color/material_blue_500</item>\n        <item>@color/material_light_blue_500</item>\n        <item>@color/material_cyan_500</item>\n        <item>@color/material_teal_500</item>\n        <item>@color/material_green_500</item>\n\n        <item>@color/material_light_green_500</item>\n        <item>@color/material_lime_500</item>\n        <item>@color/material_yellow_500</item>\n        <item>@color/material_amber_500</item>\n        <item>@color/material_orange_500</item>\n        <item>@color/material_deep_orange_500</item>\n\n        <item>@color/material_brown_500</item>\n        <item>@color/material_grey_500</item>\n        <item>@color/material_blue_grey_500</item>\n        <item>@color/material_light_black</item>\n    </integer-array>\n\n    <color name=\"material_amber_100\">#FFECB3</color>\n    <color name=\"material_amber_200\">#FFE082</color>\n    <color name=\"material_amber_300\">#FFD54F</color>\n    <color name=\"material_amber_400\">#FFCA28</color>\n    <color name=\"material_amber_50\">#FFF8E1</color>\n    <color name=\"material_amber_500\">#FFC107</color>\n    <color name=\"material_amber_600\">#FFB300</color>\n    <color name=\"material_amber_700\">#FFA000</color>\n    <color name=\"material_amber_800\">#FF8F00</color>\n    <color name=\"material_amber_900\">#FF6F00</color>\n    <color name=\"material_amber_accent_100\">#FFE57F</color>\n    <color name=\"material_amber_accent_200\">#FFD740</color>\n    <color name=\"material_amber_accent_400\">#FFC400</color>\n    <color name=\"material_amber_accent_700\">#FFAB00</color>\n    <color name=\"material_blue_100\">#BBDEFB</color>\n    <color name=\"material_blue_200\">#90CAF9</color>\n    <color name=\"material_blue_300\">#64B5F6</color>\n    <color name=\"material_blue_400\">#42A5F5</color>\n    <color name=\"material_blue_50\">#E3F2FD</color>\n    <color name=\"material_blue_500\">#2196F3</color>\n    <color name=\"material_blue_600\">#1E88E5</color>\n    <color name=\"material_blue_700\">#1976D2</color>\n    <color name=\"material_blue_800\">#1565C0</color>\n    <color name=\"material_blue_900\">#0D47A1</color>\n    <color name=\"material_blue_accent_100\">#82B1FF</color>\n    <color name=\"material_blue_accent_200\">#448AFF</color>\n    <color name=\"material_blue_accent_400\">#2979FF</color>\n    <color name=\"material_blue_accent_700\">#2962FF</color>\n    <color name=\"material_blue_grey_100\">#CFD8DC</color>\n    <color name=\"material_blue_grey_200\">#B0BEC5</color>\n    <color name=\"material_blue_grey_300\">#90A4AE</color>\n    <color name=\"material_blue_grey_400\">#78909C</color>\n    <color name=\"material_blue_grey_50\">#ECEFF1</color>\n    <color name=\"material_blue_grey_500\">#607D8B</color>\n    <color name=\"material_blue_grey_600\">#546E7A</color>\n    <color name=\"material_blue_grey_700\">#455A64</color>\n    <color name=\"material_blue_grey_800\" tools:override=\"true\">#37474F</color>\n    <color name=\"material_blue_grey_900\" tools:override=\"true\">#263238</color>\n    <color name=\"material_brown_100\">#D7CCC8</color>\n    <color name=\"material_brown_200\">#BCAAA4</color>\n    <color name=\"material_brown_300\">#A1887F</color>\n    <color name=\"material_brown_400\">#8D6E63</color>\n    <color name=\"material_brown_50\">#EFEBE9</color>\n    <color name=\"material_brown_500\">#795548</color>\n    <color name=\"material_brown_600\">#6D4C41</color>\n    <color name=\"material_brown_700\">#5D4037</color>\n    <color name=\"material_brown_800\">#4E342E</color>\n    <color name=\"material_brown_900\">#3E2723</color>\n    <color name=\"material_cyan_100\">#B2EBF2</color>\n    <color name=\"material_cyan_200\">#80DEEA</color>\n    <color name=\"material_cyan_300\">#4DD0E1</color>\n    <color name=\"material_cyan_400\">#26C6DA</color>\n    <color name=\"material_cyan_50\">#E0F7FA</color>\n    <color name=\"material_cyan_500\">#00BCD4</color>\n    <color name=\"material_cyan_600\">#00ACC1</color>\n    <color name=\"material_cyan_700\">#0097A7</color>\n    <color name=\"material_cyan_800\">#00838F</color>\n    <color name=\"material_cyan_900\">#006064</color>\n    <color name=\"material_cyan_accent_100\">#84FFFF</color>\n    <color name=\"material_cyan_accent_200\">#18FFFF</color>\n    <color name=\"material_cyan_accent_400\">#00E5FF</color>\n    <color name=\"material_cyan_accent_700\">#00B8D4</color>\n    <color name=\"material_deep_orange_100\">#FFCCBC</color>\n    <color name=\"material_deep_orange_200\">#FFAB91</color>\n    <color name=\"material_deep_orange_300\">#FF8A65</color>\n    <color name=\"material_deep_orange_400\">#FF7043</color>\n    <color name=\"material_deep_orange_50\">#FBE9E7</color>\n    <color name=\"material_deep_orange_500\">#FF5722</color>\n    <color name=\"material_deep_orange_600\">#F4511E</color>\n    <color name=\"material_deep_orange_700\">#E64A19</color>\n    <color name=\"material_deep_orange_800\">#D84315</color>\n    <color name=\"material_deep_orange_900\">#BF360C</color>\n    <color name=\"material_deep_orange_accent_100\">#FF9E80</color>\n    <color name=\"material_deep_orange_accent_200\">#FF6E40</color>\n    <color name=\"material_deep_orange_accent_400\">#FF3D00</color>\n    <color name=\"material_deep_orange_accent_700\">#DD2C00</color>\n    <color name=\"material_deep_purple_100\">#D1C4E9</color>\n    <color name=\"material_deep_purple_200\">#B39DDB</color>\n    <color name=\"material_deep_purple_300\">#9575CD</color>\n    <color name=\"material_deep_purple_400\">#7E57C2</color>\n    <color name=\"material_deep_purple_50\">#EDE7F6</color>\n    <color name=\"material_deep_purple_500\">#673AB7</color>\n    <color name=\"material_deep_purple_600\">#5E35B1</color>\n    <color name=\"material_deep_purple_700\">#512DA8</color>\n    <color name=\"material_deep_purple_800\">#4527A0</color>\n    <color name=\"material_deep_purple_900\">#311B92</color>\n    <color name=\"material_deep_purple_accent_100\">#B388FF</color>\n    <color name=\"material_deep_purple_accent_200\">#7C4DFF</color>\n    <color name=\"material_deep_purple_accent_400\">#651FFF</color>\n    <color name=\"material_deep_purple_accent_700\">#6200EA</color>\n    <color name=\"material_green_100\">#C8E6C9</color>\n    <color name=\"material_green_200\">#A5D6A7</color>\n    <color name=\"material_green_300\">#81C784</color>\n    <color name=\"material_green_400\">#66BB6A</color>\n    <color name=\"material_green_50\">#E8F5E9</color>\n    <color name=\"material_green_500\">#4CAF50</color>\n    <color name=\"material_green_600\">#43A047</color>\n    <color name=\"material_green_700\">#388E3C</color>\n    <color name=\"material_green_800\">#2E7D32</color>\n    <color name=\"material_green_900\">#1B5E20</color>\n    <color name=\"material_green_accent_100\">#B9F6CA</color>\n    <color name=\"material_green_accent_200\">#69F0AE</color>\n    <color name=\"material_green_accent_400\">#00E676</color>\n    <color name=\"material_green_accent_700\">#00C853</color>\n    <color name=\"material_grey_200\">#EEEEEE</color>\n    <color name=\"material_grey_400\">#BDBDBD</color>\n    <color name=\"material_grey_500\">#9E9E9E</color>\n    <color name=\"material_grey_700\">#616161</color>\n    <color name=\"material_indigo_100\">#C5CAE9</color>\n    <color name=\"material_indigo_200\">#9FA8DA</color>\n    <color name=\"material_indigo_300\">#7986CB</color>\n    <color name=\"material_indigo_400\">#5C6BC0</color>\n    <color name=\"material_indigo_50\">#E8EAF6</color>\n    <color name=\"material_indigo_500\">#3F51B5</color>\n    <color name=\"material_indigo_600\">#3949AB</color>\n    <color name=\"material_indigo_700\">#303F9F</color>\n    <color name=\"material_indigo_800\">#283593</color>\n    <color name=\"material_indigo_900\">#1A237E</color>\n    <color name=\"material_indigo_accent_100\">#8C9EFF</color>\n    <color name=\"material_indigo_accent_200\">#536DFE</color>\n    <color name=\"material_indigo_accent_400\">#3D5AFE</color>\n    <color name=\"material_indigo_accent_700\">#304FFE</color>\n    <color name=\"material_light_black\">#212121</color>\n    <color name=\"material_light_blue_100\">#B3E5FC</color>\n    <color name=\"material_light_blue_200\">#81D4FA</color>\n    <color name=\"material_light_blue_300\">#4FC3F7</color>\n    <color name=\"material_light_blue_400\">#29B6F6</color>\n    <color name=\"material_light_blue_50\">#E1F5FE</color>\n    <color name=\"material_light_blue_500\">#03A9F4</color>\n    <color name=\"material_light_blue_600\">#039BE5</color>\n    <color name=\"material_light_blue_700\">#0288D1</color>\n    <color name=\"material_light_blue_800\">#0277BD</color>\n    <color name=\"material_light_blue_900\">#01579B</color>\n    <color name=\"material_light_blue_accent_100\">#80D8FF</color>\n    <color name=\"material_light_blue_accent_200\">#40C4FF</color>\n    <color name=\"material_light_blue_accent_400\">#00B0FF</color>\n    <color name=\"material_light_blue_accent_700\">#0091EA</color>\n    <color name=\"material_light_green_100\">#DCEDC8</color>\n    <color name=\"material_light_green_200\">#C5E1A5</color>\n    <color name=\"material_light_green_300\">#AED581</color>\n    <color name=\"material_light_green_400\">#9CCC65</color>\n    <color name=\"material_light_green_50\">#F1F8E9</color>\n    <color name=\"material_light_green_500\">#8BC34A</color>\n    <color name=\"material_light_green_600\">#7CB342</color>\n    <color name=\"material_light_green_700\">#689F38</color>\n    <color name=\"material_light_green_800\">#558B2F</color>\n    <color name=\"material_light_green_900\">#33691E</color>\n    <color name=\"material_light_green_accent_100\">#CCFF90</color>\n    <color name=\"material_light_green_accent_200\">#B2FF59</color>\n    <color name=\"material_light_green_accent_400\">#76FF03</color>\n    <color name=\"material_light_green_accent_700\">#64DD17</color>\n    <color name=\"material_light_white\">#eeeeee</color>\n    <color name=\"material_lime_100\">#F0F4C3</color>\n    <color name=\"material_lime_200\">#E6EE9C</color>\n    <color name=\"material_lime_300\">#DCE775</color>\n    <color name=\"material_lime_400\">#D4E157</color>\n    <color name=\"material_lime_50\">#F9FBE7</color>\n    <color name=\"material_lime_500\">#CDDC39</color>\n    <color name=\"material_lime_600\">#C0CA33</color>\n    <color name=\"material_lime_700\">#AFB42B</color>\n    <color name=\"material_lime_800\">#9E9D24</color>\n    <color name=\"material_lime_900\">#827717</color>\n    <color name=\"material_lime_accent_100\">#F4FF81</color>\n    <color name=\"material_lime_accent_200\">#EEFF41</color>\n    <color name=\"material_lime_accent_400\">#C6FF00</color>\n    <color name=\"material_lime_accent_700\">#AEEA00</color>\n    <color name=\"material_orange_100\">#FFE0B2</color>\n    <color name=\"material_orange_200\">#FFCC80</color>\n    <color name=\"material_orange_300\">#FFB74D</color>\n    <color name=\"material_orange_400\">#FFA726</color>\n    <color name=\"material_orange_50\">#FFF3E0</color>\n    <color name=\"material_orange_500\">#FF9800</color>\n    <color name=\"material_orange_600\">#FB8C00</color>\n    <color name=\"material_orange_700\">#F57C00</color>\n    <color name=\"material_orange_800\">#EF6C00</color>\n    <color name=\"material_orange_900\">#E65100</color>\n    <color name=\"material_orange_accent_100\">#FFD180</color>\n    <color name=\"material_orange_accent_200\">#FFAB40</color>\n    <color name=\"material_orange_accent_400\">#FF9100</color>\n    <color name=\"material_orange_accent_700\">#FF6D00</color>\n    <color name=\"material_pink_100\">#F8BBD0</color>\n    <color name=\"material_pink_200\">#F48FB1</color>\n    <color name=\"material_pink_300\">#F06292</color>\n    <color name=\"material_pink_400\">#EC407A</color>\n    <color name=\"material_pink_50\">#FCE4EC</color>\n    <color name=\"material_pink_500\">#E91E63</color>\n    <color name=\"material_pink_600\">#D81B60</color>\n    <color name=\"material_pink_700\">#C2185B</color>\n    <color name=\"material_pink_800\">#AD1457</color>\n    <color name=\"material_pink_900\">#880E4F</color>\n    <color name=\"material_pink_accent_100\">#FF80AB</color>\n    <color name=\"material_pink_accent_200\">#FF4081</color>\n    <color name=\"material_pink_accent_400\">#F50057</color>\n    <color name=\"material_pink_accent_700\">#C51162</color>\n    <color name=\"material_purple_100\">#E1BEE7</color>\n    <color name=\"material_purple_200\">#CE93D8</color>\n    <color name=\"material_purple_300\">#BA68C8</color>\n    <color name=\"material_purple_400\">#AB47BC</color>\n    <color name=\"material_purple_50\">#F3E5F5</color>\n    <color name=\"material_purple_500\">#9C27B0</color>\n    <color name=\"material_purple_600\">#8E24AA</color>\n    <color name=\"material_purple_700\">#7B1FA2</color>\n    <color name=\"material_purple_800\">#6A1B9A</color>\n    <color name=\"material_purple_900\">#4A148C</color>\n    <color name=\"material_purple_accent_100\">#EA80FC</color>\n    <color name=\"material_purple_accent_200\">#E040FB</color>\n    <color name=\"material_purple_accent_400\">#D500F9</color>\n    <color name=\"material_purple_accent_700\">#AA00FF</color>\n    <color name=\"material_red_100\">#FFCDD2</color>\n    <color name=\"material_red_200\">#EF9A9A</color>\n    <color name=\"material_red_300\">#E57373</color>\n    <color name=\"material_red_400\">#EF5350</color>\n    <color name=\"material_red_50\">#FFEBEE</color>\n    <color name=\"material_red_500\">#F44336</color>\n    <color name=\"material_red_600\">#E53935</color>\n    <color name=\"material_red_700\">#D32F2F</color>\n    <color name=\"material_red_800\">#C62828</color>\n    <color name=\"material_red_900\">#B71C1C</color>\n    <color name=\"material_red_accent_100\">#FF8A80</color>\n    <color name=\"material_red_accent_200\">#FF5252</color>\n    <color name=\"material_red_accent_400\">#FF1744</color>\n    <color name=\"material_red_accent_700\">#D50000</color>\n    <color name=\"material_teal_100\">#B2DFDB</color>\n    <color name=\"material_teal_200\">#80CBC4</color>\n    <color name=\"material_teal_300\">#4DB6AC</color>\n    <color name=\"material_teal_400\">#26A69A</color>\n    <color name=\"material_teal_50\">#E0F2F1</color>\n    <color name=\"material_teal_500\">#009688</color>\n    <color name=\"material_teal_600\">#00897B</color>\n    <color name=\"material_teal_700\">#00796B</color>\n    <color name=\"material_teal_800\">#00695C</color>\n    <color name=\"material_teal_900\">#004D40</color>\n    <color name=\"material_teal_accent_100\">#A7FFEB</color>\n    <color name=\"material_teal_accent_200\">#64FFDA</color>\n    <color name=\"material_teal_accent_400\">#1DE9B6</color>\n    <color name=\"material_teal_accent_700\">#00BFA5</color>\n    <color name=\"material_yellow_100\">#FFF9C4</color>\n    <color name=\"material_yellow_200\">#FFF59D</color>\n    <color name=\"material_yellow_300\">#FFF176</color>\n    <color name=\"material_yellow_400\">#FFEE58</color>\n    <color name=\"material_yellow_50\">#FFFDE7</color>\n    <color name=\"material_yellow_500\">#FFEB3B</color>\n    <color name=\"material_yellow_600\">#FDD835</color>\n    <color name=\"material_yellow_700\">#FBC02D</color>\n    <color name=\"material_yellow_800\">#F9A825</color>\n    <color name=\"material_yellow_900\">#F57F17</color>\n    <color name=\"material_yellow_accent_100\">#FFFF8D</color>\n    <color name=\"material_yellow_accent_200\">#FFFF00</color>\n    <color name=\"material_yellow_accent_400\">#FFEA00</color>\n    <color name=\"material_yellow_accent_700\">#FFD600</color>\n\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n\n    <color name=\"fab_color_progress\">@color/material_light_black</color>\n\n    <color name=\"color_pink_ssr\">#fb7299</color>\n\n    <color name=\"color_ng_black_accent\">#9E9E9E</color>\n    <color name=\"color_ng_black_primary\">#2B2B2B</color>\n\n    <color name=\"preference_simple_menu_background\">#FAFAFA</color>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <dimen name=\"qrcode_size\">264dp</dimen>\n    <dimen name=\"main_list_padding_bottom\">88dp</dimen>\n    <dimen name=\"bottom_sheet_padding\">8dp</dimen>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_background\">#FFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">NekoBox</string>\n    <string name=\"app_name_long\" translatable=\"false\">NekoBox for Android</string>\n    <string name=\"app_desc\">The universal proxy toolchain for Android, written in Kotlin.</string>\n    <string name=\"project\">Project</string>\n    <string name=\"github\">Source code</string>\n    <string name=\"telegram\">Telegram update channel</string>\n    <string name=\"oss_licenses\">Open source licenses</string>\n    <string name=\"app_version\">Version</string>\n    <string name=\"version_x\">Version (%s)</string>\n    <string name=\"logcat\">Export debug information</string>\n    <string name=\"menu_configuration\">Configuration</string>\n    <string name=\"menu_group\">Group</string>\n    <string name=\"menu_about\">About</string>\n    <string name=\"theme\">Theme</string>\n    <string name=\"document\">Document</string>\n    <string name=\"group_default\">Ungrouped</string>\n    <string name=\"quick_toggle\">Toggle</string>\n    <string name=\"quick_enable\">Enable</string>\n    <string name=\"quick_disable\">Disable</string>\n    <string name=\"tile_title\">Switcher</string>\n    <string name=\"menu_traffic\">Traffic</string>\n    <string name=\"menu_dashboard\">sing-box Dashboard</string>\n    <!-- externel -->\n    <string name=\"action_copy\">Copy</string>\n    <string name=\"action_open\">Open</string>\n    <!-- group -->\n    <string name=\"group_name\">Group name</string>\n    <string name=\"group_update\">Update</string>\n    <string name=\"group_subscription_link\">Subscription Link</string>\n    <string name=\"group_name_required\">Group name required</string>\n    <string name=\"group_create\">Create group</string>\n    <string name=\"group_create_subscription\">Create subscription</string>\n    <string name=\"update_all_subscription\">Update all subscriptions</string>\n    <string name=\"group_edit\">Edit group</string>\n    <string name=\"group_edit_subscription\">Edit subscription</string>\n    <string name=\"group_status_empty\">Empty group</string>\n    <string name=\"group_status_empty_subscription\">Not updated yet</string>\n    <string name=\"group_status_proxies\">%d Proxies</string>\n    <string name=\"group_status_proxies_subscription\">%d Proxies | Updated on %s</string>\n    <string name=\"group_no_difference\">%s: No difference</string>\n    <string name=\"group_updated\">%s: Updated %d proxies</string>\n    <string name=\"group_show_diff\">Diff</string>\n    <string name=\"group_diff\">Diff (%s)</string>\n    <string name=\"group_delete_confirm_prompt\">Are you sure you want to remove this group?</string>\n    <string name=\"group_added\">Added: \\n%s</string>\n    <string name=\"group_changed\">Updated: \\n%s</string>\n    <string name=\"group_deleted\">Deleted: \\n%s</string>\n    <string name=\"group_duplicate\">Duplicate: \\n%s</string>\n    <!-- misc -->\n    <string name=\"service_mode\">Service Mode</string>\n    <string name=\"service_mode_proxy\">Proxy only</string>\n    <string name=\"service_mode_vpn\" translatable=\"false\">VPN</string>\n    <string name=\"port_proxy\">Proxy Port</string>\n    <string name=\"port_http\">HTTP Proxy Port</string>\n    <string name=\"port_transproxy\">Transproxy Port</string>\n    <string name=\"cag_route\">Route Settings</string>\n    <string name=\"allow_access\">Allow Connections from the LAN</string>\n    <string name=\"allow_access_sum\">Bind inbound servers to 0.0.0.0</string>\n    <string name=\"inbound_settings\">Inbound Settings</string>\n    <string name=\"general_settings\">App Settings</string>\n    <string name=\"require_http\">Enable HTTP inbound</string>\n    <string name=\"cag_ws\">WebSocket Settings</string>\n    <string name=\"ws_max_early_data\" translatable=\"false\">Max early data</string>\n    <string name=\"ws_browser_forwarding\">Browser Forwarding</string>\n    <string name=\"ws_browser_forwarding_sum\">Forward corresponding WebSockets through the browser.</string>\n    <string name=\"speed_interval\">Speed Notification Update Interval</string>\n    <string name=\"cag_misc\">Misc Settings</string>\n    <string name=\"show_stop\">Show Stop Button</string>\n    <string name=\"show_stop_sum\">If you don’t want to use Quick Tile as the switch</string>\n    <string name=\"show_direct_speed\">Show Direct Speed</string>\n    <string name=\"show_direct_speed_sum\">Show the traffic speed without proxy in the notification as\n        well</string>\n    <string name=\"security_settings\">TLS Security Settings</string>\n    <string name=\"allow_insecure\">Allow Insecure</string>\n    <string name=\"allow_insecure_sum\">Disable certificate checking. When enabled, this configuration\n        is as secure as plaintext</string>\n    <string name=\"traffic\" translatable=\"false\">%1$s↑ %2$s↓</string>\n    <string name=\"speed_detail\">Proxy： %1$s↑ %2$s↓\\nDirect： %3$s↑ %4$s↓</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_testing\">Testing…</string>\n    <string name=\"connection_test_available\">Success: HTTPS handshake took %dms</string>\n    <string name=\"connection_test_available_http\">Success: HTTP handshake took %dms</string>\n    <string name=\"connection_test_error\">Failed: %s</string>\n    <string name=\"connection_test_fail\">Internet Unavailable</string>\n    <string name=\"connection_test_error_status_code\">Error code: #%d</string>\n    <string name=\"cag_dns\">DNS Settings</string>\n    <string name=\"remote_dns\">Remote DNS</string>\n    <string name=\"direct_dns\">Direct DNS</string>\n    <string name=\"enable_dns_routing\">Enable DNS Routing</string>\n    <string name=\"dns_routing_message\">Resolve domains in bypass routes with Direct DNS. Be aware of\n        potential DNS leaks</string>\n    <string name=\"enable_fakedns\">Enable FakeDNS</string>\n    <string name=\"fakedns_message\">May cause other applications need to be restarted to reconnect to\n        the network after proxy stopped</string>\n    <string name=\"dns_hosts\">Domain rewrite</string>\n    <string name=\"port_local_dns\">Local DNS Port</string>\n    <string name=\"require_transproxy\">Enable Transproxy Inbound</string>\n    <string name=\"transproxy_mode\">Transproxy Mode</string>\n    <string name=\"connection_test_url\">Connection Test URL</string>\n    <!-- proxy category -->\n    <string name=\"profile_name\">Profile Name</string>\n    <string name=\"server_address\">Server</string>\n    <string name=\"server_port\">Remote Port</string>\n    <string name=\"username\">Username</string>\n    <string name=\"username_opt\">Username (Optional)</string>\n    <string name=\"password_opt\">Password (Optional)</string>\n    <string name=\"key_opt\">Key (Optional)</string>\n    <string name=\"password\">Password</string>\n    <string name=\"enc_method\">Encrypt Method</string>\n    <string name=\"protocol\">Protocol</string>\n    <string name=\"protocol_param\">Protocol Param</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"obfs_param\">Obfs Param</string>\n    <string name=\"uuid\">User ID</string>\n    <string name=\"alter_id\">Alter ID</string>\n    <string name=\"security\">Transport layer encryption</string>\n    <string name=\"network\">Network</string>\n    <string name=\"header_type\">Header Type</string>\n    <string name=\"ws_host\">WebSocket Host</string>\n    <string name=\"ws_path\">WebSocket Path</string>\n    <string name=\"http_host\">HTTP Host</string>\n    <string name=\"http_path\">HTTP Path</string>\n    <string name=\"grpc_service_name\">gRPC ServiceName</string>\n    <string name=\"tls\">Use TLS</string>\n    <string name=\"sni\">Server Name Indication</string>\n    <string name=\"alpn\">Application-Layer Protocol Negotiation</string>\n    <string name=\"certificates\">Certificates</string>\n    <string name=\"early_data_header_name\">Early Data Header Name</string>\n    <string name=\"encryption\">Encryption</string>\n    <string name=\"extra_headers\">Extra Headers</string>\n    <string name=\"config_type\">Config Type</string>\n    <string name=\"edit_config\">Edit Config</string>\n    <!-- feature category -->\n    <string name=\"ipv6\">IPv6 Route</string>\n    <string name=\"prefer\">Prefer</string>\n    <string name=\"only\">Only</string>\n    <string name=\"metered\">Metered Hint</string>\n    <string name=\"metered_summary\">Hint system to treat VPN as metered</string>\n    <string name=\"menu_route\">Route</string>\n    <string name=\"route_add\">Create Route</string>\n    <string name=\"delete_route_prompt\">Are you sure you want to remove this route?</string>\n    <string name=\"route_name\">Route Name</string>\n    <string name=\"route_proxy\">Proxy</string>\n    <string name=\"route_bypass\">Bypass</string>\n    <string name=\"route_block\">Block</string>\n    <string name=\"route_profile\">Select Profile…</string>\n    <string name=\"empty_route\">Empty Route</string>\n    <string name=\"empty_route_notice\">Set some rules before saving</string>\n    <string name=\"route_bypass_domain\">Domain rule for %s</string>\n    <string name=\"route_play_store\">Play store rule for %s</string>\n    <string name=\"route_bypass_ip\">IP rule for %s</string>\n    <string name=\"route_reset\">Reset</string>\n    <string name=\"route_manage_assets\">Manage Route Assets</string>\n    <string name=\"route_assets\">Assets</string>\n    <string name=\"route_rules_provider\">Rule Assets Provider</string>\n    <string name=\"route_rules_official\">Official</string>\n    <string name=\"route_asset_status\">Local version: %s</string>\n    <string name=\"route_asset_no_update\">No update</string>\n    <string name=\"route_asset_updated\">Updated</string>\n    <string name=\"route_not_asset\">Not an asset file: excepted .db, but %s</string>\n    <string name=\"route_opt_bypass_lan\">Bypass LAN</string>\n    <string name=\"route_opt_block_ads\">Block ADs</string>\n    <string name=\"route_opt_block_analysis\">Block analysis</string>\n    <string name=\"route_opt_block_quic\">Block QUIC</string>\n    <string name=\"domain_strategy\">Domain Resolution Strategy</string>\n    <string name=\"traffic_sniffing\">Enable Traffic Sniffing</string>\n    <string name=\"enable_mux\">Enable Multiplexer</string>\n    <string name=\"mux_sum\">Mux is designed to reduce TCP handshake latency, not to increase\n        connection throughput. Using Mux to watch videos, download or speed test is usually counter\n        productive. If the server does not support it, you will not be able to access the Internet.</string>\n    <string name=\"mux_concurrency\">Mux Concurrent Connections</string>\n    <string name=\"tcp_keep_alive_interval\">TCP keep active packet delivery interval</string>\n    <string name=\"proxied_apps\">Apps VPN mode</string>\n    <string name=\"proxied_apps_summary\">Configure VPN mode for selected apps</string>\n    <string name=\"on\">On</string>\n    <string name=\"off\">Off</string>\n    <string name=\"search_apps\">Search…</string>\n    <string name=\"scanning\">Scanning…</string>\n    <string name=\"invert_selections\">Invert selections</string>\n    <string name=\"clear_selections\">Clear selections</string>\n    <string name=\"bypass_apps\">Bypass</string>\n    <string name=\"show_system_apps\">Show system apps</string>\n    <string name=\"auto_connect\">Auto Connect</string>\n    <string name=\"auto_connect_summary\">Enable proxy on startup/app update if it was running before</string>\n    <string name=\"direct_boot_aware\">Allow Toggling in Lock Screen</string>\n    <string name=\"direct_boot_aware_summary\">Your selected profile information will be less\n        protected</string>\n    <!-- notification category -->\n    <string name=\"service_vpn\">VPN Service</string>\n    <string name=\"service_proxy\">Proxy Service</string>\n    <string name=\"forward_success\">Proxy started.</string>\n    <string name=\"invalid_server\">Invalid server name</string>\n    <string name=\"service_failed\">Failed: </string>\n    <string name=\"stop\">Stop</string>\n    <string name=\"stopping\">Shutting down…</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"vpn_permission_denied\">Permission denied to create a VPN service</string>\n    <string name=\"reboot_required\">Failed to start VPN service. You might need to reboot your\n        device.</string>\n    <!-- alert category -->\n    <string name=\"profile_empty\">Please select a profile</string>\n    <string name=\"connect\">Connect</string>\n    <string name=\"clipboard_empty\">Clipboard is empty</string>\n    <!-- menu category -->\n    <string name=\"settings\">Settings</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"share\">Share</string>\n    <string name=\"add_profile\">Add Profile</string>\n    <string name=\"select_profile\">Select Profile</string>\n    <string name=\"action_socks\" translatable=\"false\">SOCKS</string>\n    <string name=\"action_http\" translatable=\"false\">HTTP</string>\n    <string name=\"action_shadowsocks\" translatable=\"false\">Shadowsocks</string>\n    <string name=\"action_shadowsocksr\" translatable=\"false\">ShadowsocksR</string>\n    <string name=\"action_vmess\" translatable=\"false\">VMess</string>\n    <string name=\"action_trojan\" translatable=\"false\">Trojan</string>\n    <string name=\"action_trojan_go\" translatable=\"false\">Trojan Go</string>\n    <string name=\"action_mieru\" translatable=\"false\">Mieru</string>\n    <string name=\"action_naive\" translatable=\"false\">Naïve</string>\n    <string name=\"action_anytls\" translatable=\"false\">AnyTLS</string>\n    <string name=\"action_hysteria\" translatable=\"false\">Hysteria</string>\n    <string name=\"action_ssh\" translatable=\"false\">SSH</string>\n    <string name=\"action_wireguard\" translatable=\"false\">WireGuard</string>\n    <string name=\"action_tuic\" translatable=\"false\">TUIC</string>\n    <string name=\"proxy_chain\">Proxy Chain</string>\n    <string name=\"custom_config\">Custom Config</string>\n    <string name=\"balancer\">Balancer</string>\n    <string name=\"balancer_settings\">Balancer Settings</string>\n    <string name=\"balancer_type\">Type</string>\n    <string name=\"balancer_strategy\">Strategy</string>\n    <string name=\"chain_settings\">Chain Settings</string>\n    <string name=\"config_settings\">Config Settings</string>\n    <string name=\"circular_reference\">Circular reference</string>\n    <string name=\"circular_reference_sum\">The route cannot contain itself.</string>\n    <string name=\"clear_profiles\">Clear</string>\n    <string name=\"action_create_group\">Empty group</string>\n    <string name=\"action_from_link\">From subscription</string>\n    <string name=\"action_scan_china_apps\">Scan China apps</string>\n    <string name=\"action_export_file\">Export to file</string>\n    <string name=\"action_export_clipboard\">Export to Clipboard</string>\n    <string name=\"action_export\">Export</string>\n    <string name=\"action_import\">Import from Clipboard</string>\n    <string name=\"action_import_file\">Import from file</string>\n    <string name=\"action_export_msg\">Successfully export!</string>\n    <string name=\"action_export_err\">Failed to export.</string>\n    <string name=\"action_import_msg\">Successfully import!</string>\n    <string name=\"action_import_err\">Failed to import.</string>\n    <string name=\"file_manager_missing\">Your device lacks an Android standard file selector, please\n        install one, such as Material Files.</string>\n    <!-- share -->\n    <!-- profile -->\n    <string name=\"profile_config\">Profile config</string>\n    <string name=\"delete\">Remove</string>\n    <string name=\"delete_confirm_prompt\">Are you sure you want to remove this profile?</string>\n    <string name=\"share_qr_nfc\">QR code</string>\n    <string name=\"add_profile_methods_scan_qr_code\">Scan QR code</string>\n    <string name=\"add_profile_methods_manual_settings\">Manual Settings</string>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">Removed</item>\n        <item quantity=\"other\">%d items removed</item>\n    </plurals>\n    <plurals name=\"added\">\n        <item quantity=\"one\">Added</item>\n        <item quantity=\"other\">%d proxies added</item>\n    </plurals>\n    <string name=\"undo\">Undo</string>\n    <string name=\"insecure\">Insecure</string>\n    <string name=\"deprecated\">Deprecated</string>\n    <!-- tasker -->\n    <!-- status -->\n    <string name=\"connecting\">Connecting…</string>\n    <string name=\"vpn_connected\">Connected, tap to check connection</string>\n    <string name=\"not_connected\">Not connected</string>\n    <!-- subscriptions -->\n    <string name=\"subscriptions\">Subscriptions</string>\n    <string name=\"cleartext_http_warning\">Cleartext HTTP traffic is insecure</string>\n    <string name=\"error_title\">Error</string>\n    <string name=\"no_proxies_found\">No proxies found in the link</string>\n    <string name=\"no_proxies_found_in_subscription\">No proxies found in the subscription</string>\n    <string name=\"no_proxies_found_in_file\">No proxies found in the file</string>\n    <string name=\"no_proxies_found_in_clipboard\">No proxies found in the clipboard</string>\n    <string name=\"deduplication\">Deduplication</string>\n    <string name=\"donate\">Donate</string>\n    <string name=\"donate_info\">I love money</string>\n    <string name=\"ignore_battery_optimizations\">Ignore battery optimizations</string>\n    <string name=\"ignore_battery_optimizations_sum\">Remove some restrictions</string>\n    <!-- plugin -->\n    <string name=\"plugin\">Plugin</string>\n    <string name=\"plugin_configure\">Configure…</string>\n    <string name=\"plugin_disabled\">Disabled</string>\n    <string name=\"plugin_unknown\">Unknown plugin %s</string>\n    <string name=\"plugin_untrusted\">Warning: This plugin does not seem to come from a known trusted\n        source.</string>\n    <string name=\"plugin_auto_connect_unlock_only\">This plugin might not work with Auto Connect</string>\n    <string name=\"proxy_cat\">Server Settings</string>\n    <string name=\"ss_cat\">Shadowsocks Settings</string>\n    <string name=\"unsaved_changes_prompt\">Changes not saved. Do you want to save?</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"apply\">Apply</string>\n    <string name=\"need_reload\">Reload proxy service to apply changes</string>\n    <string name=\"license\">License</string>\n    <string name=\"route_warn\">Make sure you have read the documentation before adding custom rules,\n        otherwise you may not be able to connect to the Internet.</string>\n    <string name=\"lines\">%d Lines</string>\n    <string name=\"night_mode\">Night Mode</string>\n    <string name=\"follow_system\">Follow System</string>\n    <string name=\"enable\">Enable</string>\n    <string name=\"disable\">Disable</string>\n    <string name=\"auto\">Auto</string>\n    <string name=\"enable_log\">Enable Log</string>\n    <string name=\"enable_log_sum\">For debugging purposes</string>\n    <string name=\"list\">List</string>\n    <string name=\"random\">Random</string>\n    <string name=\"leastPing\" translatable=\"false\">Ping</string>\n    <string name=\"api_port\">API Port</string>\n    <string name=\"probe_interval\">Balancer observation interval</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"v2rayn\" translatable=\"false\">V2RayN</string>\n    <string name=\"available\" translatable=\"false\">%dms</string>\n    <string name=\"unavailable\">Unavailable</string>\n    <string name=\"always_show_address\">Always Show Address</string>\n    <string name=\"always_show_address_sum\">Always display the server address on the configuration\n        card</string>\n    <string name=\"clear_traffic_statistics\">Clear traffic statistics</string>\n    <string name=\"connection_test\">Connection test</string>\n    <string name=\"connection_test_clear_results\">Clear test results</string>\n    <string name=\"connection_test_tcp_ping\" translatable=\"false\">TCPing</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing unavailable</string>\n    <string name=\"connection_test_icmp_ping\" translatable=\"false\">ICMPing</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing unavailable</string>\n    <string name=\"connection_test_url_test\" translatable=\"false\">URL Test</string>\n    <string name=\"connection_test_domain_not_found\">Domain not found</string>\n    <string name=\"connection_test_refused\">Connection refused</string>\n    <string name=\"connection_test_unreachable\">Unreachable</string>\n    <string name=\"connection_test_timeout\">Timeout</string>\n    <string name=\"append_http_proxy\">Append HTTP Proxy to VPN</string>\n    <string name=\"append_http_proxy_sum\">HTTP proxy will be used directly from (browser/ some\n        supported apps), without going through the virtual NIC device (Android 10+)</string>\n    <string name=\"protocol_settings\">Protocol Settings</string>\n    <string name=\"trojan_provider\">Trojan Provider</string>\n    <string name=\"group_basic\">Basic</string>\n    <string name=\"group_settings\">Group Settings</string>\n    <string name=\"subscription\">Subscription</string>\n    <string name=\"subscription_settings\">Subscription Settings</string>\n    <string name=\"group_type\">Group Type</string>\n    <string name=\"subscription_type\">Subscription Type</string>\n    <string name=\"delete_group_prompt\">Are you sure you want to remove this group?</string>\n    <string name=\"force_resolve\">Force Resolve</string>\n    <string name=\"force_resolve_sum\">Resolve all domain names to IP addresses when updating. Host\n        and SNI will be automatically appended if possible</string>\n    <string name=\"deduplication_sum\">Remove duplicate configurations when updating</string>\n    <string name=\"raw\">Raw</string>\n    <string name=\"update_settings\">Update Settings</string>\n    <string name=\"auto_update\">Auto Update</string>\n    <string name=\"auto_update_delay\">Auto Update Delay (In minutes)</string>\n    <string name=\"update_when_connected_only\">Update only when connected</string>\n    <string name=\"update_when_connected_only_sum\">Prevent IP address leakage</string>\n    <string name=\"subscription_user_agent\">UserAgent</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"missing_plugin\">Missing Plugin</string>\n    <string name=\"profile_requiring_plugin\">Profile %s requires the %s plugin to be installed, but\n        it was not found.</string>\n    <string name=\"action_learn_more\">LEARN MORE</string>\n    <string name=\"action_download\">DOWNLOAD</string>\n    <string name=\"install_from_play_store\">Install from Play Store</string>\n    <string name=\"install_from_fdroid\">Install from F-Droid</string>\n    <string name=\"download\">Download</string>\n    <string name=\"ooc_subscription_token\" translatable=\"false\">OOCv1 API Token</string>\n    <string name=\"ooc_subscription_token_invalid\">Invalid OOCv1 Token</string>\n    <string name=\"update_subscription_warning\">Proxy is not connected, are you sure you want to\n        continue updating?</string>\n    <string name=\"ooc_warning\">Warning</string>\n    <string name=\"ooc_missing_protocol\">The subscription requires support for protocol %s, but it\n        cannot be found. Unsupported profiles will be ignored.</string>\n    <string name=\"service_subscription\">Subscription Update Service</string>\n    <string name=\"subscription_update\">Subscription Update</string>\n    <string name=\"subscription_update_message\">Updating %s …</string>\n    <string name=\"group_filter\">Filter</string>\n    <string name=\"subscription_used\">%s Used</string>\n    <string name=\"subscription_traffic\">%s Used / %s Remaining</string>\n    <string name=\"subscription_expire\">Expire: %s</string>\n    <string name=\"subscription_import\">Import subscription</string>\n    <string name=\"subscription_import_message\">Confirm you want to import subscription %s? If you\n        are coming from an untrusted source, doing this may result in your IP and this behavior\n        being leaked.</string>\n    <string name=\"profile_import\">Import profile</string>\n    <string name=\"profile_import_message\">Confirm you want to import profile %s?</string>\n    <string name=\"clear_profiles_message\">Are you sure you want to clear this group?</string>\n    <string name=\"apps\">Applications</string>\n    <string name=\"select_apps\">Select Applications</string>\n    <string name=\"apps_message\">%d Applications</string>\n    <string name=\"hysteria_obfs\">Obfuscation Password</string>\n    <string name=\"hysteria_auth_type\">Authentication Type</string>\n    <string name=\"hysteria_auth_payload\">Authentication Payload</string>\n    <string name=\"hysteria_upload_mbps\">Max Upload Speed (in Mbps)</string>\n    <string name=\"hysteria_download_mbps\">Max Download Speed (in Mbps)</string>\n    <string name=\"experimental_settings\">Experimental</string>\n    <string name=\"hysteria_stream_receive_window\">QUIC Stream Receive Window</string>\n    <string name=\"hysteria_connection_receive_window\">QUIC Connection Receive Window</string>\n    <string name=\"hysteria_disable_mtu_discovery\">Disable Path MTU Discovery</string>\n    <string name=\"group_order\">Order</string>\n    <string name=\"group_order_origin\">Origin</string>\n    <string name=\"group_order_by_name\">By Name</string>\n    <string name=\"group_order_by_delay\">By Delay</string>\n    <string name=\"plugin_exists_but_on_shit_system\">Profile %s requires the %s plugin, but your\n        proprietary equipment vendor (usually surveillance capital giants and malware maker)\n        tampered with your Android, making the plugin unusable.</string>\n\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (Shadowsocks Android Plugin)</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (Shadowsocks Android Plugin)</string>\n\n    <string name=\"traffic_uplink\" translatable=\"false\">%s | %s/s ↑</string>\n    <string name=\"traffic_downlink\" translatable=\"false\">%s | %s/s ↓</string>\n\n    <string name=\"traffic_uplink_total\" translatable=\"false\">%s ↑</string>\n    <string name=\"traffic_downlink_total\" translatable=\"false\">%s ↓</string>\n\n    <string name=\"tcp_connections\">%d TCP connections</string>\n    <string name=\"udp_connections\">%d UDP connections</string>\n\n    <string name=\"copy_label\">Copy Name</string>\n    <string name=\"copy_package_name\">Copy Package Name</string>\n    <string name=\"open_app\">Open App</string>\n    <string name=\"open_settings\">Open Settings</string>\n    <string name=\"open_market\">Open Market</string>\n    <string name=\"create_rule\">Create Rule</string>\n\n    <string name=\"copy_success\">Copy success!</string>\n    <string name=\"copy_failed\">Copy failed.</string>\n    <string name=\"app_no_launcher\">The app has no interface.</string>\n    <string name=\"route_for\">Rule for %s</string>\n\n    <string name=\"route_need_vpn\">Routing rule %s relies on the VPN to be in effect, so it is\n        ignored.</string>\n\n    <string name=\"profile_traffic_statistics\">Profile Traffic Statistics</string>\n    <string name=\"profile_traffic_statistics_summary\">When disabled, the used traffic will not be\n        counted</string>\n    <string name=\"no_statistics\">No statistics yet</string>\n    <string name=\"app_statistics_disabled\">App Traffic statistics disabled</string>\n    <string name=\"ssh_auth_type_none\">None</string>\n    <string name=\"ssh_public_key\">Public Key</string>\n    <string name=\"ssh_private_key\">Private Key</string>\n    <string name=\"ssh_private_key_passphrase\">Private Key Passphrase</string>\n    <string name=\"translate_platform\">Translate Platform</string>\n    <string name=\"menu_tools\">Tools</string>\n    <string name=\"menu_log\">Logs</string>\n    <string name=\"clear_logcat\">Clear Logcat</string>\n    <string name=\"wireguard_local_address\">Local Address</string>\n    <string name=\"wireguard_public_key\">Peer Public Key</string>\n    <string name=\"wireguard_psk\">Peer Pre-Shared Key</string>\n\n    <string name=\"cloudflare_wrap\" translatable=\"false\">Cloudflare Warp</string>\n    <string name=\"warp_license\">CloudFlare Warp is a free WireGuard VPN provider. By using it, you\n        agree to the TOS.</string>\n    <string name=\"warp_generate\">Generate Configuration</string>\n    <string name=\"generating\">Generating…</string>\n    <string name=\"tun_implementation\">TUN Implementation</string>\n    <string name=\"destination_override\">Override Destination</string>\n    <string name=\"destination_override_summary\">Use the sniffed domain to overwrite the destination\n        address, not just for routing</string>\n    <string name=\"resolve_destination\">Resolve Destination</string>\n    <string name=\"resolve_destination_summary\">If the destination address is a domain, it is then\n        passed out based on the IPv6 strategy (conflicts with FakeDNS)</string>\n    <string name=\"pcap\" translatable=\"false\">Pcap</string>\n    <string name=\"pcap_notice\">Pcap files will be saved to %s</string>\n    <string name=\"naive_insecure_concurrency\">Insecure Concurrency</string>\n    <string name=\"naive_insecure_concurrency_summary\">Use N concurrent tunnel connections to be more\n        robust under bad network conditions. More connections make the tunneling easier to detect\n        and less secure. This project strives for the strongest security against traffic analysis.\n        Using it in an insecure way defeats its purpose. \\n\\nIf you must use this, try N=2 first to\n        see if it solves your issues. Strongly recommend against using more than 4 connections here.</string>\n\n    <string name=\"stun_test\">NAT behaviour discovery</string>\n    <string name=\"stun_test_summary\">Determine the client\\'s NAT mapping behaviour and the NAT\n        filtering behaviour defined in RFC 3478 using STUN.</string>\n    <string name=\"start\">Start</string>\n    <string name=\"stun_attest_loading\">This may take a few minutes…</string>\n    <string name=\"nat_stun_server_hint\">Stun server</string>\n    <string name=\"nat_result_hint\">NAT check result</string>\n    <string name=\"tools_network\">Network</string>\n    <string name=\"mtu\" translatable=\"false\">MTU</string>\n\n    <string name=\"backup\">Backup</string>\n    <string name=\"backup_groups_and_configurations\">Groups and configurations</string>\n    <string name=\"backup_rules\">Routing rules</string>\n    <string name=\"backup_settings\">Settings</string>\n    <string name=\"backup_summary\">If the routing settings are not backed up with configurations,\n        then custom outbounds will be lost.</string>\n    <string name=\"backup_not_file\">Not an backup file: excepted .json, but %s</string>\n    <string name=\"invalid_backup_file\">Invalid backup file</string>\n    <string name=\"backup_import\">Import</string>\n    <string name=\"backup_import_summary\">Importing will overwrite the existing data.</string>\n    <string name=\"backup_importing\">Importing…</string>\n\n    <string name=\"packet_encoding\">Packet Encoding</string>\n    <string name=\"acquire_wake_lock\">Acquire WakeLock</string>\n    <string name=\"release_wake_lock\">Release WakeLock</string>\n    <string name=\"acquire_wake_lock_summary\">Keep the CPU on</string>\n    <string name=\"action_switch\">Switch</string>\n\n    <string name=\"tuic_udp_relay_mode\">UDP Relay Mode</string>\n    <string name=\"tuic_congestion_controller\">Congestion Controller</string>\n    <string name=\"tuic_disable_sni\">Disable SNI</string>\n    <string name=\"tuic_reduce_rtt\">Enable 0-RTT QUIC handshake</string>\n\n    <string name=\"please_update\">Your APP is too old (%s). And will stop working at %s. Please\n        update!</string>\n    <string name=\"please_update_force\">Your APP is too old (%s). And has been stopped working at %s.\n        Please update!</string>\n    <string name=\"connection_test_delete_unavailable\">Clear unavailable</string>\n    <string name=\"move\">Move</string>\n    <string name=\"exe_prefer_provider\">Plugin Preferred Provider</string>\n    <string name=\"create_shortcut\">Create Shortcut</string>\n    <string name=\"app_tls_version\">Subscription Min TLS Version</string>\n    <string name=\"hop_interval\">Port Hopping Interval(second)</string>\n    <string name=\"domain_strategy_for_remote\">Domain strategy for Remote</string>\n    <string name=\"domain_strategy_for_direct\">Domain strategy for Direct</string>\n    <string name=\"domain_strategy_for_server\">Domain strategy for Server address</string>\n    <string name=\"show_bottom_bar\">Show bottom bar like SagerNet</string>\n    <string name=\"utls_fingerprint\">uTLS fingerprint</string>\n    <string name=\"custom_outbound_json\">Custom outbound JSON</string>\n    <string name=\"custom_config_json\">Custom config JSON</string>\n    <string name=\"is_outbound_only\">The JSON set is outbound</string>\n    <string name=\"save\">Save</string>\n    <string name=\"set_panel_url\">Set panel URL</string>\n    <string name=\"enable_clash_api\">Enable Clash API</string>\n    <string name=\"log_level\">Log Level</string>\n    <string name=\"enable_clash_api_summary\">Provide clash api and yacd dashboard at 127.0.0.1:9090</string>\n    <string name=\"xtls_flow\">Flow (VLESS Sub-protocol)</string>\n    <string name=\"ads\">Ads</string>\n    <string name=\"bypass_lan_in_core\">Bypass LAN in Core</string>\n    <string name=\"need_restart\">Restart APP to apply changes</string>\n    <string name=\"use_selector\">Use selector</string>\n    <string name=\"front_proxy\">Front proxy</string>\n    <string name=\"landing_proxy\">Landing Proxy</string>\n    <string name=\"action_shadowtls\" translatable=\"false\">ShadowTLS</string>\n    <string name=\"protocol_version\">Protocol Version</string>\n    <string name=\"share_subscription\">Share Subscription</string>\n    <string name=\"show_group_in_notification\">Show group name in notification</string>\n    <string name=\"reset_connections\">Reset Connections</string>\n    <string name=\"remove_duplicate\">Remove duplicate servers</string>\n    <string name=\"mtu_help\">Long press the preference to set custom MTU.</string>\n    <string name=\"log_level_help\">Long press the preference to set the buffer size.</string>\n    <string name=\"tls_camouflage_settings\">TLS Camouflage Settings</string>\n    <string name=\"test_concurrency\">Test concurrency</string>\n    <string name=\"mux_type\">Mux protocol</string>\n    <string name=\"sniff_routing\">Sniff result for routing</string>\n    <string name=\"sniff_override\">Sniff result for destination</string>\n    <string name=\"resolve_server\">Resolve the server address according to the IPv6 policy</string>\n    <string name=\"auto_select_proxy_apps\">Auto select proxy apps</string>\n    <string name=\"auto_select_proxy_apps_message\">Auto select proxy apps, this will clear your\n        current selection.</string>\n    <string name=\"enable_ech\">Enable ECH</string>\n    <string name=\"enable_ech_sum\">Enable Encrypted Client Hello</string>\n    <string name=\"ech_settings\">ECH Settings</string>\n    <string name=\"ech_config\">ECH Config</string>\n    <string name=\"http_upgrade_host\">HTTPUpgrade Host</string>\n    <string name=\"http_upgrade_path\">HTTPUpgrade Path</string>\n    <string name=\"update_current_subscription\">Update current Group\\'s subscription</string>\n    <string name=\"group_not_subscription\">Group type is not subscription</string>\n    <string name=\"allow_insecure_on_request_sum\">Disable certificate checking when updating\n        subscriptions</string>\n    <string name=\"global_allow_insecure\">Always allow insecure</string>\n    <string name=\"mux_preference\">Mulitplex</string>\n    <string name=\"padding\">Padding</string>\n    <string name=\"network_change_reset_connections\">Reset outbound connections when network changes</string>\n    <string name=\"wake_reset_connections\">Reset outbound connections when device wake from sleep</string>\n    <string name=\"preview_version_hint\">This application is a preview version and may contain many problems. If you do not want to test it, please go to GitHub to download the Release version!</string>\n    <string name=\"check_update_preview\">Check for preview version updates</string>\n    <string name=\"check_update_release\">Check for release version updates</string>\n    <string name=\"update_dialog_title\">New version available</string>\n    <string name=\"update_dialog_message\">Current version: %1$s\\nAvailable version: %2$s\\nDo you want to download it?</string>\n    <string name=\"check_update_no\">Check successful, but no updates.</string>\n    <string name=\"reset_settings\">Restore default settings</string>\n    <string name=\"reset_settings_message\">Restore default settings, but data such as nodes and groups will be retained. To completely clear data, clear application data directly in the system settings.</string>\n    <string name=\"minimize\">Minimize</string>\n    <string name=\"app_list_permission_denied\">Unable to read installed apps.\\nThis is usually because the system has restricted app read permissions.\\nPlease grant permissions in the system settings.</string>\n    <string name=\"open_app_settings\">Open System Settings</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources>\n    <!-- Base application theme. -->\n    <style name=\"Theme.SagerNet\" parent=\"Theme.MaterialComponents.DayNight.NoActionBar\">\n        <item name=\"actionBarStyle\">@style/Widget.MaterialComponents.ActionBar.Solid</item>\n        <item name=\"actionModeCloseDrawable\">@drawable/ic_navigation_close</item>\n        <item name=\"colorButtonNormal\">?colorAccent</item>\n        <item name=\"android:navigationBarColor\">?colorPrimaryDark</item>\n        <item name=\"colorPrimary\">@color/material_pink_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_pink_700</item>\n        <item name=\"colorAccent\">@color/material_pink_accent_200</item>\n\n        <item name=\"colorMaterial100\">@color/material_pink_100</item>\n        <item name=\"colorMaterial300\">@color/material_pink_300</item>\n\n        <item name=\"accentOrTextSecondary\">?colorAccent</item>\n        <item name=\"accentOrTextPrimary\">?colorAccent</item>\n        <item name=\"primaryOrTextSecondary\">?colorPrimary</item>\n        <item name=\"primaryOrTextPrimary\">?colorPrimary</item>\n\n        <item name=\"fabColorBackground\">?colorPrimary</item>\n        <item name=\"selectedColorPrimary\">?colorPrimary</item>\n        <item name=\"actionBarTheme\">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>\n        <item name=\"tabTextColor\">#99FFFFFF</item>\n        <item name=\"tabSelectedTextColor\">@color/white</item>\n        <item name=\"tabIndicatorColor\">@color/white</item>\n        <item name=\"tabRippleColor\">@color/white</item>\n        <item name=\"whiteOrTextPrimary\">@color/white</item>\n\n        <item name=\"windowActionModeOverlay\">true</item>\n\n        <!-- Remove ActionBar but keep styles and themes -->\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n\n        <item name=\"chipStyle\">@style/Widget.MaterialComponents.Chip.Choice.SagerNet</item>\n\n        <item name=\"itemShapeAppearance\">\n            @style/ShapeAppearance.MaterialComponents.MediumComponent\n        </item>\n\n        <item name=\"alertDialogTheme\">\n            @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.SagerNet\n        </item>\n\n        <item name=\"materialAlertDialogTheme\">\n            @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.SagerNet\n        </item>\n\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog\" parent=\"Theme.MaterialComponents.DayNight.Dialog.Alert\">\n        <item name=\"actionBarStyle\">@style/Widget.MaterialComponents.ActionBar.Solid</item>\n        <item name=\"actionModeCloseDrawable\">@drawable/ic_navigation_close</item>\n        <item name=\"colorButtonNormal\">?colorAccent</item>\n        <item name=\"android:navigationBarColor\">?colorPrimaryDark</item>\n        <item name=\"colorPrimary\">@color/material_pink_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_pink_700</item>\n        <item name=\"colorAccent\">@color/material_pink_accent_200</item>\n\n        <item name=\"colorMaterial100\">@color/material_pink_100</item>\n        <item name=\"colorMaterial300\">@color/material_pink_300</item>\n\n        <item name=\"accentOrTextSecondary\">?colorAccent</item>\n        <item name=\"accentOrTextPrimary\">?colorAccent</item>\n        <item name=\"primaryOrTextSecondary\">?colorPrimary</item>\n        <item name=\"primaryOrTextPrimary\">?colorPrimary</item>\n\n        <item name=\"fabColorBackground\">?colorPrimary</item>\n        <item name=\"selectedColorPrimary\">?colorPrimary</item>\n        <item name=\"actionBarTheme\">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>\n        <item name=\"tabTextColor\">#99FFFFFF</item>\n        <item name=\"tabSelectedTextColor\">@color/white</item>\n        <item name=\"tabIndicatorColor\">@color/white</item>\n        <item name=\"tabRippleColor\">@color/white</item>\n        <item name=\"whiteOrTextPrimary\">@color/white</item>\n\n        <item name=\"windowActionModeOverlay\">true</item>\n\n        <!-- Remove ActionBar but keep styles and themes -->\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n\n        <item name=\"chipStyle\">@style/Widget.MaterialComponents.Chip.Choice.SagerNet</item>\n\n        <item name=\"itemShapeAppearance\">\n            @style/ShapeAppearance.MaterialComponents.MediumComponent\n        </item>\n\n        <item name=\"alertDialogTheme\">\n            @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.SagerNet\n        </item>\n\n        <item name=\"materialAlertDialogTheme\">\n            @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.SagerNet\n        </item>\n\n        <item name=\"windowFixedHeightMajor\" type=\"dimen\">95%</item>\n\n    </style>\n\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=\"ThemeOverlay.MaterialComponents.MaterialAlertDialog.SagerNet\">\n        <item name=\"buttonBarPositiveButtonStyle\">\n            @style/Widget.MaterialComponents.Button.TextButton.Dialog.Primary\n        </item>\n        <item name=\"buttonBarNegativeButtonStyle\">\n            @style/Widget.MaterialComponents.Button.TextButton.Dialog.Primary\n        </item>\n        <item name=\"buttonBarNeutralButtonStyle\">\n            @style/Widget.MaterialComponents.Button.TextButton.Dialog.Flush.Primary\n        </item>\n    </style>\n\n    <style name=\"Widget.MaterialComponents.Button.TextButton.Dialog.Primary\">\n        <item name=\"android:textColor\">?primaryOrTextPrimary</item>\n    </style>\n\n    <style name=\"Widget.MaterialComponents.Button.TextButton.Dialog.Flush.Primary\">\n        <item name=\"android:textColor\">?primaryOrTextPrimary</item>\n    </style>\n\n    <style name=\"Widget.MaterialComponents.Chip.Choice.SagerNet\">\n        <item name=\"textStartPadding\">30dp</item>\n        <item name=\"textEndPadding\">30dp</item>\n        <item name=\"chipBackgroundColor\">@color/chip_background</item>\n        <item name=\"android:textColor\">@color/chip_text_color</item>\n        <item name=\"checkedIconTint\">?primaryOrTextPrimary</item>\n        <item name=\"rippleColor\">@color/chip_ripple_color</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Pink_SSR\">\n        <item name=\"colorPrimary\">@color/color_pink_ssr</item>\n        <item name=\"colorPrimaryDark\">@color/color_pink_ssr</item>\n        <item name=\"colorAccent\">@color/material_pink_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_pink_accent_100</item>\n        <item name=\"colorMaterial300\">@color/color_pink_ssr</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Amber\">\n        <item name=\"colorPrimary\">@color/material_amber_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_amber_700</item>\n        <item name=\"colorAccent\">@color/material_amber_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_amber_100</item>\n        <item name=\"colorMaterial300\">@color/material_amber_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Blue\">\n        <item name=\"colorPrimary\">@color/material_blue_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_blue_700</item>\n        <item name=\"colorAccent\">@color/material_blue_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_blue_100</item>\n        <item name=\"colorMaterial300\">@color/material_blue_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.BlueGrey\">\n        <item name=\"colorPrimary\">@color/material_blue_grey_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_blue_grey_700</item>\n        <item name=\"colorAccent\">@color/material_blue_grey_200</item>\n        <item name=\"colorMaterial100\">@color/material_blue_grey_100</item>\n        <item name=\"colorMaterial300\">@color/material_blue_grey_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Brown\">\n        <item name=\"colorPrimary\">@color/material_brown_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_brown_700</item>\n        <item name=\"colorAccent\">@color/material_brown_200</item>\n        <item name=\"colorMaterial100\">@color/material_brown_100</item>\n        <item name=\"colorMaterial300\">@color/material_brown_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Cyan\">\n        <item name=\"colorPrimary\">@color/material_cyan_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_cyan_700</item>\n        <item name=\"colorAccent\">@color/material_cyan_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_cyan_100</item>\n        <item name=\"colorMaterial300\">@color/material_cyan_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.DeepOrange\">\n        <item name=\"colorPrimary\">@color/material_deep_orange_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_deep_orange_700</item>\n        <item name=\"colorAccent\">@color/material_deep_orange_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_deep_orange_100</item>\n        <item name=\"colorMaterial300\">@color/material_deep_orange_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.DeepPurple\">\n        <item name=\"colorPrimary\">@color/material_deep_purple_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_deep_purple_700</item>\n        <item name=\"colorAccent\">@color/material_deep_purple_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_deep_purple_100</item>\n        <item name=\"colorMaterial300\">@color/material_deep_purple_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Green\">\n        <item name=\"colorPrimary\">@color/material_green_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_green_700</item>\n        <item name=\"colorAccent\">@color/material_green_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_green_100</item>\n        <item name=\"colorMaterial300\">@color/material_green_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Grey\">\n        <item name=\"colorPrimary\">@color/material_grey_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_grey_700</item>\n        <item name=\"colorAccent\">@color/material_grey_200</item>\n        <item name=\"colorMaterial100\">@color/material_grey_100</item>\n        <item name=\"colorMaterial300\">@color/material_grey_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Indigo\">\n        <item name=\"colorPrimary\">@color/material_indigo_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_indigo_700</item>\n        <item name=\"colorAccent\">@color/material_indigo_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_indigo_100</item>\n        <item name=\"colorMaterial300\">@color/material_indigo_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.LightBlue\">\n        <item name=\"colorPrimary\">@color/material_light_blue_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_light_blue_700</item>\n        <item name=\"colorAccent\">@color/material_light_blue_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_light_blue_100</item>\n        <item name=\"colorMaterial300\">@color/material_light_blue_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.LightGreen\">\n        <item name=\"colorPrimary\">@color/material_light_green_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_light_green_700</item>\n        <item name=\"colorAccent\">@color/material_light_green_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_light_green_100</item>\n        <item name=\"colorMaterial300\">@color/material_light_green_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Lime\">\n        <item name=\"colorPrimary\">@color/material_lime_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_lime_700</item>\n        <item name=\"colorAccent\">@color/material_lime_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_lime_100</item>\n        <item name=\"colorMaterial300\">@color/material_lime_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Orange\">\n        <item name=\"colorPrimary\">@color/material_orange_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_orange_700</item>\n        <item name=\"colorAccent\">@color/material_orange_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_orange_100</item>\n        <item name=\"colorMaterial300\">@color/material_orange_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Purple\">\n        <item name=\"colorPrimary\">@color/material_purple_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_purple_700</item>\n        <item name=\"colorAccent\">@color/material_purple_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_purple_100</item>\n        <item name=\"colorMaterial300\">@color/material_purple_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Red\">\n        <item name=\"colorPrimary\">@color/material_red_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_red_700</item>\n        <item name=\"colorAccent\">@color/material_red_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_red_100</item>\n        <item name=\"colorMaterial300\">@color/material_red_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Teal\">\n        <item name=\"colorPrimary\">@color/material_teal_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_teal_700</item>\n        <item name=\"colorAccent\">@color/material_teal_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_teal_100</item>\n        <item name=\"colorMaterial300\">@color/material_teal_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Yellow\">\n        <item name=\"colorPrimary\">@color/material_yellow_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_yellow_700</item>\n        <item name=\"colorAccent\">@color/material_yellow_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_yellow_100</item>\n        <item name=\"colorMaterial300\">@color/material_yellow_300</item>\n        <item name=\"colorOnPrimarySurface\">@color/black</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Amber\">\n        <item name=\"colorPrimary\">@color/material_amber_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_amber_700</item>\n        <item name=\"colorAccent\">@color/material_amber_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_amber_100</item>\n        <item name=\"colorMaterial300\">@color/material_amber_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Blue\">\n        <item name=\"colorPrimary\">@color/material_blue_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_blue_700</item>\n        <item name=\"colorAccent\">@color/material_blue_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_blue_100</item>\n        <item name=\"colorMaterial300\">@color/material_blue_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.BlueGrey\">\n        <item name=\"colorPrimary\">@color/material_blue_grey_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_blue_grey_700</item>\n        <item name=\"colorAccent\">@color/material_blue_grey_200</item>\n        <item name=\"colorMaterial100\">@color/material_blue_grey_100</item>\n        <item name=\"colorMaterial300\">@color/material_blue_grey_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Brown\">\n        <item name=\"colorPrimary\">@color/material_brown_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_brown_700</item>\n        <item name=\"colorAccent\">@color/material_brown_200</item>\n        <item name=\"colorMaterial100\">@color/material_brown_100</item>\n        <item name=\"colorMaterial300\">@color/material_brown_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Cyan\">\n        <item name=\"colorPrimary\">@color/material_cyan_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_cyan_700</item>\n        <item name=\"colorAccent\">@color/material_cyan_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_cyan_100</item>\n        <item name=\"colorMaterial300\">@color/material_cyan_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.DeepOrange\">\n        <item name=\"colorPrimary\">@color/material_deep_orange_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_deep_orange_700</item>\n        <item name=\"colorAccent\">@color/material_deep_orange_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_deep_orange_100</item>\n        <item name=\"colorMaterial300\">@color/material_deep_orange_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.DeepPurple\">\n        <item name=\"colorPrimary\">@color/material_deep_purple_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_deep_purple_700</item>\n        <item name=\"colorAccent\">@color/material_deep_purple_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_deep_purple_100</item>\n        <item name=\"colorMaterial300\">@color/material_deep_purple_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Green\">\n        <item name=\"colorPrimary\">@color/material_green_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_green_700</item>\n        <item name=\"colorAccent\">@color/material_green_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_green_100</item>\n        <item name=\"colorMaterial300\">@color/material_green_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Grey\">\n        <item name=\"colorPrimary\">@color/material_grey_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_grey_700</item>\n        <item name=\"colorAccent\">@color/material_grey_200</item>\n        <item name=\"colorMaterial100\">@color/material_grey_100</item>\n        <item name=\"colorMaterial300\">@color/material_grey_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Indigo\">\n        <item name=\"colorPrimary\">@color/material_indigo_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_indigo_700</item>\n        <item name=\"colorAccent\">@color/material_indigo_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_indigo_100</item>\n        <item name=\"colorMaterial300\">@color/material_indigo_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.LightBlue\">\n        <item name=\"colorPrimary\">@color/material_light_blue_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_light_blue_700</item>\n        <item name=\"colorAccent\">@color/material_light_blue_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_light_blue_100</item>\n        <item name=\"colorMaterial300\">@color/material_light_blue_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.LightGreen\">\n        <item name=\"colorPrimary\">@color/material_light_green_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_light_green_700</item>\n        <item name=\"colorAccent\">@color/material_light_green_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_light_green_100</item>\n        <item name=\"colorMaterial300\">@color/material_light_green_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Lime\">\n        <item name=\"colorPrimary\">@color/material_lime_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_lime_700</item>\n        <item name=\"colorAccent\">@color/material_lime_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_lime_100</item>\n        <item name=\"colorMaterial300\">@color/material_lime_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Orange\">\n        <item name=\"colorPrimary\">@color/material_orange_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_orange_700</item>\n        <item name=\"colorAccent\">@color/material_orange_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_orange_100</item>\n        <item name=\"colorMaterial300\">@color/material_orange_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Purple\">\n        <item name=\"colorPrimary\">@color/material_purple_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_purple_700</item>\n        <item name=\"colorAccent\">@color/material_purple_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_purple_100</item>\n        <item name=\"colorMaterial300\">@color/material_purple_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Red\">\n        <item name=\"colorPrimary\">@color/material_red_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_red_700</item>\n        <item name=\"colorAccent\">@color/material_red_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_red_100</item>\n        <item name=\"colorMaterial300\">@color/material_red_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Pink_SSR\">\n        <item name=\"colorPrimary\">@color/color_pink_ssr</item>\n        <item name=\"colorPrimaryDark\">@color/color_pink_ssr</item>\n        <item name=\"colorAccent\">@color/material_pink_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_pink_accent_100</item>\n        <item name=\"colorMaterial300\">@color/color_pink_ssr</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Teal\">\n        <item name=\"colorPrimary\">@color/material_teal_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_teal_700</item>\n        <item name=\"colorAccent\">@color/material_teal_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_teal_100</item>\n        <item name=\"colorMaterial300\">@color/material_teal_300</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Yellow\">\n        <item name=\"colorPrimary\">@color/material_yellow_500</item>\n        <item name=\"colorPrimaryDark\">@color/material_yellow_700</item>\n        <item name=\"colorAccent\">@color/material_yellow_accent_200</item>\n        <item name=\"colorMaterial100\">@color/material_yellow_100</item>\n        <item name=\"colorMaterial300\">@color/material_yellow_300</item>\n        <item name=\"colorOnPrimarySurface\">@color/black</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Black\">\n        <item name=\"colorPrimary\">@color/color_ng_black_primary</item>\n        <item name=\"colorPrimaryDark\">@color/color_ng_black_primary</item>\n        <item name=\"colorAccent\">@color/color_ng_black_accent</item>\n        <item name=\"colorMaterial100\">@color/color_ng_black_primary</item>\n        <item name=\"colorMaterial300\">@color/color_ng_black_primary</item>\n\n        <item name=\"selectedColorPrimary\">@color/color_ng_black_accent</item>\n        <item name=\"fabColorBackground\">@color/color_ng_black_accent</item>\n        <item name=\"itemShapeFillColor\">@color/color_ng_black_accent</item>\n\n        <item name=\"accentOrTextSecondary\">?android:textColorSecondary</item>\n        <item name=\"accentOrTextPrimary\">?android:textColorPrimary</item>\n        <item name=\"primaryOrTextSecondary\">?android:textColorSecondary</item>\n        <item name=\"primaryOrTextPrimary\">?android:textColorPrimary</item>\n    </style>\n\n    <style name=\"Theme.SagerNet.Dialog.Black\">\n        <item name=\"colorPrimary\">@color/color_ng_black_primary</item>\n        <item name=\"colorPrimaryDark\">@color/color_ng_black_primary</item>\n        <item name=\"colorAccent\">@color/color_ng_black_accent</item>\n        <item name=\"colorMaterial100\">@color/color_ng_black_primary</item>\n        <item name=\"colorMaterial300\">@color/color_ng_black_primary</item>\n\n        <item name=\"selectedColorPrimary\">@color/color_ng_black_accent</item>\n        <item name=\"fabColorBackground\">@color/color_ng_black_accent</item>\n        <item name=\"itemShapeFillColor\">@color/color_ng_black_accent</item>\n\n        <item name=\"accentOrTextSecondary\">?android:textColorSecondary</item>\n        <item name=\"accentOrTextPrimary\">?android:textColorPrimary</item>\n        <item name=\"primaryOrTextSecondary\">?android:textColorSecondary</item>\n        <item name=\"primaryOrTextPrimary\">?android:textColorPrimary</item>\n    </style>\n\n    <style name=\"Theme.Start\" parent=\"Theme.MaterialComponents.DayNight\">\n        <item name=\"android:navigationBarColor\">@android:color/transparent</item>\n        <item name=\"colorPrimary\">@android:color/transparent</item>\n        <item name=\"colorPrimaryDark\">@android:color/transparent</item>\n        <item name=\"android:backgroundDimEnabled\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ar/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"menu_group\">مجموعة</string>\n    <string name=\"menu_configuration\">ترتيب</string>\n    <string name=\"logcat\">تصدير معلومات التصحيح</string>\n    <string name=\"version_x\">الإصدار (%s)</string>\n    <string name=\"app_version\">إصدار</string>\n    <string name=\"oss_licenses\">تراخيص مفتوحة المصدر</string>\n    <string name=\"telegram\">قناة تحديث Telegram</string>\n    <string name=\"github\">مصدر الرمز</string>\n    <string name=\"app_desc\">سلسلة أدوات البروکسی العالمية لنظام Android ، مكتوبة بلغة Kotlin.</string>\n    <string name=\"project\">مشروع</string>\n    <string name=\"group_status_proxies\">%d البروکسیز</string>\n    <string name=\"group_status_empty_subscription\">لم يتم تحديثه بعد</string>\n    <string name=\"group_status_empty\">مجموعة فارغة</string>\n    <string name=\"group_edit_subscription\">تحرير الاشتراك‬</string>\n    <string name=\"group_edit\">تحرير المجموعة</string>\n    <string name=\"group_create_subscription\">إنشاء اشتراك</string>\n    <string name=\"group_create\">إنشاء مجموعة</string>\n    <string name=\"group_name_required\">اسم المجموعة مطلوب</string>\n    <string name=\"group_subscription_link\">رابط الاشتراك</string>\n    <string name=\"group_update\">تحديث</string>\n    <string name=\"group_name\">أسم المجموعة</string>\n    <string name=\"tile_title\">الجلاد</string>\n    <string name=\"quick_toggle\">تبديل</string>\n    <string name=\"quick_enable\">تمكين</string>\n    <string name=\"quick_disable\">تعطيل</string>\n    <string name=\"group_default\">غير مجمعة</string>\n    <string name=\"document\">وثيقة</string>\n    <string name=\"theme\">موضوع</string>\n    <string name=\"menu_about\">حول</string>\n    <string name=\"group_status_proxies_subscription\">%d البروکسیز | تم التحديث في %s</string>\n    <string name=\"group_updated\">%s: تم تحديث %d البروکسیز</string>\n    <string name=\"port_http\">منفذ بروکسی HTTP</string>\n    <string name=\"port_proxy\">منفذ بروکسی SOCKS5</string>\n    <string name=\"service_mode_proxy\">البروکسی فقط</string>\n    <string name=\"port_transproxy\">منفذ ترانسبروكسى</string>\n    <string name=\"service_mode\">وضع الخدمة</string>\n    <string name=\"group_duplicate\">ينسخ:\n\\n%s</string>\n    <string name=\"group_deleted\">تم الحذف:\n\\n%s</string>\n    <string name=\"group_changed\">محدث:\n\\n%s</string>\n    <string name=\"group_added\">تمت الإضافة:\n\\n%s</string>\n    <string name=\"group_delete_confirm_prompt\">هل أنت متأكد أنك تريد إزالة هذه المجموعة؟</string>\n    <string name=\"group_diff\">الفارق (%s)</string>\n    <string name=\"group_show_diff\">الفارق</string>\n    <string name=\"group_no_difference\">%s: لا فرق</string>\n    <string name=\"grpc_service_name\">اسم خدمة gRPC</string>\n    <string name=\"http_path\">مسار HTTP</string>\n    <string name=\"http_host\">مضيف HTTP</string>\n    <string name=\"ws_path\">مسار WebSocket</string>\n    <string name=\"ws_host\">مضيف WebSocket</string>\n    <string name=\"header_type\">نوع الرأس</string>\n    <string name=\"network\">شبكة</string>\n    <string name=\"security\">تشفير طبقة النقل</string>\n    <string name=\"alter_id\">تغيير رقم التعريف</string>\n    <string name=\"uuid\">معرف المستخدم</string>\n    <string name=\"obfs_param\">Obfs بارام</string>\n    <string name=\"obfs\">Obfs(أو بی اِف اِس)</string>\n    <string name=\"protocol_param\">بروتوكول بارام</string>\n    <string name=\"protocol\">البروتوكول</string>\n    <string name=\"enc_method\">طريقة التشفير</string>\n    <string name=\"password\">كلمة المرور</string>\n    <string name=\"key_opt\">مفتاح (اختياري)</string>\n    <string name=\"password_opt\">كلمة المرور (اختياري)</string>\n    <string name=\"username_opt\">اسم مستخدِم (اختياري)</string>\n    <string name=\"username\">اسم مستخدِم</string>\n    <string name=\"server_port\">منفذ بعيد</string>\n    <string name=\"server_address\">الخادم</string>\n    <string name=\"profile_name\">اسم ملف التعريف</string>\n    <string name=\"connection_test_url\">اختبار الاتصال URL</string>\n    <string name=\"transproxy_mode\">وضع ترانسبروكسى</string>\n    <string name=\"require_transproxy\">تفعيل ترانسبروكسی الوارد</string>\n    <string name=\"port_local_dns\">منفذ DNS المحلي</string>\n    <string name=\"dns_hosts\">إعادة كتابة المجال</string>\n    <string name=\"fakedns_message\">قد يتسبب في الحاجة إلى إعادة تشغيل التطبيقات الأخرى لإعادة الاتصال بالشبكة بعد توقف البروکسی</string>\n    <string name=\"enable_fakedns\">تفعيل DNS وهمي</string>\n    <string name=\"dns_routing_message\">حل المجالات في مسارات الالتفاف باستخدام Direct DNS. كن على علم بتسريبات DNS المحتملة</string>\n    <string name=\"enable_dns_routing\">تمكين توجيه DNS</string>\n    <string name=\"direct_dns\">DNS المباشر</string>\n    <string name=\"remote_dns\">DNS البعيد</string>\n    <string name=\"cag_dns\">إعدادات DNS</string>\n    <string name=\"connection_test_error_status_code\">رمز الخطأ: #%d</string>\n    <string name=\"connection_test_fail\">الإنترنت غير متوفر</string>\n    <string name=\"connection_test_error\">فشل: %s</string>\n    <string name=\"connection_test_available_http\">نجاح: استغرقت مصافحة HTTP %dms</string>\n    <string name=\"connection_test_available\">نجاح: استغرقت مصافحة HTTPS %dms</string>\n    <string name=\"connection_test_testing\">اختبارات…</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"speed_detail\">بروکسی:\n\\n\\t%1$s↑ %2$s↓\n\\nمباشر:\n\\n\\t%3$s↑ %4$s↓</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"allow_insecure_sum\">تعطيل فحص الشهادة. عند التمكين ، يكون هذا التكوين آمنًا مثل النص العادي</string>\n    <string name=\"allow_insecure\">السماح بعدم الأمان</string>\n    <string name=\"security_settings\">اعدادات الامان</string>\n    <string name=\"show_direct_speed_sum\">أظهر سرعة المرور بدون وكيل في الإشعار أيضًا</string>\n    <string name=\"show_direct_speed\">عرض السرعة المباشرة</string>\n    <string name=\"show_stop_sum\">إذا كنت لا تريد استخدام \\\"اللوحة السريعة\\\" كمفتاح التبديل</string>\n    <string name=\"allow_access_sum\">ربط خوادم الإدخال إلى 0.0.0.0</string>\n    <string name=\"show_stop\">إظهار زر الإيقاف</string>\n    <string name=\"cag_misc\">إعدادات متنوعة</string>\n    <string name=\"speed_interval\">الفاصل الزمني لتحديث إشعار السرعة</string>\n    <string name=\"ws_browser_forwarding_sum\">إعادة توجيه WebSockets المقابلة من خلال المتصفح.</string>\n    <string name=\"ws_browser_forwarding\">إعادة توجيه المتصفح</string>\n    <string name=\"cag_ws\">إعدادات WebSocket</string>\n    <string name=\"require_http\">تفعيل HTTP الوارد</string>\n    <string name=\"general_settings\">إعدادات التطبيقات</string>\n    <string name=\"inbound_settings\">إعدادات الواردة</string>\n    <string name=\"allow_access\">السماح بالاتصالات من الشبكة المحلية</string>\n    <string name=\"cag_route\">إعدادات الطريق</string>\n    <string name=\"route_asset_updated\">محدث</string>\n    <string name=\"route_asset_no_update\">لا تحديث</string>\n    <string name=\"route_asset_status\">الإصدار المحلي: %s</string>\n    <string name=\"route_rules_official\">الرسمية</string>\n    <string name=\"route_rules_provider\">مزود أصول القاعدة</string>\n    <string name=\"route_assets\">الأصول</string>\n    <string name=\"route_manage_assets\">إدارة أصول الطريق</string>\n    <string name=\"route_reset\">إعادة تعيين</string>\n    <string name=\"route_bypass_ip\">قاعدة IP ل %s</string>\n    <string name=\"route_bypass_domain\">قاعدة المجال ل %s</string>\n    <string name=\"empty_route_notice\">ضع بعض القواعد قبل الحفظ</string>\n    <string name=\"empty_route\">طريق فارغ</string>\n    <string name=\"route_profile\">حدد ملف التعريف…</string>\n    <string name=\"route_block\">منع</string>\n    <string name=\"route_bypass\">ضع جانبا</string>\n    <string name=\"route_proxy\">بروکسی</string>\n    <string name=\"route_name\">اسم الطريق</string>\n    <string name=\"delete_route_prompt\">هل أنت متأكد أنك تريد إزالة هذا المسار؟</string>\n    <string name=\"route_add\">إنشاء الطريق</string>\n    <string name=\"menu_route\">طريق</string>\n    <string name=\"metered_summary\">نظام تلميح لمعاملة VPN على أنها محسوبة</string>\n    <string name=\"metered\">تلميح مقنن</string>\n    <string name=\"only\">فقط</string>\n    <string name=\"prefer\">يفضل</string>\n    <string name=\"ipv6\">مسار IPv6</string>\n    <string name=\"edit_config\">تحرير التكوين</string>\n    <string name=\"config_type\">نوع التكوين</string>\n    <string name=\"extra_headers\">رؤوس إضافية</string>\n    <string name=\"encryption\">التشفير</string>\n    <string name=\"early_data_header_name\">اسم رأس البيانات المبكر</string>\n    <string name=\"certificates\">الشهادات</string>\n    <string name=\"alpn\">تفاوض بروتوكول طبقة التطبيق</string>\n    <string name=\"sni\">بيان اسم الخادم</string>\n    <string name=\"tls\">استخدم TLS</string>\n    <string name=\"add_profile_methods_scan_qr_code\">مسح رمز كيو آر</string>\n    <string name=\"share_qr_nfc\">رمز كيو آر</string>\n    <string name=\"delete_confirm_prompt\">هل أنت متأكد أنك تريد إزالة هذا ملف التعريف؟</string>\n    <string name=\"delete\">إزالة</string>\n    <string name=\"profile_config\">تكوين ملف التعريف</string>\n    <string name=\"file_manager_missing\">جهازك يفتقر إلى محدد ملفات أندرويد القياسي، يرجى تثبيت واحد، مثل Material Files.</string>\n    <string name=\"action_import_err\">فشل الاستيراد.</string>\n    <string name=\"action_import_msg\">تم الاستيراد بنجاح!</string>\n    <string name=\"action_export_err\">فشل التصدير.</string>\n    <string name=\"action_export_msg\">تم التصدير بنجاح!</string>\n    <string name=\"action_import_file\">استيراد من ملف</string>\n    <string name=\"action_import\">الاستيراد من الحافظة</string>\n    <string name=\"action_export\">يصدر</string>\n    <string name=\"action_export_clipboard\">تصدير إلى الحافظة</string>\n    <string name=\"action_export_file\">تصدير إلى ملف</string>\n    <string name=\"action_scan_china_apps\">فحص تطبيقات الصين</string>\n    <string name=\"action_from_link\">من الاشتراك</string>\n    <string name=\"action_create_group\">مجموعة فارغة</string>\n    <string name=\"clear_profiles\">صافي</string>\n    <string name=\"circular_reference_sum\">لا يمكن أن يحتوي المسار على نفسه.</string>\n    <string name=\"circular_reference\">مرجع دائري</string>\n    <string name=\"config_settings\">إعدادات التكوين</string>\n    <string name=\"chain_settings\">إعدادات السلسلة</string>\n    <string name=\"balancer_strategy\">إستراتيجية</string>\n    <string name=\"balancer_type\">اكتب</string>\n    <string name=\"balancer_settings\">إعدادات الموازن</string>\n    <string name=\"balancer\">موازن</string>\n    <string name=\"custom_config\">التكوين المخصص</string>\n    <string name=\"proxy_chain\">سلسلة البروکسی</string>\n    <string name=\"select_profile\">حدد ملف التعريف</string>\n    <string name=\"add_profile\">إضافة ملف تعريف</string>\n    <string name=\"share\">مشاركة</string>\n    <string name=\"edit\">تعديل</string>\n    <string name=\"settings\">إعدادات</string>\n    <string name=\"clipboard_empty\">الحافظة فارغة</string>\n    <string name=\"connect\">الاتصال</string>\n    <string name=\"profile_empty\">الرجاء تحديد ملف تعريف</string>\n    <string name=\"reboot_required\">فشل بدء تشغيل خدمة VPN. قد تحتاج إلى إعادة تشغيل جهازك.</string>\n    <string name=\"vpn_permission_denied\">تم رفض الإذن لإنشاء خدمة VPN</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"stopping\">جارٍ إيقاف التشغيل…</string>\n    <string name=\"stop\">قف</string>\n    <string name=\"service_failed\">باءت بالفشل:</string>\n    <string name=\"invalid_server\">اسم الخادم غير صالح</string>\n    <string name=\"forward_success\">بدأت البروکسی.</string>\n    <string name=\"service_proxy\">خدمة البروکسی</string>\n    <string name=\"service_vpn\">خدمة VPN</string>\n    <string name=\"direct_boot_aware_summary\">ستكون معلومات ملف التعريف المحدد أقل حماية</string>\n    <string name=\"direct_boot_aware\">السماح بالتبديل في شاشة القفل</string>\n    <string name=\"auto_connect_summary\">قم بتمكين البروکسی عند تحديث بدء التشغيل/التطبيق إذا كان يعمل من قبل</string>\n    <string name=\"auto_connect\">اتصال تلقائي</string>\n    <string name=\"show_system_apps\">عرض تطبيقات النظام</string>\n    <string name=\"bypass_apps\">ضع جانبا</string>\n    <string name=\"clear_selections\">تحديدات واضحة</string>\n    <string name=\"invert_selections\">عكس التحديدات</string>\n    <string name=\"scanning\">يتم المسح…</string>\n    <string name=\"search_apps\">بحث…</string>\n    <string name=\"off\">عن</string>\n    <string name=\"on\">على</string>\n    <string name=\"proxied_apps_summary\">تكوين وضع VPN للتطبيقات المحددة</string>\n    <string name=\"proxied_apps\">وضع تطبيقات VPN</string>\n    <string name=\"tcp_keep_alive_interval\">يحتفظ TCP بالفاصل الزمني النشط لتسليم الحزم</string>\n    <string name=\"mux_sum\">تم تصميم Mux لتقليل زمن انتقال مصافحة TCP، وليس لزيادة إنتاجية الاتصال. عادةً ما يؤدي استخدام Mux لمشاهدة مقاطع الفيديو أو التنزيل أو اختبار السرعة إلى نتائج عكسية</string>\n    <string name=\"mux_concurrency\">اتصالات مسك المتزامنة</string>\n    <string name=\"enable_mux\">تمكين المضاعف</string>\n    <string name=\"traffic_sniffing\">تمكين استنشاق حركة المرور</string>\n    <string name=\"domain_strategy\">استراتيجية حل المجال</string>\n    <string name=\"route_opt_block_ads\">إعلانات كتلة</string>\n    <string name=\"route_opt_bypass_lan\">جانبا LAN</string>\n    <string name=\"route_not_asset\">ليس ملف أصل: استثناء .db، لكن %s</string>\n    <string name=\"hysteria_download_mbps\">أقصى سرعة تنزيل (بالميجابت في الثانية)</string>\n    <string name=\"hysteria_upload_mbps\">أقصى سرعة تحميل (بالميجابت في الثانية)</string>\n    <string name=\"hysteria_auth_payload\">حمولة المصادقة</string>\n    <string name=\"hysteria_auth_type\">نوع المصادقة</string>\n    <string name=\"hysteria_obfs\">كلمة مرور التشويش</string>\n    <string name=\"apps_message\">%d تطبيقات</string>\n    <string name=\"select_apps\">حدد التطبيقات</string>\n    <string name=\"apps\">التطبيقات</string>\n    <string name=\"clear_profiles_message\">هل أنت متأكد أنك تريد مسح هذه المجموعة؟</string>\n    <string name=\"profile_import_message\">هل تريد تأكيد رغبتك في استيراد ملف التعريف %s؟</string>\n    <string name=\"profile_import\">استيراد ملف التعريف</string>\n    <string name=\"subscription_import_message\">هل تريد تأكيد رغبتك في استيراد اشتراك %s؟ إذا كنت قادمًا من مصدر غير موثوق به، فقد يؤدي القيام بذلك إلى تسريب عنوان IP الخاص بك وهذا السلوك.</string>\n    <string name=\"subscription_import\">اشتراك الاستيراد</string>\n    <string name=\"subscription_traffic\">%s مستخدمة / %s متبقية</string>\n    <string name=\"subscription_used\">%s مستخدمة</string>\n    <string name=\"group_filter\">التكرير</string>\n    <string name=\"subscription_update_message\">جاري تحديث %s …</string>\n    <string name=\"subscription_update\">تحديث الاشتراك</string>\n    <string name=\"service_subscription\">خدمة تحديث الاشتراك</string>\n    <string name=\"ooc_missing_protocol\">يتطلب الاشتراك دعم البروتوكول %s، ولكن لا يمكن العثور عليه. سيتم تجاهل الملفات الشخصية غير المدعومة.</string>\n    <string name=\"ooc_warning\">تحذير</string>\n    <string name=\"update_subscription_warning\">البروکسی غير متصل، هل أنت متأكد أنك تريد متابعة التحديث؟</string>\n    <string name=\"ooc_subscription_token_invalid\">رمز OOCv1 غير صالح</string>\n    <string name=\"download\">تحميل</string>\n    <string name=\"install_from_fdroid\">التثبيت من F-Droid</string>\n    <string name=\"install_from_play_store\">التثبيت من متجر Play</string>\n    <string name=\"action_download\">تحميل</string>\n    <string name=\"action_learn_more\">يتعلم أكثر</string>\n    <string name=\"profile_requiring_plugin\">يتطلب ملف التعريف %s تثبيت المكون الإضافي %s، لكن لم يتم العثور عليه.</string>\n    <string name=\"missing_plugin\">في عداد المفقودين المكونات في</string>\n    <string name=\"confirm\">أكد</string>\n    <string name=\"subscription_user_agent\">وكيل المستخدم</string>\n    <string name=\"update_when_connected_only_sum\">منع تسرب عنوان IP</string>\n    <string name=\"update_when_connected_only\">التحديث فقط عند الاتصال</string>\n    <string name=\"auto_update_delay\">تأخير التحديث التلقائي (بالدقائق)</string>\n    <string name=\"auto_update\">التحديث التلقائي</string>\n    <string name=\"update_settings\">تحديث الاعدادات</string>\n    <string name=\"raw\">الخام</string>\n    <string name=\"deduplication_sum\">قم بإزالة التكوينات المكررة عند التحديث</string>\n    <string name=\"force_resolve_sum\">حل جميع أسماء المجالات إلى عناوين IP عند التحديث. سيتم إلحاق المضيف و SNI تلقائيًا إن أمكن</string>\n    <string name=\"force_resolve\">الحل القسري</string>\n    <string name=\"delete_group_prompt\">هل أنت متأكد أنك تريد إزالة هذه المجموعة؟</string>\n    <string name=\"subscription_type\">نوع الاشتراك</string>\n    <string name=\"group_type\">نوع المجموعة</string>\n    <string name=\"subscription_settings\">إعدادات الاشتراك</string>\n    <string name=\"subscription\">الاشتراك</string>\n    <string name=\"group_settings\">إعدادات المجموعة</string>\n    <string name=\"group_basic\">أساسي</string>\n    <string name=\"trojan_provider\">Trojan مزود</string>\n    <string name=\"protocol_settings\">إعدادات البروتوكول</string>\n    <string name=\"append_http_proxy_sum\">سيتم استخدام بروکسی HTTP مباشرة من (المتصفح/ بعض التطبيقات المدعومة)، دون المرور عبر جهاز NIC الافتراضي (Android 10+)</string>\n    <string name=\"append_http_proxy\">إلحاق بروکسی HTTP ب VPN</string>\n    <string name=\"connection_test_timeout\">نفذ الوقت</string>\n    <string name=\"connection_test_unreachable\">لا يمكن الوصول إليه</string>\n    <string name=\"connection_test_refused\">الاتصال مرفوض</string>\n    <string name=\"connection_test_domain_not_found\">المجال لم يتم العثور</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing غير متوفر</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing غير متوفر</string>\n    <string name=\"connection_test_clear_results\">المقاصة نتائج الاختبار</string>\n    <string name=\"connection_test\">اختبار الاتصال</string>\n    <string name=\"clear_traffic_statistics\">مسح إحصائيات حركة المرور</string>\n    <string name=\"always_show_address_sum\">اعرض دائمًا عنوان الخادم على بطاقة التكوين</string>\n    <string name=\"always_show_address\">اعرض العنوان دائمًا</string>\n    <string name=\"unavailable\">غير متوفره</string>\n    <string name=\"standard\">معيار</string>\n    <string name=\"probe_interval\">الفاصل الزمني لرصد الموازن</string>\n    <string name=\"api_port\">منفذ API</string>\n    <string name=\"random\">عشوائي</string>\n    <string name=\"list\">قائمة</string>\n    <string name=\"enable_log_sum\">من أجل إصلاح الخلل</string>\n    <string name=\"enable_log\">تمكين التقارير</string>\n    <string name=\"auto\">تلقائي</string>\n    <string name=\"disable\">إلغاء</string>\n    <string name=\"enable\">مكن</string>\n    <string name=\"follow_system\">اتبع النظام</string>\n    <string name=\"night_mode\">وضع الليل</string>\n    <string name=\"lines\">%d سطور</string>\n    <string name=\"route_warn\">تأكد من قراءة الوثائق قبل إضافة القواعد المخصصة، وإلا فقد لا تتمكن من الاتصال بالإنترنت.</string>\n    <string name=\"license\">الرخصة</string>\n    <string name=\"need_reload\">أعد تحميل خدمة البروکسی لتطبيق التغييرات</string>\n    <string name=\"apply\">تطبيق</string>\n    <string name=\"no\">لا</string>\n    <string name=\"yes\">نعم</string>\n    <string name=\"unsaved_changes_prompt\">لم يتم حفظ التغييرات. هل تريد حفظ؟</string>\n    <string name=\"ss_cat\">إعدادات Shadowsocks</string>\n    <string name=\"proxy_cat\">اعدادات الخادم</string>\n    <string name=\"plugin_auto_connect_unlock_only\">قد لا يعمل هذا المكون الإضافي مع الاتصال التلقائي</string>\n    <string name=\"plugin_untrusted\">تحذير: يبدو أن هذا المكون الإضافي لا يأتي من مصدر موثوق ومعروف.</string>\n    <string name=\"plugin_unknown\">مكون إضافي غير معروف %s</string>\n    <string name=\"plugin_disabled\">غير مفعّل</string>\n    <string name=\"plugin_configure\">تهيئة…</string>\n    <string name=\"plugin\">الإضافات</string>\n    <string name=\"ignore_battery_optimizations_sum\">قم بإزالة بعض القيود</string>\n    <string name=\"ignore_battery_optimizations\">تجاهل تحسينات البطارية</string>\n    <string name=\"donate_info\">انا احب المال</string>\n    <string name=\"donate\">تبرع</string>\n    <string name=\"deduplication\">احذف البيانات المكررة</string>\n    <string name=\"no_proxies_found_in_clipboard\">لم يتم العثور على بروکسیز في الحافظة</string>\n    <string name=\"no_proxies_found_in_file\">لم يتم العثور على بروکسیز في الملف</string>\n    <string name=\"no_proxies_found_in_subscription\">لم يتم العثور على بروکسیز في الاشتراك</string>\n    <string name=\"no_proxies_found\">لم يتم العثور على بروکسیز في الارتباط</string>\n    <string name=\"error_title\">خطأ</string>\n    <string name=\"cleartext_http_warning\">حركة مرور نص واضح HTTP غير آمنة</string>\n    <string name=\"subscriptions\">الاشتراكات</string>\n    <string name=\"not_connected\">غير متصل</string>\n    <string name=\"vpn_connected\">متصل، انقر للتحقق من الاتصال</string>\n    <string name=\"connecting\">يتصل…</string>\n    <string name=\"deprecated\">مهجور</string>\n    <string name=\"insecure\">غير آمن</string>\n    <string name=\"undo\">تراجع</string>\n    <plurals name=\"added\">\n        <item quantity=\"zero\">مضاف</item>\n        <item quantity=\"one\">%d تمت إضافة البروکسیز</item>\n        <item quantity=\"two\"/>\n        <item quantity=\"few\"/>\n        <item quantity=\"many\"/>\n        <item quantity=\"other\"/>\n    </plurals>\n    <plurals name=\"removed\">\n        <item quantity=\"zero\">محذوف</item>\n        <item quantity=\"one\">%d العناصر التي تمت إزالتها</item>\n        <item quantity=\"two\"/>\n        <item quantity=\"few\"/>\n        <item quantity=\"many\"/>\n        <item quantity=\"other\"/>\n    </plurals>\n    <string name=\"add_profile_methods_manual_settings\">الإعدادات اليدوية</string>\n    <string name=\"experimental_settings\">تجريبي</string>\n    <string name=\"route_need_vpn\">تعتمد قاعدة التوجيه %s على أن تكون VPN سارية المفعول، لذلك يتم تجاهلها.</string>\n    <string name=\"route_for\">حكم ل %s</string>\n    <string name=\"app_no_launcher\">التطبيق ليس له واجهة.</string>\n    <string name=\"copy_failed\">فشل النسخ.</string>\n    <string name=\"copy_success\">تم النسخ بنجاح!</string>\n    <string name=\"create_rule\">إنشاء قاعدة</string>\n    <string name=\"open_market\">السوق المفتوح</string>\n    <string name=\"open_settings\">أفتح الإعدادات</string>\n    <string name=\"open_app\">افتح التطبيق</string>\n    <string name=\"copy_package_name\">نسخ اسم الحزمة</string>\n    <string name=\"copy_label\">نسخ الاسم</string>\n    <string name=\"udp_connections\">%d اتصالات UDP</string>\n    <string name=\"tcp_connections\">%d اتصالات TCP</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (مكون أندرويد الإضافي Shadowsocks)</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (مكون أندرويد الإضافي Shadowsocks)</string>\n    <string name=\"plugin_exists_but_on_shit_system\">يتطلب الملف التعریف %s المكوّن الإضافي %s، لكن بائع المعدات المملوكة لك (عادةً عمالقة المراقبة وصانع البرامج الضارة) تلاعب بجهاز أندرويد، مما يجعل المكون الإضافي غير قابل للاستخدام.</string>\n    <string name=\"group_order_by_delay\">عن طريق التأخير</string>\n    <string name=\"group_order_by_name\">بالاسم</string>\n    <string name=\"group_order_origin\">أصل</string>\n    <string name=\"group_order\">ترتيب</string>\n    <string name=\"hysteria_disable_mtu_discovery\">تعطيل اكتشاف MTU للمسار</string>\n    <string name=\"hysteria_connection_receive_window\">نافذة تلقي اتصال QUIC</string>\n    <string name=\"hysteria_stream_receive_window\">نافذة تلقي دفق QUIC</string>\n    <string name=\"menu_traffic\">مرور</string>\n    <string name=\"no_statistics\">لا توجد إحصاءات حتى الآن</string>\n    <string name=\"clear_logcat\">تقارير أداء واضحة</string>\n    <string name=\"menu_log\">السجلات</string>\n    <string name=\"menu_tools\">أدوات</string>\n    <string name=\"translate_platform\">منصة الترجمة</string>\n    <string name=\"ssh_private_key_passphrase\">عبارة مرور المفتاح الخاص</string>\n    <string name=\"ssh_private_key\">مفتاح خاص</string>\n    <string name=\"ssh_public_key\">المفتاح العمومي</string>\n    <string name=\"ssh_auth_type_none\">لا شيء</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-be/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"logcat\">Экспартаваць інфармацыю пра адладку</string>\n    <string name=\"version_x\">Версія (%s)</string>\n    <string name=\"app_version\">Версія</string>\n    <string name=\"oss_licenses\">Ліцэнзіі з адкрытым зыходным кодам</string>\n    <string name=\"telegram\">Канал абнаўлення тэлеграм</string>\n    <string name=\"github\">Зыходны код</string>\n    <string name=\"project\">Праект</string>\n    <string name=\"app_desc\">Універсальны набор інструментаў проксі для Android, напісаны на Kotlin.</string>\n    <string name=\"ws_path\">Шлях WebSocket</string>\n    <string name=\"ws_host\">Хост WebSocket</string>\n    <string name=\"header_type\">Тып загалоўка</string>\n    <string name=\"network\">Сетка</string>\n    <string name=\"security\">Шыфраванне транспартнага ўзроўню</string>\n    <string name=\"alter_id\">Змяніць ID</string>\n    <string name=\"uuid\">Ідентифікатор карыстальніка</string>\n    <string name=\"obfs_param\">Obfs Парам</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"protocol_param\">Пратакол Param</string>\n    <string name=\"protocol\">Пратакол</string>\n    <string name=\"enc_method\">Метад шыфравання</string>\n    <string name=\"password\">Пароль</string>\n    <string name=\"key_opt\">Ключ (неабавязкова)</string>\n    <string name=\"password_opt\">Пароль (неабавязкова)</string>\n    <string name=\"username_opt\">Імя карыстальніка (неабавязкова)</string>\n    <string name=\"username\">Імя карыстальніка</string>\n    <string name=\"server_port\">Аддалены порт</string>\n    <string name=\"server_address\">Сервер</string>\n    <string name=\"profile_name\">Імя профілю</string>\n    <string name=\"connection_test_url\">Тэст URL падлучэння</string>\n    <string name=\"transproxy_mode\">Рэжым Transproxy</string>\n    <string name=\"require_transproxy\">Уключыць Transproxy Уваходныя</string>\n    <string name=\"inbound_settings\">Уключыць Transproxy Уваходныя</string>\n    <string name=\"port_local_dns\">Лакальны порт DNS</string>\n    <string name=\"dns_hosts\">Перапісаць дамен</string>\n    <string name=\"fakedns_message\">Можа выклікаць перазапуск іншых прыкладанняў для паўторнага падлучэння да сеткі пасля спынення проксі</string>\n    <string name=\"enable_fakedns\">Уключыць FakeDNS</string>\n    <string name=\"dns_routing_message\">Выкарыстоўвайце прамы DNS для маршрутаў па -за проксі. У гэтым выпадку верагодна ўцечка DNS</string>\n    <string name=\"enable_dns_routing\">Уключыць маршрутызацыю DNS</string>\n    <string name=\"direct_dns\">Прамы DNS</string>\n    <string name=\"remote_dns\">Аддалены DNS</string>\n    <string name=\"cag_dns\">Налады DNS</string>\n    <string name=\"connection_test_error_status_code\">Код памылкі: #%d</string>\n    <string name=\"connection_test_fail\">Інтэрнэт недаступны</string>\n    <string name=\"connection_test_error\">Не атрымалася: %s</string>\n    <string name=\"connection_test_available_http\">Поспех: поціск рукі HTTP заняў %dms</string>\n    <string name=\"connection_test_available\">Поспех: поціск рукі HTTPS заняў %dms</string>\n    <string name=\"connection_test_testing\">Тэставанне…</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"speed_detail\">Проксі : %1$s↑ %2$s↓\n\\nПрамая : %3$s↑ %4$s↓</string>\n    <string name=\"allow_insecure_sum\">Адключыць праверку сертыфікатаў. Калі ўключана, гэтая канфігурацыя такая ж бяспечная, як і адкрыты тэкст</string>\n    <string name=\"allow_insecure\">Дазволіць небяспечна</string>\n    <string name=\"security_settings\">Налады бяспекі</string>\n    <string name=\"show_direct_speed_sum\">У апавяшчэнні таксама паказвайце хуткасць руху без проксі</string>\n    <string name=\"show_direct_speed\">Паказаць прамую хуткасць</string>\n    <string name=\"show_stop_sum\">Калі вы не хочаце выкарыстоўваць кнопку панэлі апавяшчэнняў у якасці пераключальніка злучэння</string>\n    <string name=\"show_stop\">Паказаць кнопку прыпынку</string>\n    <string name=\"cag_misc\">Розныя налады</string>\n    <string name=\"speed_interval\">Інтэрвал абнаўлення апавяшчэнняў аб хуткасці</string>\n    <string name=\"ws_browser_forwarding_sum\">Перадайце адпаведныя WebSockets праз браўзэр.</string>\n    <string name=\"ws_browser_forwarding\">Пераадрасацыя браўзэра</string>\n    <string name=\"cag_ws\">Налады WebSocket</string>\n    <string name=\"require_http\">Уключыць увод HTTP</string>\n    <string name=\"general_settings\">Налады прыкладання</string>\n    <string name=\"allow_access_sum\">Прывязаць уваходныя серверы да 0.0.0.0</string>\n    <string name=\"allow_access\">Дазволіць злучэнне з лакальнай сеткі</string>\n    <string name=\"cag_route\">Налады маршруту</string>\n    <string name=\"port_transproxy\">Transproxy порт</string>\n    <string name=\"port_http\">Проксі -порт HTTP</string>\n    <string name=\"port_proxy\">Порт проксі -сервера SOCKS5</string>\n    <string name=\"service_mode_proxy\">Толькі проксі</string>\n    <string name=\"service_mode\">Рэжым абслугоўвання</string>\n    <string name=\"group_duplicate\">Дублікат:\n\\n%s</string>\n    <string name=\"group_deleted\">Выдалена:\n\\n%s</string>\n    <string name=\"group_changed\">Абноўлена:\n\\n%s</string>\n    <string name=\"group_added\">Дададзена:\n\\n%s</string>\n    <string name=\"group_delete_confirm_prompt\">Вы ўпэўненыя, што хочаце выдаліць гэтую групу\\?</string>\n    <string name=\"group_diff\">Розніца (%s)</string>\n    <string name=\"group_show_diff\">Розніца</string>\n    <string name=\"group_updated\">%s: Абноўлены %d проксі</string>\n    <string name=\"group_no_difference\">%s: няма розніцы</string>\n    <string name=\"group_status_proxies_subscription\">%d проксі | Абноўлена %s</string>\n    <string name=\"group_status_proxies\">%d проксі</string>\n    <string name=\"group_status_empty_subscription\">Пакуль не абноўлена</string>\n    <string name=\"group_status_empty\">Пустая група</string>\n    <string name=\"group_edit_subscription\">Рэдагаваць падпіску</string>\n    <string name=\"group_edit\">Рэдагаваць групу</string>\n    <string name=\"group_create_subscription\">Стварыць падпіску</string>\n    <string name=\"group_create\">Стварыць групу</string>\n    <string name=\"group_name_required\">Патрабуецца назва групы</string>\n    <string name=\"group_subscription_link\">Спасылка на падпіску</string>\n    <string name=\"group_update\">Абнаўленне</string>\n    <string name=\"group_name\">Назва групы</string>\n    <string name=\"menu_traffic\">Трафік</string>\n    <string name=\"tile_title\">Перамыкач</string>\n    <string name=\"quick_toggle\">Пераключыць</string>\n    <string name=\"quick_enable\">Уключыць</string>\n    <string name=\"quick_disable\">Выключыць</string>\n    <string name=\"group_default\">Разгрупаваны</string>\n    <string name=\"document\">Дакумент</string>\n    <string name=\"theme\">Тэма</string>\n    <string name=\"menu_about\">Пра</string>\n    <string name=\"menu_group\">Група</string>\n    <string name=\"menu_configuration\">Канфігурацыя</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-de/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"group_subscription_link\">Abonnement-Link</string>\n    <string name=\"group_update\">Aktualisieren</string>\n    <string name=\"group_name\">Gruppen name</string>\n    <string name=\"tile_title\">Umschalter</string>\n    <string name=\"quick_toggle\">Umschalten</string>\n    <string name=\"quick_enable\">Aktivieren</string>\n    <string name=\"quick_disable\">Deaktivieren</string>\n    <string name=\"group_default\">Ungruppiert</string>\n    <string name=\"document\">Dokument</string>\n    <string name=\"theme\">Style</string>\n    <string name=\"menu_about\">Über</string>\n    <string name=\"menu_group\">Gruppe</string>\n    <string name=\"menu_configuration\">Konfiguration</string>\n    <string name=\"app_version\">Version</string>\n    <string name=\"logcat\">Debug-Informationen exportieren</string>\n    <string name=\"version_x\">Version (%s)</string>\n    <string name=\"oss_licenses\">Quelloffene Lizenzen</string>\n    <string name=\"telegram\">Telegramm-Update-Kanal</string>\n    <string name=\"github\">Quellcode</string>\n    <string name=\"project\">Projekt</string>\n    <string name=\"app_desc\">Die universelle Proxy-Toolchain für Android, geschrieben in Kotlin.</string>\n    <string name=\"group_create\">Erstellen Gruppe</string>\n    <string name=\"group_name_required\">Gruppen Name benötigt</string>\n    <string name=\"group_delete_confirm_prompt\">Bist du dir sicher das du diese Gruppe entfernen möchtest\\?</string>\n    <string name=\"license\">Lizenz</string>\n    <string name=\"lines\">%d Linien</string>\n    <string name=\"auto\">Automatisch</string>\n    <string name=\"api_port\">API-Port</string>\n    <string name=\"night_mode\">Nachtmodus</string>\n    <string name=\"follow_system\">System folgen</string>\n    <string name=\"enable\">Aktivieren</string>\n    <string name=\"disable\">Deaktivieren</string>\n    <string name=\"enable_log\">Aktiviere Protokoll</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"random\">Zufällig</string>\n    <string name=\"enable_log_sum\">Für Debugging-Zwecken</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-es/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?> \n<resources> \n    <string name=\"app_desc\">La cadena de herramientas proxy universal para Android, escrita en Kotlin.</string>\n    <string name=\"project\">Proyecto</string> \n    <string name=\"github\">Código fuente</string> \n    <string name=\"telegram\">Canal de actualización de Telegram</string> \n    <string name=\"oss_licenses\">Licencias de código abierto</string> \n    <string name=\"app_version\">Versión</string> \n    <string name=\"version_x\">Versión (%s)</string> \n    <string name=\"logcat\">Exportar información de depuración</string>\n    <string name=\"menu_configuration\">Configuración</string>\n    <string name=\"menu_group\">Grupo</string> \n    <string name=\"menu_about\">Acerca de</string> \n    <string name=\"theme\">Tema</string> \n    <string name=\"document\">Documento</string> \n    <string name=\"group_default\">Desagrupado</string> \n    <string name=\"quick_toggle\">Alternar</string>\n    <string name=\"quick_enable\">Habilitar</string>\n    <string name=\"quick_disable\">Deshabilitar</string>\n    <string name=\"tile_title\">Conmutador</string> \n    <string name=\"menu_traffic\">Tráfico</string> \n    <!-- externel --> \n    <string name=\"action_copy\">Copiar</string> \n    <string name=\"action_open\">Abrir</string> \n    <!-- group --> \n    <string name=\"group_name\">Nombre del grupo</string> \n    <string name=\"group_update\">Actualizar</string> \n    <string name=\"group_subscription_link\">Enlace de suscripción</string> \n    <string name=\"group_name_required\">Nombre de grupo requerido</string> \n    <string name=\"group_create\">Crear grupo</string> \n    <string name=\"group_create_subscription\">Crear suscripción</string> \n    <string name=\"group_edit\">Editar grupo</string> \n    <string name=\"group_edit_subscription\">Editar suscripción</string> \n    <string name=\"group_status_empty\">Grupo vacío</string> \n    <string name=\"group_status_empty_subscription\">Aún no actualizado</string> \n    <string name=\"group_status_proxies\">%d proxys</string> \n    <string name=\"group_status_proxies_subscription\">%d proxys | Actualizado el %s</string> \n    <string name=\"group_no_difference\">%s: Sin diferencia</string> \n    <string name=\"group_updated\">%s: %d proxys actualizados</string> \n    <string name=\"group_show_diff\">Diferencia</string> \n    <string name=\"group_diff\">Diferencia (%s)</string> \n    <string name=\"group_delete_confirm_prompt\">¿Seguro que quieres eliminar este grupo?</string> \n    <string name=\"group_added\">Añadido: \\n%s</string> \n    <string name=\"group_changed\">Actualizado: \\n%s</string> \n    <string name=\"group_deleted\">Eliminado: \\n%s</string> \n    <string name=\"group_duplicate\">Duplicado: \\n%s</string> \n    <!-- misc --> \n    <string name=\"service_mode\">Modo de servicio</string> \n    <string name=\"service_mode_proxy\">Solo proxy</string> \n    <string name=\"service_mode_vpn\" translatable=\"false\">VPN</string> \n    <string name=\"port_proxy\">Puerto proxy SOCKS5</string> \n    <string name=\"port_http\">Puerto proxy HTTP</string> \n    <string name=\"port_transproxy\">Puerto transproxy</string> \n    <string name=\"cag_route\">Ajustes de ruta</string> \n    <string name=\"allow_access\">Permitir conexiones desde la LAN</string> \n    <string name=\"allow_access_sum\">Vincular servidores entrantes a 0.0.0.0</string> \n    <string name=\"inbound_settings\">Ajustes de entrada</string> \n    <string name=\"general_settings\">Ajustes de la app</string> \n    <string name=\"require_http\">Activar HTTP entrante</string> \n    <string name=\"cag_ws\">Ajustes de WebSocket</string> \n    <string name=\"ws_max_early_data\" translatable=\"false\">Max early data</string> \n    <string name=\"ws_browser_forwarding\">Reenvío del navegador</string> \n    <string name=\"ws_browser_forwarding_sum\">Reenvía los WebSockets correspondientes a través del navegador.</string> \n    <string name=\"speed_interval\">Intervalo de actualización de notificación de velocidad</string> \n    <string name=\"cag_misc\">Ajustes misceláneos</string> \n    <string name=\"show_stop\">Mostrar botón Detener</string> \n    <string name=\"show_stop_sum\">Si no desea utilizar mosaico como interruptor</string> \n    <string name=\"show_direct_speed\">Mostrar velocidad directa</string> \n    <string name=\"show_direct_speed_sum\">Muestra la velocidad del tráfico sin proxy en la notificación también</string> \n    <string name=\"security_settings\">Ajustes de seguridad</string> \n    <string name=\"allow_insecure\">Permitir inseguro</string> \n    <string name=\"allow_insecure_sum\">Desactiva la comprobación de certificados. Cuando está activada, esta configuración es tan segura como el texto sin formato</string> \n    <string name=\"traffic\" translatable=\"false\">%1$s↑ %2$s↓</string> \n    <string name=\"speed_detail\">Proxy： %1$s↑ %2$s↓\\nDirecto： %3$s↑ %4$s↓</string> \n    <string name=\"speed\">%s/s</string> \n    <string name=\"connection_test_testing\">Probando…</string> \n    <string name=\"connection_test_available\">Éxito: el protocolo de enlace HTTPS tomó %dms</string> \n    <string name=\"connection_test_available_http\">Éxito: el protocolo de enlace HTTP tomó %dms</string> \n    <string name=\"connection_test_error\">Error: %s</string> \n    <string name=\"connection_test_fail\">Internet no disponible</string> \n    <string name=\"connection_test_error_status_code\">Código de error: #%d</string> \n    <string name=\"cag_dns\">Ajustes de DNS</string> \n    <string name=\"remote_dns\">DNS remoto</string> \n    <string name=\"direct_dns\">DNS directo</string> \n    <string name=\"enable_dns_routing\">Activar enrutamiento DNS</string>\n    <string name=\"dns_routing_message\">Resuelve dominios en rutas de omisión con el DNS directo. Tenga en cuenta las posibles fugas de DNS</string> \n    <string name=\"enable_fakedns\">Activar DNS falso</string> \n    <string name=\"fakedns_message\">Puede hacer que sea necesario reiniciar otras aplicaciones para volver a conectarse a la red después de detener el proxy</string> \n    <string name=\"dns_hosts\">Reescritura de dominio</string> \n    <string name=\"port_local_dns\">Puerto DNS local</string> \n    <string name=\"require_transproxy\">Activar transproxy entrante</string> \n    <string name=\"transproxy_mode\">Modo transproxy</string> \n    <string name=\"connection_test_url\">URL de prueba de conexión</string> \n    <!-- proxy category --> \n    <string name=\"profile_name\">Nombre de perfil</string> \n    <string name=\"server_address\">Servidor</string> \n    <string name=\"server_port\">Puerto remoto</string> \n    <string name=\"username\">Nombre de usuario</string> \n    <string name=\"username_opt\">Nombre de usuario (Opcional)</string> \n    <string name=\"password_opt\">Contraseña (Opcional)</string> \n    <string name=\"key_opt\">Clave (Opcional)</string> \n    <string name=\"password\">Contraseña</string> \n    <string name=\"enc_method\">Método de cifrado</string> \n    <string name=\"protocol\">Protocolo</string> \n    <string name=\"protocol_param\">Parámetro de protocolo</string> \n    <string name=\"obfs\">Obfs</string> \n    <string name=\"obfs_param\">Parámetro obfs</string> \n    <string name=\"uuid\">ID de usuario</string> \n    <string name=\"alter_id\">ID alternativo</string> \n    <string name=\"security\">Cifrado de la capa de transporte</string> \n    <string name=\"network\">Red</string> \n    <string name=\"header_type\">Tipo de encabezado</string> \n    <string name=\"ws_host\">Host WebSocket</string> \n    <string name=\"ws_path\">Ruta WebSocket</string> \n    <string name=\"http_host\">Host HTTP</string> \n    <string name=\"http_path\">Ruta HTTP</string> \n    <string name=\"grpc_service_name\">Nombre de servicio gRPC</string> \n    <string name=\"tls\">Usar TLS</string> \n    <string name=\"sni\">Indicación del nombre del servidor</string> \n    <string name=\"alpn\">Negociación de protocolo de capa de aplicación</string> \n    <string name=\"certificates\">Certificados</string>\n    <string name=\"early_data_header_name\">Nombre de encabezado de datos tempranos</string>\n    <string name=\"encryption\">Cifrado</string> \n    <string name=\"extra_headers\">Encabezados adicionales</string> \n    <string name=\"config_type\">Tipo de configuración</string> \n    <string name=\"edit_config\">Editar configuración</string> \n    <!-- feature category --> \n    <string name=\"ipv6\">Ruta IPv6</string> \n    <string name=\"prefer\">Preferir</string> \n    <string name=\"only\">Solo</string> \n    <string name=\"metered\">Sugerencia medida</string> \n    <string name=\"metered_summary\">Sistema de sugerencias para tratar la VPN como medida</string> \n    <string name=\"menu_route\">Ruta</string> \n    <string name=\"route_add\">Crear ruta</string> \n    <string name=\"delete_route_prompt\">¿Está seguro de que desea eliminar esta ruta?</string> \n    <string name=\"route_name\">Nombre de ruta</string> \n    <string name=\"route_proxy\">Proxy</string> \n    <string name=\"route_bypass\">Omitir</string> \n    <string name=\"route_block\">Bloquear</string> \n    <string name=\"route_profile\">Seleccionar perfil…</string> \n    <string name=\"empty_route\">Ruta vacía</string> \n    <string name=\"empty_route_notice\">Establezca algunas reglas antes de guardar</string> \n    <string name=\"route_bypass_domain\">Regla de dominio para %s</string> \n    <string name=\"route_play_store\">Regla de la Play Store para %s</string> \n    <string name=\"route_bypass_ip\">Regla de IP para %s</string> \n    <string name=\"route_reset\">Reiniciar</string>\n    <string name=\"route_manage_assets\">Administrar activos de ruta</string>\n    <string name=\"route_assets\">Activos</string> \n    <string name=\"route_rules_provider\">Proveedor de activos de reglas</string> \n    <string name=\"route_rules_official\">Oficial</string> \n    <string name=\"route_asset_status\">Versión local: %s</string> \n    <string name=\"route_asset_no_update\">Sin actualización</string> \n    <string name=\"route_asset_updated\">Actualizado</string> \n    <string name=\"route_not_asset\">No es un archivo de activos: excepto .db, pero %s</string> \n    <string name=\"route_opt_bypass_lan\">Omitir LAN</string> \n    <string name=\"route_opt_block_ads\">Bloquear anuncios</string> \n    <string name=\"route_opt_block_analysis\">Bloquear analítica</string> \n    <string name=\"domain_strategy\">Estrategia de resolución de dominio</string> \n    <string name=\"traffic_sniffing\">Activar rastreo de tráfico</string> \n    <string name=\"enable_mux\">Activar multiplexor</string>\n    <string name=\"mux_concurrency\">Conexiones simultáneas Mux</string>\n    <string name=\"tcp_keep_alive_interval\">TCP mantiene activo el intervalo de entrega de paquetes</string>\n    <string name=\"proxied_apps\">Modo VPN de aplicaciones</string> \n    <string name=\"proxied_apps_summary\">Configurar el modo VPN para las aplicaciones seleccionadas</string> \n    <string name=\"on\">Encendido</string> \n    <string name=\"off\">Apagado</string> \n    <string name=\"search_apps\">Buscar…</string> \n    <string name=\"scanning\">Escaneando…</string> \n    <string name=\"invert_selections\">Invertir selecciones</string> \n    <string name=\"clear_selections\">Borrar selecciones</string> \n    <string name=\"bypass_apps\">Omitir</string> \n    <string name=\"show_system_apps\">Mostrar aplicaciones del sistema</string> \n    <string name=\"auto_connect\">Autoconectar</string> \n    <string name=\"auto_connect_summary\">Activa el proxy en el inicio/actualización de la aplicación si se estaba ejecutando antes</string> \n    <string name=\"direct_boot_aware\">Permitir alternar en la pantalla de bloqueo</string> \n    <string name=\"direct_boot_aware_summary\">La información de su perfil seleccionado estará menos protegida</string> \n    <!-- notification category --> \n    <string name=\"service_vpn\">Servicio VPN</string> \n    <string name=\"service_proxy\">Servicio de proxy</string> \n    <string name=\"forward_success\">Proxy iniciado.</string> \n    <string name=\"invalid_server\">Nombre de servidor no válido</string> \n    <string name=\"service_failed\">Fallido:</string> \n    <string name=\"stop\">Detener</string> \n    <string name=\"stopping\">Apagando…</string> \n    <string name=\"vpn_error\">%s</string> \n    <string name=\"vpn_permission_denied\">Permiso denegado para crear un servicio VPN</string> \n    <string name=\"reboot_required\">No se pudo iniciar el servicio VPN. Es posible que deba reiniciar su dispositivo.</string> \n    <!-- alert category --> \n    <string name=\"profile_empty\">Por favor seleccione un perfil</string> \n    <string name=\"connect\">Conectar</string> \n    <string name=\"clipboard_empty\">El portapapeles está vacío</string> \n    <!-- menu category --> \n    <string name=\"settings\">Ajustes</string> \n    <string name=\"edit\">Editar</string> \n    <string name=\"share\">Compartir</string> \n    <string name=\"add_profile\">Añadir perfil</string> \n    <string name=\"select_profile\">Seleccionar perfil</string>\n    <string name=\"proxy_chain\">Cadena del proxy</string>\n    <string name=\"custom_config\">Configuración personalizada</string> \n    <string name=\"balancer\">Equilibrador</string> \n    <string name=\"balancer_settings\">Ajustes del equilibrador</string> \n    <string name=\"balancer_type\">Tipo</string> \n    <string name=\"balancer_strategy\">Estrategia</string> \n    <string name=\"chain_settings\">Ajustes de la cadena</string> \n    <string name=\"config_settings\">Ajustes de configuración</string> \n    <string name=\"circular_reference\">Referencia circular</string> \n    <string name=\"circular_reference_sum\">La ruta no puede contenerse a sí misma.</string> \n    <string name=\"clear_profiles\">Borrar</string> \n    <string name=\"action_create_group\">Grupo vacío</string> \n    <string name=\"action_from_link\">Desde suscripción</string> \n    <string name=\"action_scan_china_apps\">Escanear aplicaciones de China</string> \n    <string name=\"action_export_file\">Exportar a archivo</string> \n    <string name=\"action_export_clipboard\">Exportar al portapapeles</string> \n    <string name=\"action_export\">Exportar</string> \n    <string name=\"action_import\">Importar desde portapapeles</string> \n    <string name=\"action_import_file\">Importar desde archivo</string> \n    <string name=\"action_export_msg\">¡Exportación exitosa!</string> \n    <string name=\"action_export_err\">Error al exportar.</string> \n    <string name=\"action_import_msg\">¡Importación exitosa!</string> \n    <string name=\"action_import_err\">No se pudo importar.</string> \n    <string name=\"file_manager_missing\">Su dispositivo carece de un selector de archivos estándar de Android, instale uno, como Material Files.</string> \n    <!-- share --> \n    <!-- profile --> \n    <string name=\"profile_config\">Configuración de perfil</string> \n    <string name=\"delete\">Eliminar</string> \n    <string name=\"delete_confirm_prompt\">¿Está seguro de que desea eliminar este perfil?</string> \n    <string name=\"share_qr_nfc\">Código QR</string> \n    <string name=\"add_profile_methods_scan_qr_code\">Escanear código QR</string> \n    <string name=\"add_profile_methods_manual_settings\">Ajustes manuales</string> \n    <plurals name=\"removed\"> \n        <item quantity=\"one\">Eliminado</item> \n        <item quantity=\"other\">%d elementos eliminados</item> \n    </plurals> \n    <plurals name=\"added\"> \n        <item quantity=\"one\">Añadido</item> \n        <item quantity=\"other\">%d proxys añadidos</item> \n    </plurals> \n    <string name=\"undo\">Deshacer</string>\n    <string name=\"insecure\">Inseguro</string>\n    <string name=\"deprecated\">Obsoleto</string> \n    <!-- tasker --> \n    <!-- status --> \n    <string name=\"connecting\">Conectando…</string> \n    <string name=\"vpn_connected\">Conectado, toque para verificar la conexión</string> \n    <string name=\"not_connected\">No conectado</string> \n    <!-- subscriptions --> \n    <string name=\"subscriptions\">Suscripciones</string> \n    <string name=\"cleartext_http_warning\">El tráfico HTTP de texto claro no es seguro</string> \n    <string name=\"error_title\">Error</string> \n    <string name=\"no_proxies_found\">No se encontraron proxys en el enlace</string> \n    <string name=\"no_proxies_found_in_subscription\">No se encontraron proxys en la suscripción</string> \n    <string name=\"no_proxies_found_in_file\">No se encontraron proxys en el archivo</string> \n    <string name=\"no_proxies_found_in_clipboard\">No se encontraron proxys en el portapapeles</string> \n    <string name=\"deduplication\">Deduplicación</string> \n    <string name=\"donate\">Donar</string> \n    <string name=\"donate_info\">Amo el dinero</string> \n    <string name=\"ignore_battery_optimizations\">Ignorar optimizaciones de batería</string> \n    <string name=\"ignore_battery_optimizations_sum\">Eliminar algunas restricciones</string> \n    <!-- plugin --> \n    <string name=\"plugin\">Complemento</string> \n    <string name=\"plugin_configure\">Configurar…</string> \n    <string name=\"plugin_disabled\">Desactivado</string> \n    <string name=\"plugin_unknown\">Complemento desconocido %s</string> \n    <string name=\"plugin_untrusted\">Advertencia: este complemento no parece provenir de una fuente confiable conocida.</string> \n    <string name=\"plugin_auto_connect_unlock_only\">Es posible que este complemento no funcione con Autoconectar</string> \n    <string name=\"proxy_cat\">Ajustes del servidor</string> \n    <string name=\"ss_cat\">Ajustes de Shadowsocks</string> \n    <string name=\"unsaved_changes_prompt\">Cambios no guardados. ¿Quieres guardar?</string> \n    <string name=\"yes\">Sí</string> \n    <string name=\"no\">No</string> \n    <string name=\"apply\">Aplicar</string> \n    <string name=\"need_reload\">Vuelva a cargar el servicio de proxy para aplicar los cambios</string>\n    <string name=\"license\">Licencia</string> \n    <string name=\"route_warn\">Asegúrese de haber leído la documentación antes de agregar reglas personalizadas; de lo contrario, es posible que no pueda conectarse a Internet.</string> \n    <string name=\"lines\">%d líneas</string> \n    <string name=\"night_mode\">Modo nocturno</string> \n    <string name=\"follow_system\">Seguir sistema</string> \n    <string name=\"enable\">Permitir</string> \n    <string name=\"disable\">Desactivar</string> \n    <string name=\"auto\">Automático</string> \n    <string name=\"enable_log\">Activar registro</string> \n    <string name=\"enable_log_sum\">Para propósitos de depuración</string> \n    <string name=\"list\">Lista</string> \n    <string name=\"random\">Aleatorio</string> \n    <string name=\"leastPing\" translatable=\"false\">Ping</string> \n    <string name=\"api_port\">Puerto API</string> \n    <string name=\"probe_interval\">Intervalo de observación del equilibrador</string> \n    <string name=\"standard\">Estándar</string> \n    <string name=\"v2rayn\" translatable=\"false\">V2RayN</string> \n    <string name=\"available\" translatable=\"false\">%dms</string> \n    <string name=\"unavailable\">No disponible</string> \n    <string name=\"always_show_address\">Mostrar siempre la dirección</string> \n    <string name=\"always_show_address_sum\">Muestra siempre la dirección del servidor en la tarjeta de configuración</string> \n    <string name=\"clear_traffic_statistics\">Borrar estadísticas de tráfico</string> \n    <string name=\"connection_test\">Prueba de conexión</string> \n    <string name=\"connection_test_clear_results\">Borrar resultados de la prueba</string> \n    <string name=\"connection_test_tcp_ping\" translatable=\"false\">TCPing</string> \n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing no disponible</string> \n    <string name=\"connection_test_icmp_ping\" translatable=\"false\">ICMPing</string> \n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing no disponible</string> \n    <string name=\"connection_test_url_test\" translatable=\"false\">URL Test</string> \n    <string name=\"connection_test_domain_not_found\">Dominio no encontrado</string> \n    <string name=\"connection_test_refused\">Conexión denegada</string> \n    <string name=\"connection_test_unreachable\">Inalcanzable</string> \n    <string name=\"connection_test_timeout\">Se acabó el tiempo</string> \n    <string name=\"append_http_proxy\">Añadir proxy HTTP a la VPN</string> \n    <string name=\"append_http_proxy_sum\">El proxy HTTP se utilizará directamente desde (navegador/algunas aplicaciones compatibles), sin pasar por el dispositivo NIC virtual (Android 10+)</string>\n    <string name=\"protocol_settings\">Ajustes de protocolo</string>\n    <string name=\"trojan_provider\">Proveedor Trojan</string> \n    <string name=\"group_basic\">Básico</string> \n    <string name=\"group_settings\">Ajustes de grupo</string> \n    <string name=\"subscription\">Suscripción</string> \n    <string name=\"subscription_settings\">Ajustes de suscripción</string> \n    <string name=\"group_type\">Tipo de grupo</string> \n    <string name=\"subscription_type\">Tipo de suscripción</string> \n    <string name=\"delete_group_prompt\">¿Seguro que quieres eliminar este grupo?</string> \n    <string name=\"force_resolve\">Forzar resolución</string> \n    <string name=\"force_resolve_sum\">Resuelve todos los nombres de dominio a direcciones IP al actualizar. Host y SNI se agregarán automáticamente si es posible</string> \n    <string name=\"deduplication_sum\">Eliminar configuraciones duplicadas al actualizar</string> \n    <string name=\"raw\">Raw</string> \n    <string name=\"update_settings\">Ajustes de actualización</string> \n    <string name=\"auto_update\">Actualización automática</string> \n    <string name=\"auto_update_delay\">Retraso de actualización automática (en minutos)</string> \n    <string name=\"update_when_connected_only\">Actualizar solo cuando está conectado</string> \n    <string name=\"update_when_connected_only_sum\">Evita la fuga de direcciones IP</string> \n    <string name=\"subscription_user_agent\">Agente de usuario</string> \n    <string name=\"confirm\">Confirmar</string> \n    <string name=\"missing_plugin\">Complemento faltante</string> \n    <string name=\"profile_requiring_plugin\">El perfil %s requiere que se instale el complemento %s, pero no se encontró.</string> \n    <string name=\"action_learn_more\">APRENDER MÁS</string> \n    <string name=\"action_download\">DESCARGAR</string> \n    <string name=\"install_from_play_store\">Instalar desde Play Store</string> \n    <string name=\"install_from_fdroid\">Instalar desde F-Droid</string> \n    <string name=\"download\">Descargar</string> \n    <string name=\"ooc_subscription_token\" translatable=\"false\">OOCv1 API Token</string> \n    <string name=\"ooc_subscription_token_invalid\">Token OOCv1 no válido</string> \n    <string name=\"update_subscription_warning\">El proxy no está conectado, ¿estás seguro de que deseas continuar con la actualización?</string> \n    <string name=\"ooc_warning\">Advertencia</string> \n    <string name=\"ooc_missing_protocol\">La suscripción requiere compatibilidad con el protocolo %s, pero no se encuentra. Los perfiles no admitidos serán ignorados.</string> \n    <string name=\"service_subscription\">Servicio de actualización de suscripción</string> \n    <string name=\"subscription_update\">Actualización de suscripción</string> \n    <string name=\"subscription_update_message\">Actualizando %s…</string> \n    <string name=\"group_filter\">Filtrar</string>\n    <string name=\"subscription_used\">%s usado</string>\n    <string name=\"subscription_traffic\">%s usados ​​/ %s restantes</string> \n    <string name=\"subscription_import\">Importar suscripción</string> \n    <string name=\"subscription_import_message\">¿Confirma que desea importar la suscripción %s? Si proviene de una fuente que no es de confianza, hacer esto puede provocar que se filtre su IP y su comportamiento.</string> \n    <string name=\"profile_import\">Importar perfil</string> \n    <string name=\"profile_import_message\">¿Confirma que desea importar el perfil %s?</string> \n    <string name=\"clear_profiles_message\">¿Estás seguro de que quieres borrar este grupo?</string> \n    <string name=\"apps\">Aplicaciones</string> \n    <string name=\"select_apps\">Seleccionar aplicaciones</string> \n    <string name=\"apps_message\">%d aplicaciones</string> \n    <string name=\"hysteria_obfs\">Contraseña de ofuscación</string> \n    <string name=\"hysteria_auth_type\">Tipo de autenticación</string> \n    <string name=\"hysteria_auth_payload\">Payload de autenticación</string> \n    <string name=\"hysteria_upload_mbps\">Velocidad máxima de carga (en Mbps)</string> \n    <string name=\"hysteria_download_mbps\">Velocidad máxima de descarga (en Mbps)</string> \n    <string name=\"experimental_settings\">Experimental</string>\n    <string name=\"hysteria_stream_receive_window\">Ventana de recepción de transmisión QUIC</string>\n    <string name=\"hysteria_connection_receive_window\">Ventana de recepción de conexión QUIC</string> \n    <string name=\"group_order\">Orden</string> \n    <string name=\"group_order_origin\">Origen</string> \n    <string name=\"group_order_by_name\">Por nombre</string> \n    <string name=\"group_order_by_delay\">Por retraso</string> \n    <string name=\"plugin_exists_but_on_shit_system\">El perfil %s requiere el complemento %s, pero el proveedor de su equipo patentado (por lo general, los gigantes del capital de vigilancia y el fabricante de malware) manipuló su Android e inutilizó el complemento.</string> \n \n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (complemento de Android Shadowsocks)</string> \n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (complemento de Android Shadowsocks)</string> \n \n    <string name=\"traffic_uplink\" translatable=\"false\">%s | %s/s ↑</string> \n    <string name=\"traffic_downlink\" translatable=\"false\">%s | %s/s ↓</string> \n \n    <string name=\"traffic_uplink_total\" translatable=\"false\">%s ↑</string> \n    <string name=\"traffic_downlink_total\" translatable=\"false\">%s ↓</string>\n\n    <string name=\"tcp_connections\">%d conexiones TCP</string> \n    <string name=\"udp_connections\">%d conexiones UDP</string>\n\n    <string name=\"copy_label\">Copiar nombre</string>\n    <string name=\"copy_package_name\">Copiar nombre del paquete</string> \n    <string name=\"open_app\">Abrir la app</string> \n    <string name=\"open_settings\">Abrir los ajustes</string> \n    <string name=\"open_market\">Abrir la tienda</string> \n    <string name=\"create_rule\">Crear regla</string> \n \n    <string name=\"copy_success\">¡Copiado exitoso!</string> \n    <string name=\"copy_failed\">Error al copiar.</string> \n    <string name=\"app_no_launcher\">La aplicación no tiene interfaz.</string> \n    <string name=\"route_for\">Regla para %s</string> \n \n    <string name=\"route_need_vpn\">La regla de enrutamiento %s depende de que la VPN esté en vigor, por lo que se ignora.</string>\n\n    <string name=\"profile_traffic_statistics\">Estadísticas de tráfico del perfil</string>\n    <string name=\"profile_traffic_statistics_summary\">Cuando está desactivado, el tráfico utilizado no se contará</string>\n    <string name=\"no_statistics\">Aún no hay estadísticas</string> \n    <string name=\"app_statistics_disabled\">Estadísticas de tráfico de aplicaciones desactivado</string> \n    <string name=\"ssh_auth_type_none\">Ninguna</string> \n    <string name=\"ssh_public_key\">Llave pública</string> \n    <string name=\"ssh_private_key\">Llave privada</string> \n    <string name=\"ssh_private_key_passphrase\">Frase de contraseña de clave privada</string> \n    <string name=\"translate_platform\">Plataforma de traducción</string> \n    <string name=\"menu_tools\">Herramientas</string> \n    <string name=\"menu_log\">Registros</string> \n    <string name=\"clear_logcat\">Borrar Logcat</string> \n    <string name=\"wireguard_local_address\">Dirección local</string> \n    <string name=\"wireguard_public_key\">Clave pública de pares</string> \n    <string name=\"wireguard_psk\">Clave precompartida de pares</string> \n \n    <string name=\"cloudflare_wrap\" translatable=\"false\">Cloudflare Warp</string> \n    <string name=\"warp_license\">CloudFlare Warp es un proveedor gratuito de la VPN WireGuard. Al usarlo, usted acepta los TOS.</string> \n    <string name=\"warp_generate\">Generar configuración</string> \n    <string name=\"generating\">Generando…</string> \n    <string name=\"tun_implementation\">Implementación TUN</string> \n    <string name=\"destination_override\">Anular destino</string> \n    <string name=\"destination_override_summary\">Usa el dominio rastreado para sobrescribir la dirección de destino, no solo para el enrutamiento</string> \n    <string name=\"resolve_destination\">Resolver destino</string> \n    <string name=\"resolve_destination_summary\">Si la dirección de destino es un dominio, se distribuye según la estrategia IPv6.</string> \n    <string name=\"pcap\" translatable=\"false\">Pcap</string>\n    <string name=\"pcap_notice\">Los archivos Pcap se guardarán en %s</string>\n    <string name=\"naive_insecure_concurrency\">Concurrencia insegura</string> \n    <string name=\"naive_insecure_concurrency_summary\">Usa N conexiones de túnel concurrentes para ser más robusto en malas condiciones de red. Más conexiones hacen que la tunelización sea más fácil de detectar y menos segura. Este proyecto se esfuerza por lograr la mayor seguridad contra el análisis del tráfico. Usarlo de manera insegura anula su propósito. \\n\\nSi debe usar esto, pruebe N=2 primero para ver si resuelve sus problemas. Se recomienda enfáticamente no usar más de 4 conexiones aquí.</string> \n \n    <string name=\"stun_test\">Descubrimiento de comportamiento NAT</string> \n    <string name=\"stun_test_summary\">Determine el comportamiento de asignación de NAT del cliente y el comportamiento de filtrado de NAT definido en RFC 3478 mediante STUN.</string> \n    <string name=\"start\">Iniciar</string> \n    <string name=\"stun_attest_loading\">Esto puede tomar unos pocos minutos…</string> \n    <string name=\"nat_stun_server_hint\">Servidor Stun</string> \n    <string name=\"nat_result_hint\">Resultado de la comprobación de NAT</string> \n    <string name=\"tools_network\">Red</string> \n    <string name=\"mtu\" translatable=\"false\">MTU</string> \n \n    <string name=\"backup\">Respaldo</string> \n    <string name=\"backup_groups_and_configurations\">Grupos y configuraciones</string> \n    <string name=\"backup_rules\">Reglas de enrutamiento</string> \n    <string name=\"backup_settings\">Ajustes</string> \n    <string name=\"backup_summary\">Si no se realiza un respaldo de los ajustes de enrutamiento con las configuraciones, se perderán las salidas personalizadas.</string> \n    <string name=\"backup_not_file\">No es un archivo de respaldo: excepto .json, pero %s</string> \n    <string name=\"invalid_backup_file\">Archivo de respaldo no válido</string> \n    <string name=\"backup_import\">Importar</string> \n    <string name=\"backup_import_summary\">La importación sobrescribirá los datos existentes.</string> \n    <string name=\"backup_importing\">Importando…</string> \n \n    <string name=\"packet_encoding\">Codificación de paquetes</string>\n    <string name=\"acquire_wake_lock\">Adquirir WakeLock</string>\n    <string name=\"release_wake_lock\">Liberar WakeLock</string> \n    <string name=\"acquire_wake_lock_summary\">Mantener la CPU encendida</string> \n    <string name=\"action_switch\">Cambiar</string> \n \n    <string name=\"connection_test_delete_unavailable\">Borrado no disponible</string> \n\n</resources> \n"
  },
  {
    "path": "app/src/main/res/values-fa/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">NekoBox</string>\n    <string name=\"app_name_long\" translatable=\"false\">NekoBox for Android</string>\n    <string name=\"app_desc\">مجموعه ابزار جامع پراکسی برای اندروید، نوشته‌شده با کاتلین.</string>\n    <string name=\"project\">پروژه</string>\n    <string name=\"github\">کد منبع</string>\n    <string name=\"telegram\">کانال دریافت نسخه‌های به‌روز در تلگرام</string>\n    <string name=\"oss_licenses\">مجوزهای متن‌باز</string>\n    <string name=\"app_version\">نسخه برنامه</string>\n    <string name=\"version_x\">نسخه (%s)</string>\n    <string name=\"logcat\">صدور اطلاعات عیب‌یابی</string>\n    <string name=\"menu_configuration\">پیکربندی</string>\n    <string name=\"menu_group\">گروه‌ها</string>\n    <string name=\"menu_about\">درباره برنامه</string>\n    <string name=\"theme\">ظاهر</string>\n    <string name=\"document\">مستندات</string>\n    <string name=\"group_default\">گروه پیش‌فرض</string>\n    <string name=\"quick_toggle\">تغییر وضعیت</string>\n    <string name=\"quick_enable\">فعال‌سازی</string>\n    <string name=\"quick_disable\">غیرفعال‌سازی</string>\n    <string name=\"tile_title\">سوییچر</string>\n    <string name=\"menu_traffic\">ترافیک</string>\n    <string name=\"menu_dashboard\">sing-box تنظیمات</string>\n    <!-- externel -->\n    <string name=\"action_copy\">کپی</string>\n    <string name=\"action_open\">باز کردن</string>\n    <!-- group -->\n    <string name=\"group_name\">نام گروه</string>\n    <string name=\"group_update\">به‌روزرسانی گروه</string>\n    <string name=\"group_subscription_link\">لینک اشتراک</string>\n    <string name=\"group_name_required\">نام گروه را وارد کنید</string>\n    <string name=\"group_create\">ایجاد گروه</string>\n    <string name=\"group_create_subscription\">ایجاد اشتراک</string>\n    <string name=\"update_all_subscription\">به‌روزرسانی همه اشتراک‌ها</string>\n    <string name=\"group_edit\">ویرایش گروه</string>\n    <string name=\"group_edit_subscription\">ویرایش اشتراک</string>\n    <string name=\"group_status_empty\">خالی</string>\n    <string name=\"group_status_empty_subscription\">هنوز به‌روزرسانی نشده است</string>\n    <string name=\"group_status_proxies\">%d پراکسی‌ها</string>\n    <string name=\"group_status_proxies_subscription\">%d پراکسی‌ها | به‌روزرسانی شدند در %s</string>\n    <string name=\"group_no_difference\">%s: بدون تفاوت</string>\n    <string name=\"group_updated\">%s: Updated %d proxies</string>\n    <string name=\"group_show_diff\">تفاوت‌ها</string>\n    <string name=\"group_diff\">تفاوت‌ها (%s)</string>\n    <string name=\"group_delete_confirm_prompt\">آیا مطمئن هستید که می‌خواهید این گروه را حذف کنید؟</string>\n    <string name=\"group_added\">اضافه شده: \\n%s</string>\n    <string name=\"group_changed\">به‌روز شد \\n%s</string>\n    <string name=\"group_deleted\">حذف شد: \\n%s</string>\n    <string name=\"group_duplicate\">تکراری: \\n%s</string>\n    <!-- misc -->\n    <string name=\"service_mode\">حالت سرویس</string>\n    <string name=\"service_mode_proxy\">فقط به حالت پراکسی</string>\n    <string name=\"service_mode_vpn\" translatable=\"false\">VPN</string>\n    <string name=\"port_proxy\">پورت پراکسی</string>\n    <string name=\"port_http\">پورت پراکسی HTTP</string>\n    <string name=\"port_transproxy\">پورت Transproxy</string>\n    <string name=\"cag_route\">تنظیمات مسیردهی</string>\n    <string name=\"allow_access\">اجازه اتصال از شبکه محلی</string>\n    <string name=\"allow_access_sum\">اجازه اتصال سرورهای ورودی به 0.0.0.0</string>\n    <string name=\"inbound_settings\">تنظیمات اتصالات ورودی</string>\n    <string name=\"general_settings\">تنظیمات برنامه</string>\n    <string name=\"require_http\">فعال‌کردن ورودی HTTP</string>\n    <string name=\"cag_ws\">تنظیمات WebSocket</string>\n    <string name=\"ws_max_early_data\" translatable=\"false\">Max early data</string>\n    <string name=\"ws_browser_forwarding\">اتصال مرورگر</string>\n    <string name=\"ws_browser_forwarding_sum\">ارسال WebSocketهای مربوطه از طریق مرورگر</string>\n    <string name=\"speed_interval\">تنظیم فاصله زمانی نمایش سرعت</string>\n    <string name=\"cag_misc\">تنظیمات متفرقه</string>\n    <string name=\"show_stop\">نمایش دکمه توقف</string>\n    <string name=\"show_stop_sum\">اگر دکمه تغییر وضعیت در نوار اعلان را نمی‌خواهید</string>\n    <string name=\"show_direct_speed\">نمایش سرعت</string>\n    <string name=\"show_direct_speed_sum\">نمایش سرعت اتصال، حتی در هنگام غیرفعال بودن پراکسی</string>\n    <string name=\"security_settings\">تنظیمات امنیت TLS</string>\n    <string name=\"allow_insecure\">اتصال محافظت نشده</string>\n    <string name=\"allow_insecure_sum\">اگر بررسی گواهی‌ها غیرفعال باشد، اتصال بدون رمزنگاری خواهد بود.</string>\n    <string name=\"traffic\" translatable=\"false\">%1$s↑ %2$s↓</string>\n    <string name=\"speed_detail\">سرعت： %1$s↑ %2$s↓\\nمستقیم： %3$s↑ %4$s↓</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_testing\">در حال آزمایش</string>\n    <string name=\"connection_test_available\">اتصال موفق! سرعت برقراری ارتباط HTTPS: %dms</string>\n    <string name=\"connection_test_available_http\">اتصال موفق! سرعت برقراری ارتباط HTTP: %dms</string>\n    <string name=\"connection_test_error\">ناموفق بود: %s</string>\n    <string name=\"connection_test_fail\">اینترنت متصل نیست</string>\n    <string name=\"connection_test_error_status_code\">کد ارور: #%d</string>\n    <string name=\"cag_dns\">تنظیمات DNs</string>\n    <string name=\"remote_dns\">DNS ریموت</string>\n    <string name=\"direct_dns\">DNS مستقیم</string>\n    <string name=\"enable_dns_routing\">فعال‌کردن مسیریابی DNS</string>\n    <string name=\"dns_routing_message\">بررسی دامنه‌ها در دورزدن مسیرها با DNS مستقیم. احتمال افشای نشانی DNS وجود دارد.</string>\n    <string name=\"enable_fakedns\">فعال کردن FakeDNS</string>\n    <string name=\"fakedns_message\">ممکن است نیاز باشد که برنامه‌های دیگر را برای اتصال مجدد، دوباره راه‌اندازی کنید.</string>\n    <string name=\"dns_hosts\">بازنویسی دامنه</string>\n    <string name=\"port_local_dns\">پورت DNS محلی</string>\n    <string name=\"require_transproxy\">فعال کردن Transproxy ورودی</string>\n    <string name=\"transproxy_mode\">حالت Transproxy</string>\n    <string name=\"connection_test_url\">آزمایش اتصال به لینک</string>\n    <!-- proxy category -->\n    <string name=\"profile_name\">نام</string>\n    <string name=\"server_address\">آدرس سرور</string>\n    <string name=\"server_port\">پورت سرور</string>\n    <string name=\"username\">نام کاربری</string>\n    <string name=\"username_opt\">نام کاربری (اختیاری)</string>\n    <string name=\"password_opt\">رمز عبور (اختیاری)</string>\n    <string name=\"key_opt\">کلید (اختیاری)</string>\n    <string name=\"password\">Pرمز عبور</string>\n    <string name=\"enc_method\">نوع رمزنگاری</string>\n    <string name=\"protocol\">دستورالعمل</string>\n    <string name=\"protocol_param\">مؤلفه دستورالعمل</string>\n    <string name=\"obfs\">دستورالعمل درهم‌سازی obfs</string>\n    <string name=\"obfs_param\">مؤلفه Obfs</string>\n    <string name=\"uuid\">شناسه کاربری</string>\n    <string name=\"alter_id\">شناسه دیگر</string>\n    <string name=\"security\">رمزنگاری لایه Transport</string>\n    <string name=\"network\">شبکه</string>\n    <string name=\"header_type\">نوع سرآیند</string>\n    <string name=\"ws_host\">میزبان WebSocket</string>\n    <string name=\"ws_path\">مسیر WebSocket</string>\n    <string name=\"http_host\">میزبان HTTP</string>\n    <string name=\"http_path\">مسیر HTTP</string>\n    <string name=\"grpc_service_name\">نام سرویس gRPC</string>\n    <string name=\"tls\">استفاده از TLS</string>\n    <string name=\"sni\">SNI</string>\n    <string name=\"alpn\">ALPN</string>\n    <string name=\"certificates\">گواهی‌ها</string>\n    <string name=\"early_data_header_name\">نام سرآیند درخواست داده‌های اولیه</string>\n    <string name=\"encryption\">رمزنگاری</string>\n    <string name=\"extra_headers\">سرآیندهای اضافی</string>\n    <string name=\"config_type\">نوع پیکربندی</string>\n    <string name=\"edit_config\">ویرایش پیکربندی</string>\n    <!-- feature category -->\n    <string name=\"ipv6\">مسیریابی IP نسخه 6</string>\n    <string name=\"prefer\">ترجیح</string>\n    <string name=\"only\">فقط</string>\n    <string name=\"metered\">اتصال اندازه‌گیری‌شده</string>\n    <string name=\"metered_summary\">تنظیم VPN برای سیستم به عنوان اتصال اندازه‌گیری‌شده</string>\n    <string name=\"menu_route\">مسیریابی</string>\n    <string name=\"route_add\">ایجاد مسیر اختصاصی جدید</string>\n    <string name=\"delete_route_prompt\">آیا مطمئن هستید که این مسیر را می‌خواهید حذف کنید؟</string>\n    <string name=\"route_name\">نام مسیر</string>\n    <string name=\"route_proxy\">اتصال با پراکسی</string>\n    <string name=\"route_bypass\">اتصال مستقیم بدون پراکسی</string>\n    <string name=\"route_block\">مسدودکردن اتصال</string>\n    <string name=\"route_profile\">انتخاب پراکسی...</string>\n    <string name=\"empty_route\">مسیر خالی</string>\n    <string name=\"empty_route_notice\">قبل از ثبت، دستورالعملی اضافه کنید.</string>\n    <string name=\"route_bypass_domain\">دستورالعمل برای دامنه‌های %s</string>\n    <string name=\"route_play_store\">دستورالعمل برای فروشگاه Play %s</string>\n    <string name=\"route_bypass_ip\">دستورالعمل برای IPهای %s</string>\n    <string name=\"route_reset\">بازنشانی</string>\n    <string name=\"route_manage_assets\">مدیریت جزئیات مسیریابی</string>\n    <string name=\"route_assets\">جزئیات</string>\n    <string name=\"route_rules_provider\">تأمین‌کننده جزئیات دستورالعمل‌ها</string>\n    <string name=\"route_rules_official\">رسمی</string>\n    <string name=\"route_asset_status\">نسخه محلی: %s</string>\n    <string name=\"route_asset_no_update\">به‌روزرسانی یافت نشد</string>\n    <string name=\"route_asset_updated\">به‌روزرسانی شد</string>\n    <string name=\"route_not_asset\">مشکل: ot an asset file: excepted .db, but %s</string>\n    <string name=\"route_opt_bypass_lan\">دورزدن شبکه محلی</string>\n    <string name=\"route_opt_block_ads\">مسدود کردن تبلیغات</string>\n    <string name=\"route_opt_block_analysis\">مسدود کردن ردیاب‌های اینترنتی</string>\n    <string name=\"route_opt_block_quic\">مسدود کردن QUIC</string>\n    <string name=\"domain_strategy\">راهبرد شناسایی دامنه</string>\n    <string name=\"traffic_sniffing\">فعال کردن ثبت ترافیک</string>\n    <string name=\"enable_mux\">فعال کردن مالتی‌پلکسر</string>\n    <string name=\"mux_sum\">باید سرور هم امکان پشتیبانی از مالتی‌پلکسر را داشته باشد. مالتی‌پلکسر برای کاهش زمان تأخیر در اتصال TCP طراحی شده است و نه برای افزایش توان اتصال. استفاده از مالتی‌پلکسر برای تماشای فیلم، دانلود فایل یا آزمایش سرعت معمولاً نتیجه‌ای ندارد.</string>\n    <string name=\"mux_concurrency\">تعداد اتصال همزمان با مالتی‌پلکسر</string>\n    <string name=\"tcp_keep_alive_interval\">فعال نگه‌داشتن بازه دریافت بسته‌های TCP</string>\n    <string name=\"proxied_apps\">تانل‌کردن اپلیکیشن‌ها</string>\n    <string name=\"proxied_apps_summary\">انتخاب اپلیکیشن‌های مورد نظر برای پراکسی شدن</string>\n    <string name=\"on\">روشن</string>\n    <string name=\"off\">خاموش</string>\n    <string name=\"search_apps\">در حال جستجو...</string>\n    <string name=\"scanning\">در حال اسکن...</string>\n    <string name=\"invert_selections\">برعکس‌کردن گزینه‌های انتخاب‌شده</string>\n    <string name=\"clear_selections\">برداشتن انتخاب‌ها</string>\n    <string name=\"bypass_apps\">پراکسی نشوند</string>\n    <string name=\"show_system_apps\">نمایش برنامه‌های سیستمی</string>\n    <string name=\"auto_connect\">اتصال خودکار</string>\n    <string name=\"auto_connect_summary\">فعال کردن پراکسی در هنگام راه‌اندازی/به‌روزرسانی برنامه اگر قبلاً در حال اجرا بوده است</string>\n    <string name=\"direct_boot_aware\">امکان تغییر وضعیت پراکسی در صفحه قفل</string>\n    <string name=\"direct_boot_aware_summary\">اطلاعات پراکسی انتخابی شما، کمتر محافظت می‌شود.</string>\n    <!-- notification category -->\n    <string name=\"service_vpn\">VPN سراسری</string>\n    <string name=\"service_proxy\">پراکسی</string>\n    <string name=\"forward_success\">پراکسی راه‌اندازی شد.</string>\n    <string name=\"invalid_server\">نام سرور نادرست است</string>\n    <string name=\"service_failed\">ناموفق: </string>\n    <string name=\"stop\">توقف</string>\n    <string name=\"stopping\">خاموش کردن...</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"vpn_permission_denied\">اجازه ایجاد سرویس VPN داده نشد.</string>\n    <string name=\"reboot_required\">راه‌اندازی VPN ناموفق بود، بهتر است یک‌بار دستگاهتان را خاموش و راه‌اندازی مجدد کنید.</string>\n    <!-- alert category -->\n    <string name=\"profile_empty\">لطفاً یک پراکسی را انتخاب کنید.</string>\n    <string name=\"connect\">اتصال</string>\n    <string name=\"clipboard_empty\">بریده‌دان خالی است.</string>\n    <!-- menu category -->\n    <string name=\"settings\">تنظیمات</string>\n    <string name=\"edit\">ویرایش</string>\n    <string name=\"share\">اشتراک‌گذاری</string>\n    <string name=\"add_profile\">اضافه‌کردن پراکسی جدید</string>\n    <string name=\"select_profile\">انتخاب پراکسی</string>\n    <string name=\"action_socks\" translatable=\"false\">SOCKS</string>\n    <string name=\"action_http\" translatable=\"false\">HTTP</string>\n    <string name=\"action_shadowsocks\" translatable=\"false\">Shadowsocks</string>\n    <string name=\"action_shadowsocksr\" translatable=\"false\">ShadowsocksR</string>\n    <string name=\"action_vmess\" translatable=\"false\">VMess</string>\n    <string name=\"action_trojan\" translatable=\"false\">Trojan</string>\n    <string name=\"action_trojan_go\" translatable=\"false\">Trojan Go</string>\n    <string name=\"action_mieru\" translatable=\"false\">Mieru</string>\n    <string name=\"action_naive\" translatable=\"false\">Naïve</string>\n    <string name=\"action_hysteria\" translatable=\"false\">Hysteria</string>\n    <string name=\"action_ssh\" translatable=\"false\">SSH</string>\n    <string name=\"action_wireguard\" translatable=\"false\">WireGuard</string>\n    <string name=\"action_tuic\" translatable=\"false\">TUIC</string>\n    <string name=\"proxy_chain\">زنجیره پراکسی</string>\n    <string name=\"custom_config\">پیکربندی اختصاصی</string>\n    <string name=\"balancer\">برابرکننده</string>\n    <string name=\"balancer_settings\">تنظیمات برابرکننده</string>\n    <string name=\"balancer_type\">نوع</string>\n    <string name=\"balancer_strategy\">راهبرد</string>\n    <string name=\"chain_settings\">تنظیمات زنجیره</string>\n    <string name=\"config_settings\">تنظیمات پیکربندی</string>\n    <string name=\"circular_reference\">ارجاع حلقه‌ای</string>\n    <string name=\"circular_reference_sum\">مسیر نمی‌تواند شامل خودش باشد.</string>\n    <string name=\"clear_profiles\">پاک‌کردن</string>\n    <string name=\"action_create_group\">گروه خالی</string>\n    <string name=\"action_from_link\">از اشتراک</string>\n    <string name=\"action_scan_china_apps\">اسکن برنامه‌های چینی</string>\n    <string name=\"action_export_file\">صدور به صورت فایل</string>\n    <string name=\"action_export_clipboard\">صدور در بریده‌دان</string>\n    <string name=\"action_export\">صدور</string>\n    <string name=\"action_import\">وارد کردن از بریده‌دان</string>\n    <string name=\"action_import_file\">وارد کردن از یک فایل</string>\n    <string name=\"action_export_msg\">صدور موفق</string>\n    <string name=\"action_export_err\">صدور انجام نشد.</string>\n    <string name=\"action_import_msg\">با موفقیت اضافه شد!</string>\n    <string name=\"action_import_err\">وارد نشد.</string>\n    <string name=\"file_manager_missing\">لطفاً دستگاه شما فاقد انتخابگر فایل استاندارد Android است\n یکی مانند Material Files را نصب کنید.</string>\n    <!-- share -->\n    <!-- profile -->\n    <string name=\"profile_config\">پیکربندی پراکسی</string>\n    <string name=\"delete\">حذف</string>\n    <string name=\"delete_confirm_prompt\">آیا مطمئن هستید که می‌خواهید این پراکسی را حذف کنید؟</string>\n    <string name=\"share_qr_nfc\">کد QR</string>\n    <string name=\"add_profile_methods_scan_qr_code\">اسکن از کد QR</string>\n    <string name=\"add_profile_methods_manual_settings\">تنظیمات دستی</string>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">حذف شد</item>\n        <item quantity=\"other\">%d حذف شدند</item>\n    </plurals>\n    <plurals name=\"added\">\n        <item quantity=\"one\">پراکسی اضافه شد</item>\n        <item quantity=\"other\">%d پراکسی اضافه شدند</item>\n    </plurals>\n    <string name=\"undo\">برگشت</string>\n    <string name=\"insecure\">ناامن</string>\n    <string name=\"deprecated\">منسوخ</string>\n    <!-- tasker -->\n    <!-- status -->\n    <string name=\"connecting\">در حال اتصال...</string>\n    <string name=\"vpn_connected\">متصل شد! برای بررسی اتصال، اینجا کلیک کنید.</string>\n    <string name=\"not_connected\">وصل نیست</string>\n    <!-- subscriptions -->\n    <string name=\"subscriptions\">اشتراک‌ها</string>\n    <string name=\"cleartext_http_warning\">ترافیک HTTP شفاف ناامن است</string>\n    <string name=\"error_title\">خطا</string>\n    <string name=\"no_proxies_found\">هیچ پراکسی در لینک مورد نظر یافت نشد.</string>\n    <string name=\"no_proxies_found_in_subscription\">هیچ پراکسی در اشتراک مورد نظر یافت نشد.</string>\n    <string name=\"no_proxies_found_in_file\">هیچ پراکسی در فایل مورد نظر یافت نشد.</string>\n    <string name=\"no_proxies_found_in_clipboard\">هیچ پراکسی در بریده‌دان یافت نشد.</string>\n    <string name=\"deduplication\">حذف موارد تکراری</string>\n    <string name=\"donate\">حمایت از برنامه‌نویس</string>\n    <string name=\"donate_info\">عاشق پول هستم!</string>\n    <string name=\"ignore_battery_optimizations\">بهینه‌سازی باتری را برای این برنامه غیرفعال کنید</string>\n    <string name=\"ignore_battery_optimizations_sum\">چند محدودیت برداشته می‌شود</string>\n    <!-- plugin -->\n    <string name=\"plugin\">افزونه</string>\n    <string name=\"plugin_configure\">پیکربندی...</string>\n    <string name=\"plugin_disabled\">غیرفعال</string>\n    <string name=\"plugin_unknown\">افزونه نامشخص %s</string>\n    <string name=\"plugin_untrusted\">هشدار: به نظر می‌رسد این افزونه از منابع شناخته‌شده نیست و ممکن است ناامن باشد.</string>\n    <string name=\"plugin_auto_connect_unlock_only\">این افزونه ممکن است با اتصال خودکار، کار نکند</string>\n    <string name=\"proxy_cat\">تنظیمات سرور</string>\n    <string name=\"ss_cat\">تنظیمات سرور</string>\n    <string name=\"unsaved_changes_prompt\">تغییرات جدید هنوز ذخیره نشدند، آیا می‌خواهید ذخیره شوند؟</string>\n    <string name=\"yes\">بله</string>\n    <string name=\"no\">خیر</string>\n    <string name=\"apply\">اعمال تغییرات</string>\n    <string name=\"need_reload\">برای اعمال تغییرات، سرویس پراکسی را دوباره راه‌اندازی کنید</string>\n    <string name=\"license\">مجوز</string>\n    <string name=\"route_warn\">اطمینان حاصل کنید که مستندات فنی را قبل از افزودن قوانین سفارشی مطالعه کرده‌اید،\n در غیر این صورت ممکن است نتوانید به اینترنت وصل شوید.</string>\n    <string name=\"lines\">%d خط</string>\n    <string name=\"night_mode\">حالت تاریک</string>\n    <string name=\"follow_system\">مانند سیستم</string>\n    <string name=\"enable\">فعال</string>\n    <string name=\"disable\">غیرفعال</string>\n    <string name=\"auto\">خودکار</string>\n    <string name=\"enable_log\">فعال کردن ثبت آمار</string>\n    <string name=\"enable_log_sum\">برای اهداف عیب‌یابی</string>\n    <string name=\"list\">فهرست</string>\n    <string name=\"random\">تصادفی</string>\n    <string name=\"leastPing\" translatable=\"false\">Ping</string>\n    <string name=\"api_port\">پورت API</string>\n    <string name=\"probe_interval\">مدت زمان Balancer observation</string>\n    <string name=\"standard\">استاندارد</string>\n    <string name=\"v2rayn\" translatable=\"false\">V2RayN</string>\n    <string name=\"available\" translatable=\"false\">%dms</string>\n    <string name=\"unavailable\">در دسترس نیست</string>\n    <string name=\"always_show_address\">نشانی‌ها همواره نمایش داده شود</string>\n    <string name=\"always_show_address_sum\">همیشه نشانی سرور را روی کارت پیکربندی نمایش دهید</string>\n    <string name=\"clear_traffic_statistics\">پاک کردن اطلاعات ترافیک</string>\n    <string name=\"connection_test\">آزمایش اتصال</string>\n    <string name=\"connection_test_clear_results\">پاک کردن نتایج آزمایش</string>\n    <string name=\"connection_test_tcp_ping\" translatable=\"false\">TCPing</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing در دسترس نیست</string>\n    <string name=\"connection_test_icmp_ping\" translatable=\"false\">ICMPing</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing در دسترس نیست</string>\n    <string name=\"connection_test_url_test\" translatable=\"false\">آزمایش اتصال به لینک</string>\n    <string name=\"connection_test_domain_not_found\">دامنه یافت نشد</string>\n    <string name=\"connection_test_refused\">اتصال رد شد</string>\n    <string name=\"connection_test_unreachable\">متصل نمی‌شود</string>\n    <string name=\"connection_test_timeout\">بدون پاسخ</string>\n    <string name=\"append_http_proxy\">پراکسی HTTP را به VPN اضافه کنید</string>\n    <string name=\"append_http_proxy_sum\">پروکسی HTTP مستقیماً از (مرورگر/برخی برنامه‌های پشتیبانی‌شده) بدون استفاده از دستگاه NIC مجازی (نسخه اندروید بالاتر از 10) استفاده می‌شود.</string>\n    <string name=\"protocol_settings\">تنظیمات پروتکل‌ها</string>\n    <string name=\"trojan_provider\">تأمین‌کننده Trojan</string>\n    <string name=\"group_basic\">ابتدایی</string>\n    <string name=\"group_settings\">تنظیمات گروه</string>\n    <string name=\"subscription\">اشتراک</string>\n    <string name=\"subscription_settings\">تنظیمات اشتراک</string>\n    <string name=\"group_type\">نوع گروه</string>\n    <string name=\"subscription_type\">نوع اشتراک</string>\n    <string name=\"delete_group_prompt\">آیا می‌خواهید این گروه را حذف کنید؟</string>\n    <string name=\"force_resolve\">بررسی اجباری</string>\n    <string name=\"force_resolve_sum\">هنگام به‌روزرسانی گروه، همه نام‌های دامنه را به نشانی IP تفسیر کنید. میزبان و SNI در صورت امکان به صورت خودکار اضافه می‌شوند.</string>\n    <string name=\"deduplication_sum\">حذف خودکار پراکسی‌های تکراری، بعد از به‌روزرسانی</string>\n    <string name=\"raw\">خام</string>\n    <string name=\"update_settings\">اعمال تغییرات</string>\n    <string name=\"auto_update\">به‌روزرسانی خودکار</string>\n    <string name=\"auto_update_delay\">به‌روزرسانی خودکار گروه (در مدت زمان مورد نظر به دقیقه)</string>\n    <string name=\"update_when_connected_only\">به‌روزرسانی هنگام متصل بودن</string>\n    <string name=\"update_when_connected_only_sum\">از افشای IP جلوگیری می‌شود.</string>\n    <string name=\"subscription_user_agent\">UserAgent</string>\n    <string name=\"confirm\">تأیید</string>\n    <string name=\"missing_plugin\">افزونه مورد نیاز یافت‌ نشد</string>\n    <string name=\"profile_requiring_plugin\">پراکسی %s نیاز به نصب افزونه %s دارد، اما پیدا نشد.</string>\n    <string name=\"action_learn_more\">اطلاعات بیشتر</string>\n    <string name=\"action_download\">دانلود</string>\n    <string name=\"install_from_play_store\">نصب از فروشگاه Play</string>\n    <string name=\"install_from_fdroid\">نصب از F-Droid</string>\n    <string name=\"download\">دانلود</string>\n    <string name=\"ooc_subscription_token\">مشخصه OOCv1 API</string>\n    <string name=\"ooc_subscription_token_invalid\">مشخصه OOCv1 نادرست</string>\n    <string name=\"update_subscription_warning\">پروکسی متصل نیست، آیا مطمئن هستید که می خواهید به‌روزرسانی کنید؟</string>\n    <string name=\"ooc_warning\">هشدار</string>\n    <string name=\"ooc_missing_protocol\">اشتراک به پشتیبانی از پروتکل %s نیاز دارد، اما یافت نشد. پراکسی های پشتیبانی نشده نادیده گرفته می شوند.</string>\n    <string name=\"service_subscription\">سرویس به‌روزسانی اشتراک</string>\n    <string name=\"subscription_update\">به‌روزرسانی اشتراک</string>\n    <string name=\"subscription_update_message\">به‌روزرسانی %s …</string>\n    <string name=\"group_filter\">پالایش</string>\n    <string name=\"subscription_used\">%s استفاده‌شده</string>\n    <string name=\"subscription_traffic\">%s استفاده‌شده / %s باقی‌مانده</string>\n    <string name=\"subscription_expire\">انقضا: %s</string>\n    <string name=\"subscription_import\">وارد کردن اشتراک</string>\n    <string name=\"subscription_import_message\">تأیید می‌کنید که می‌خواهید اشتراک %s را وارد کنید؟ اگر از یک منبع نامعتبر وارد کنید، ممکن است منجر به لو رفتن IP شما شود.</string>\n    <string name=\"profile_import\">اضافه کردن پراکسی</string>\n    <string name=\"profile_import_message\">تأیید می‌کنید که می‌خواهید پراکسی %s اضافه شود؟</string>\n    <string name=\"clear_profiles_message\">آیا می‌خواهید پراکسی‌های این گروه را پاک کنید؟</string>\n    <string name=\"apps\">برنامه‌ها</string>\n    <string name=\"select_apps\">انتخاب برنامه‌های مورد نظر</string>\n    <string name=\"apps_message\">%d برنامه</string>\n    <string name=\"hysteria_obfs\">رمز دستورالعمل مبهم‌سازی</string>\n    <string name=\"hysteria_auth_type\">نوع احراز هویت</string>\n    <string name=\"hysteria_auth_payload\">بسته احرازهویت</string>\n    <string name=\"hysteria_upload_mbps\">حدأکثر سرعت ارسال (به مگابیت‌برثانیه)</string>\n    <string name=\"hysteria_download_mbps\">حدأکثر سرعت دریافتی (به مگابیت‌برثانیه)</string>\n    <string name=\"experimental_settings\">تنظیمات آزمایشی</string>\n    <string name=\"hysteria_stream_receive_window\">پنجره دریافت جریان QUIC</string>\n    <string name=\"hysteria_connection_receive_window\">پنجره دریافت اتصال QUIC</string>\n    <string name=\"hysteria_disable_mtu_discovery\">غیرفعال کردن Path MTU Discovery</string>\n    <string name=\"group_order\">ترتیب</string>\n    <string name=\"group_order_origin\">مبدأ</string>\n    <string name=\"group_order_by_name\">به ترتیب نام</string>\n    <string name=\"group_order_by_delay\">به میزان تأخیر</string>\n    <string name=\"plugin_exists_but_on_shit_system\">پراکسی %s به افزونه %s نیاز دارد، اما تأمین‌کننده تجهیزات اختصاصی شما(معمولاً سازمان‌های جاسوسی و سازنده بدافزار) اندروید شما را دستکاری کرده و افزونه را غیرقابل استفاده کرده است!</string>\n\n    <string name=\"shadowsocks_plugin_simple_obfs\">Obfs ساده (افزونه اندروید Shadowsocks)</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (افزونه Shadowsocks اندروید)</string>\n\n    <string name=\"traffic_uplink\" translatable=\"false\">%s | %s/s ↑</string>\n    <string name=\"traffic_downlink\" translatable=\"false\">%s | %s/s ↓</string>\n\n    <string name=\"traffic_uplink_total\" translatable=\"false\">%s ↑</string>\n    <string name=\"traffic_downlink_total\" translatable=\"false\">%s ↓</string>\n\n    <string name=\"tcp_connections\">%d اتصال TCP</string>\n    <string name=\"udp_connections\">%d اتصال UDP</string>\n\n    <string name=\"copy_label\">کپی کردن نام</string>\n    <string name=\"copy_package_name\">کپی کردن نام بسته</string>\n    <string name=\"open_app\">باز کردن برنامه</string>\n    <string name=\"open_settings\">باز کردن تنظیمات</string>\n    <string name=\"open_market\">باز کردن بازار</string>\n    <string name=\"create_rule\">افزودن دستورالعمل</string>\n\n    <string name=\"copy_success\">کپی موفق!</string>\n    <string name=\"copy_failed\">کپی ناموفق.</string>\n    <string name=\"app_no_launcher\">این برنامه، رابط کاربری ندارد.</string>\n    <string name=\"route_for\">دستورالعمل برای %s</string>\n\n    <string name=\"route_need_vpn\">دستورالعمل مسیریابی %s متکی به اجرای VPN است، بنابراین نادیده گرفته می‌شود.</string>\n\n    <string name=\"profile_traffic_statistics\">آمار ترافیک پراکسی</string>\n    <string name=\"profile_traffic_statistics_summary\">در صورت غیرفعال شدن، ترافیک استفاده شده پراکسی محاسبه نخواهد شد</string>\n    <string name=\"no_statistics\">هنوز بدون آمار</string>\n    <string name=\"app_statistics_disabled\">آمار ترافیک برنامه غیرفعال شد</string>\n    <string name=\"ssh_auth_type_none\">هیچ کدام</string>\n    <string name=\"ssh_public_key\">کلید عمومی</string>\n    <string name=\"ssh_private_key\">کلید خصوصی</string>\n    <string name=\"ssh_private_key_passphrase\">عبارات عبور کلید خصوصی</string>\n    <string name=\"translate_platform\">سکوی ترجمه</string>\n    <string name=\"menu_tools\">ابزارها</string>\n    <string name=\"menu_log\">آمار و اطلاعات</string>\n    <string name=\"clear_logcat\">پاک‌کردن اطلاعات Logcat</string>\n    <string name=\"wireguard_local_address\">نشانی محلی</string>\n    <string name=\"wireguard_public_key\">کلید عمومی همتا</string>\n    <string name=\"wireguard_psk\">کلید همتا از پیش مشترک</string>\n\n    <string name=\"cloudflare_wrap\" translatable=\"false\">Cloudflare Warp</string>\n    <string name=\"warp_license\">CloudFlare Warp یک ارائه دهنده رایگان WireGuard VPN است. با استفاده از آن، با شرایط استفاده، موافقت می کنید.</string>\n    <string name=\"warp_generate\">ایجاد پیکربندی</string>\n    <string name=\"generating\">در حال ایجاد...</string>\n    <string name=\"tun_implementation\">پیاده‌سازی TUN</string>\n    <string name=\"destination_override\">بازنویسی مقصد</string>\n    <string name=\"destination_override_summary\">از دامنه ردیابی‌شده برای بازنویسی آدرس مقصد استفاده کنید، نه فقط برای مسیریابی.</string>\n    <string name=\"resolve_destination\">بررسی مقصد</string>\n    <string name=\"resolve_destination_summary\">اگر آدرس مقصد یک دامنه باشد، بر اساس استراتژی IPv6 (تعارض با FakeDNS) از بین می‌رود.</string>\n    <string name=\"pcap\" translatable=\"false\">Pcap</string>\n    <string name=\"pcap_notice\">فایل‌های Pcap در %s ذخیره می‌شوند.</string>\n    <string name=\"naive_insecure_concurrency\">همزمانی ناامن</string>\n    <string name=\"naive_insecure_concurrency_summary\">از N اتصال تونل همزمان برای قوی‌تر بودن در شرایط بد شبکه استفاده کنید. اتصالات بیشتر، اتصال به تونل را آسان‌تر و ایمن‌تر می کند. این پروژه برای افزایش امنیت در برابر شنود ترافیک تلاش می‌کند. استفاده ناامن، هدف پروژه نیست. \\n\\nاگر می‌خواهید از این استفاده کنید، ابتدا N=2 را امتحان کنید تا ببینید آیا مشکلات شما حل می‌شود یا خیر. اکیداً توصیه می‌شود از بیش از 4 اتصال در اینجا استفاده نکنید.</string>\n\n    <string name=\"stun_test\">کشف رفتار NAT</string>\n    <string name=\"stun_test_summary\">تعیین رفتار NAT و رفتار فیلتر NAT تعریف‌شده در RFC 3478 با استفاده از STUN.</string>\n    <string name=\"start\">Start</string>\n    <string name=\"stun_attest_loading\">ممکن است چند دقیقه‌ای طول بکشد...</string>\n    <string name=\"nat_stun_server_hint\">سرور STUN</string>\n    <string name=\"nat_result_hint\">بررسی نتیجه NAT</string>\n    <string name=\"tools_network\">شبکه</string>\n    <string name=\"mtu\" translatable=\"false\">MTU</string>\n\n    <string name=\"backup\">پشتیبان‌گیری</string>\n    <string name=\"backup_groups_and_configurations\">گروه‌ها و پیکربندی‌ها</string>\n    <string name=\"backup_rules\">دستورالعمل‌های مسیریابی</string>\n    <string name=\"backup_settings\">تنظیمات</string>\n    <string name=\"backup_summary\">اگر از دستورالعمل‌های مسیریابی با پیکربندی‌ها، پشتیبان‌گیری نشود، خروجی‌های سفارشی از بین خواهند رفت.</string>\n    <string name=\"backup_not_file\">یک فایل پشتیبانی نیست.: انتظار می‌رود یک فایل .json باشد اما %s است.</string>\n    <string name=\"invalid_backup_file\">فایل پشتیبانی نادرست</string>\n    <string name=\"backup_import\">افزودن</string>\n    <string name=\"backup_import_summary\">افزودن فایل پشتیبانی، اطلاعات و تنظیمات کنونی را حذف و بازنویسی می‌کند.</string>\n    <string name=\"backup_importing\">افزودن...</string>\n\n    <string name=\"packet_encoding\">رمزگذاری بسته</string>\n    <string name=\"acquire_wake_lock\">به دست آوردن WakeLock</string>\n    <string name=\"release_wake_lock\">رها کردن WakeLock</string>\n    <string name=\"acquire_wake_lock_summary\">پردازنده را روشن نگه‌می‌دارد.</string>\n    <string name=\"action_switch\">سوییچ</string>\n\n    <string name=\"tuic_udp_relay_mode\">حالت بازپخش UDP</string>\n    <string name=\"tuic_congestion_controller\">کنترل‌کننده ازدحام</string>\n    <string name=\"tuic_disable_sni\">غیرفعال کردن SNI</string>\n    <string name=\"tuic_reduce_rtt\">فعال کردن ارتباط 0-RTT QUIC</string>\n\n    <string name=\"please_update\">نسخه (%s) برنامه شما بسیار قدیمی شده است و در %s متوقف می‌شود. لطفاً به نسخه جدیدتر به‌روزرسانی کنید</string>\n    <string name=\"please_update_force\">برنامه شما خیلی قدیمی شده است (%s). و در %s کار نمی‌کند. باید نسخه جدید را دانلود کنید!</string>\n    <string name=\"connection_test_delete_unavailable\">پاک‌کردن غیرقابل‌دسترس‌ها</string>\n    <string name=\"move\">حرکت</string>\n    <string name=\"exe_prefer_provider\">ارائه‌دهنده برگزیده افزونه‌ها</string>\n    <string name=\"create_shortcut\">ساخت میانبر</string>\n    <string name=\"app_tls_version\">نسخه TLS </string>\n    <string name=\"hop_interval\">مدت زمان Port hopping (به ثانیه)</string>\n    <string name=\"domain_strategy_for_remote\">راهبرد دامنه برای ارتباط از راه دور</string>\n    <string name=\"domain_strategy_for_direct\">راهبرد دامنه برای ارتباط مستقیم</string>\n    <string name=\"domain_strategy_for_server\">راهبرد دامنه برای نشانی‌های سرور</string>\n    <string name=\"show_bottom_bar\">نمایش نوار پایین مانند SagerNet</string>\n    <string name=\"utls_fingerprint\">اثر انگشت uTLS</string>\n    <string name=\"custom_outbound_json\">خروجی سفارشی JSON</string>\n    <string name=\"custom_config_json\">پراکسی سفارشی JSON</string>\n    <string name=\"is_outbound_only\">مجموعه JSON برای خروجی است</string>\n    <string name=\"save\">ذخیره</string>\n    <string name=\"set_panel_url\">تعیین لینک صفحه</string>\n    <string name=\"enable_clash_api\">فعال کردن Clash API</string>\n    <string name=\"log_level\">سطح ثبت آمار</string>\n    <string name=\"enable_clash_api_summary\">ارائه Clash api و صفحه yacd را در 127.0.0.1: 9090</string>\n    <string name=\"xtls_flow\">Flow (VLESS دستورالعمل فرعی)</string>\n    <string name=\"ads\">تبلیغات</string>\n    <string name=\"bypass_lan_in_core\">دورزدن شبکه محلی در هسته برنامه</string>\n    <string name=\"need_restart\">برای اجرای تغییرات، برنامه را دوباره راه‌اندازی کنید</string>\n    <string name=\"use_selector\">استفاده از انتخاب‌گر</string>\n    <string name=\"front_proxy\">پراکسی جلویی</string>\n    <string name=\"landing_proxy\" >پراکسی در حال فرود</string>\n    <string name=\"action_shadowtls\" translatable=\"false\">ShadowTLS</string>\n    <string name=\"protocol_version\">نسخه پروتکل</string>\n    <string name=\"share_subscription\">اشتراک‌گذاری گروه اشتراکی</string>\n    <string name=\"show_group_in_notification\">نمایش نام گروه در نوار اعلان</string>\n    <string name=\"reset_connections\">بازنشانی اتصالات</string>\n    <string name=\"remove_duplicate\">حذف سرورهای تکراری</string>\n    <string name=\"mtu_help\">برای تنظیم MTU سفارشی، تنظیمات را مدتی فشار دهید.</string>\n    <string name=\"log_level_help\">برای تنظیم اندازه بافر، تنظیمات را مدتی فشار دهید.</string>\n    <string name=\"tls_camouflage_settings\">تنظیمات استتار TLS</string>\n    <string name=\"test_concurrency\">آزمایش همزمان اتصال</string>\n    <string name=\"mux_type\">پروتکل مالتی‌پلکسر</string>\n    <string name=\"sniff_routing\">ردیابی نتایج برای مسیریابی</string>\n    <string name=\"sniff_override\">ردیابی نتایج برای مقصد</string>\n    <string name=\"resolve_server\">نشانی سرور را مطابق خط مشی IPv6 بررسی کنید</string>\n    <string name=\"auto_select_proxy_apps\">انتخاب خودکار برنامه‌های پراکسی</string>\n    <string name=\"auto_select_proxy_apps_message\">انتخاب خودکار برنامه‌های پراکسی، با این کار انتخاب‌های فعلی شما پاک می شود.</string>\n    <string name=\"enable_ech\">فعال کردن ECH</string>\n    <string name=\"enable_ech_sum\">فعال کردن ECH</string>\n    <string name=\"ech_settings\">تنظیمات ECH</string>\n    <string name=\"ech_config\">پیکربندی ECH</string>\n    <string name=\"http_upgrade_host\">میزبان HTTPUpgrade</string>\n    <string name=\"http_upgrade_path\">مسیر HTTPUpgrade</string>\n    <string name=\"update_current_subscription\">به‌روزرسانی اشتراک این گروه</string>\n    <string name=\"group_not_subscription\">نوع این گروه اشتراکی نیست</string>\n    <string name=\"allow_insecure_on_request_sum\">غیرفعال کردن بررسی گواهی‌ها هنگام به‌روزرسانی اشتراک‌ها</string>\n    <string name=\"global_allow_insecure\">به اتصال محافظت‌نشده همیشه اجازه داده شود.</string>\n    <string name=\"network_change_reset_connections\">بازنشانی اتصال خروجی، هنگام تغییر شبکه</string>\n    <string name=\"wake_reset_connections\">بازنشانی اتصال خروجی، هنگام روشن‌شدن صفحه موبایل</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-fr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"action_import_err\">Échec de l\\'importation.</string>\n    <string name=\"delete_confirm_prompt\">Êtes-vous sûr(e) de vouloir retirer ce profil \\?</string>\n    <string name=\"delete\">Retirer</string>\n    <string name=\"add_profile_methods_scan_qr_code\">Scannez le code QR</string>\n    <string name=\"share_qr_nfc\">Code QR</string>\n    <string name=\"deprecated\">Obsolète</string>\n    <string name=\"insecure\">Non sûr</string>\n    <string name=\"donate\">Faire un don</string>\n    <string name=\"plugin\">Greffon</string>\n    <string name=\"ignore_battery_optimizations_sum\">Retirer certaines restrictions</string>\n    <string name=\"ignore_battery_optimizations\">Ignorer l\\'optimisation de la batterie</string>\n    <string name=\"donate_info\">J\\'adore l\\'argent</string>\n    <string name=\"apply\">Appliquer</string>\n    <string name=\"no\">Non</string>\n    <string name=\"yes\">Oui</string>\n    <string name=\"license\">Licence</string>\n    <string name=\"follow_system\">Selon le système</string>\n    <string name=\"night_mode\">Mode nuit</string>\n    <string name=\"lines\">%d lignes</string>\n    <string name=\"random\">Aléatoire</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"enable_log_sum\">Pour le débogage</string>\n    <string name=\"enable_log\">Activer la journalisation</string>\n    <string name=\"delete_group_prompt\">Êtes-vous sûr(e) de vouloir supprimer ce groupe \\?</string>\n    <string name=\"subscription\">Abonnement</string>\n    <string name=\"profile_requiring_plugin\">%s nécessite l\\'installation du greffon %s, mais il n\\'a pas été trouvé.</string>\n    <string name=\"missing_plugin\">Greffon manquant</string>\n    <string name=\"confirm\">Confirmer</string>\n    <string name=\"action_download\">TÉLÉCHARGER</string>\n    <string name=\"action_learn_more\">EN SAVOIR PLUS</string>\n    <string name=\"download\">Télécharger</string>\n    <string name=\"apps_message\">%d applications</string>\n    <string name=\"select_apps\">Sélectionnez les applications</string>\n    <string name=\"apps\">Applications</string>\n    <string name=\"clear_profiles_message\">Êtes-vous sûr(e) de vouloir supprimer ce groupe \\?</string>\n    <string name=\"profile_import_message\">Confirmez-vous que vous voulez importer le profil %s \\?</string>\n    <string name=\"general_settings\">Paramètres de l\\'appli</string>\n    <string name=\"cag_route\">Paramètres d\\'itinéraire</string>\n    <string name=\"service_mode\">Mode service</string>\n    <string name=\"group_delete_confirm_prompt\">Êtes-vous sûr(e) de vouloir supprimer ce groupe \\?</string>\n    <string name=\"group_diff\">Diff (%s)</string>\n    <string name=\"group_show_diff\">Diff</string>\n    <string name=\"group_updated\">%s : %d proxys mis à jour</string>\n    <string name=\"group_no_difference\">%s : aucune différence</string>\n    <string name=\"group_status_proxies_subscription\">%d proxys | Mis à jour le %s</string>\n    <string name=\"group_status_proxies\">%d proxys</string>\n    <string name=\"group_status_empty_subscription\">Pas encore mis à jour</string>\n    <string name=\"group_status_empty\">Groupe vide</string>\n    <string name=\"group_edit_subscription\">Modifier l\\'abonnement</string>\n    <string name=\"group_edit\">Modifier le groupe</string>\n    <string name=\"group_create_subscription\">Créer un abonnement</string>\n    <string name=\"group_create\">Créer un groupe</string>\n    <string name=\"group_name_required\">Nom de groupe requis</string>\n    <string name=\"group_subscription_link\">Lien d’abonnement</string>\n    <string name=\"group_update\">Mettre à jour</string>\n    <string name=\"group_name\">Nom du groupe</string>\n    <string name=\"quick_toggle\">Basculer</string>\n    <string name=\"quick_enable\">Activer</string>\n    <string name=\"quick_disable\">Désactiver</string>\n    <string name=\"group_default\">Dégroupé</string>\n    <string name=\"document\">Document</string>\n    <string name=\"theme\">Thème</string>\n    <string name=\"menu_about\">À propos</string>\n    <string name=\"menu_group\">Groupe</string>\n    <string name=\"menu_configuration\">Configuration</string>\n    <string name=\"logcat\">Exporter les informations de débogage</string>\n    <string name=\"version_x\">Version (%s)</string>\n    <string name=\"app_version\">Version</string>\n    <string name=\"oss_licenses\">Licences à code source ouvert</string>\n    <string name=\"telegram\">Canal de mise à jour Telegram</string>\n    <string name=\"github\">Code source</string>\n    <string name=\"project\">Projet</string>\n    <string name=\"app_desc\">La chaîne d\\'outils proxy universelle pour Android, écrite en Kotlin.</string>\n    <string name=\"hysteria_upload_mbps\">Vitesse maximale de téléversement (en Mb/s)</string>\n    <string name=\"hysteria_download_mbps\">Vitesse de téléchargement maximale (en Mb/s)</string>\n    <string name=\"group_deleted\">Supprimé :\n\\n%s</string>\n    <string name=\"group_changed\">Mis à jour :\n\\n%s</string>\n    <string name=\"group_added\">Ajouté :\n\\n%s</string>\n    <string name=\"tile_title\">Commutateur</string>\n    <string name=\"service_mode_proxy\">Agent seulement</string>\n    <string name=\"group_duplicate\">Reproduction:\n\\n%s</string>\n    <string name=\"connection_test_clear_results\">Effacer les résultats des tests</string>\n    <string name=\"action_export_err\">Échec de l\\'export.</string>\n    <string name=\"ssh_private_key\">Clé privée</string>\n    <string name=\"port_transproxy\">Port du Transproxy</string>\n    <string name=\"cag_ws\">Paramètres WebSocket</string>\n    <string name=\"security_settings\">Paramètres de sécurité</string>\n    <string name=\"menu_tools\">Outils</string>\n    <string name=\"allow_insecure\">Autoriser non sécurisé</string>\n    <string name=\"menu_log\">Journaux</string>\n    <string name=\"experimental_settings\">Expérimental</string>\n    <string name=\"error_title\">Erreur</string>\n    <string name=\"plugin_unknown\">Greffon %s inconnu</string>\n    <string name=\"clear_traffic_statistics\">Effacer les statistiques du trafic</string>\n    <string name=\"ws_browser_forwarding\">Transfert de Navigateur</string>\n    <string name=\"group_filter\">Filtre</string>\n    <string name=\"clear_profiles\">Effacer</string>\n    <string name=\"always_show_address\">Toujours afficher l\\'adresse</string>\n    <string name=\"undo\">Annuler</string>\n    <string name=\"enable\">Activer</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"unavailable\">Non disponible</string>\n    <string name=\"group_basic\">Basique</string>\n    <string name=\"raw\">Brut</string>\n    <string name=\"remote_dns\">DNS distant</string>\n    <string name=\"route_profile\">Sélectionner un profil…</string>\n    <string name=\"config_type\">Type de configuration</string>\n    <string name=\"extra_headers\">Entêtes supplémentaires</string>\n    <string name=\"balancer_type\">Type</string>\n    <string name=\"action_import\">Importer depuis le presse-papiers</string>\n    <string name=\"auto\">Auto</string>\n    <string name=\"balancer_strategy\">Stratégie</string>\n    <string name=\"connection_test_url\">URL de test de connexion</string>\n    <string name=\"off\">Inactif</string>\n    <string name=\"route_block\">Bloquer</string>\n    <string name=\"action_export\">Exporter</string>\n    <string name=\"route_asset_status\">Version locale : %s</string>\n    <string name=\"plugin_disabled\">Désactivé</string>\n    <string name=\"connection_test_testing\">Essai…</string>\n    <string name=\"action_export_clipboard\">Exporter dans le presse-papiers</string>\n    <string name=\"dns_routing_message\">Résoler les domaines dans les routes de contournement avec DNS Direct. Soyez conscient des fuites DNS potentielles</string>\n    <string name=\"on\">Actif</string>\n    <string name=\"menu_traffic\">Trafic</string>\n    <string name=\"connection_test_error\">Échec : %s</string>\n    <string name=\"menu_route\">Route</string>\n    <string name=\"cag_dns\">Paramètres DNS</string>\n    <string name=\"protocol\">Protocole</string>\n    <string name=\"port_proxy\">Port du Proxy SOCKS5</string>\n    <string name=\"connection_test_error_status_code\">Code d\\'erreur : #%d</string>\n    <string name=\"route_asset_updated\">Mis à jour</string>\n    <string name=\"action_export_file\">Exporter dans un fichier</string>\n    <string name=\"connection_test_available\">Succès : la négociation HTTPS a pris %dms</string>\n    <string name=\"require_transproxy\">Activer Transproxy entrant</string>\n    <string name=\"server_port\">Port distant</string>\n    <string name=\"show_direct_speed\">Afficher la vitesse directe</string>\n    <string name=\"username_opt\">Nom d\\'utilisateur (facultatif)</string>\n    <string name=\"encryption\">Cryptage</string>\n    <string name=\"stop\">Arrêter</string>\n    <string name=\"speed_detail\">Proxy： %1$s↑ %2$s↓\n\\nDirecte： %3$s↑ %4$s↓</string>\n    <string name=\"subscriptions\">Souscriptions</string>\n    <string name=\"cag_misc\">Paramètres divers</string>\n    <string name=\"enable_dns_routing\">Activer le routage DNS</string>\n    <string name=\"port_http\">Port du Proxy HTTP</string>\n    <string name=\"invalid_server\">Nom de serveur invalide</string>\n    <string name=\"ws_browser_forwarding_sum\">Transférer les WebSockets correspondants via le navigateur.</string>\n    <string name=\"require_http\">Activer HTTP Entrant</string>\n    <string name=\"show_direct_speed_sum\">Afficher également la vitesse du trafic sans proxy dans la notification</string>\n    <string name=\"allow_access_sum\">Lier les serveurs entrants à 0.0.0.0</string>\n    <string name=\"connection_test_available_http\">Succès : la négociation HTTP a pris %dms</string>\n    <string name=\"dns_hosts\">Réécriture de Domaine</string>\n    <string name=\"password\">Mot de passe</string>\n    <string name=\"connection_test_domain_not_found\">Domaine non trouvé</string>\n    <string name=\"allow_access\">Autoriser les Connexions depuis le réseau local</string>\n    <string name=\"inbound_settings\">Paramètres Entrants</string>\n    <string name=\"speed_interval\">Intervalle de mise à jour des notifications de vitesse</string>\n    <string name=\"show_stop\">Afficher le bouton d\\'arrêt</string>\n    <string name=\"show_stop_sum\">Si vous ne souhaitez pas utiliser Quick Tile comme commutateur</string>\n    <string name=\"allow_insecure_sum\">Désactivez la vérification des certificats. Lorsque cette option est activée, cette configuration est aussi sécurisée que le texte en clair</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_fail\">Internet non disponible</string>\n    <string name=\"transproxy_mode\">Mode Transproxy</string>\n    <string name=\"connection_test_unreachable\">Non accessible</string>\n    <string name=\"action_import_file\">Importer depuis un fichier</string>\n    <string name=\"port_local_dns\">Port du DNS local</string>\n    <string name=\"profile_name\">Nom de profil</string>\n    <string name=\"server_address\">Serveur</string>\n    <string name=\"key_opt\">Clé (facultatif)</string>\n    <string name=\"enc_method\">Méthode de chiffrement</string>\n    <string name=\"http_host\">Hôte HTTP</string>\n    <string name=\"http_path\">Chemin HTTP</string>\n    <string name=\"tls\">Utiliser TLS</string>\n    <string name=\"certificates\">Certificats</string>\n    <string name=\"edit_config\">Modifier la configuration</string>\n    <string name=\"ipv6\">Route IPv6</string>\n    <string name=\"prefer\">Préférer</string>\n    <string name=\"route_proxy\">Proxy</string>\n    <string name=\"route_add\">Créer une route</string>\n    <string name=\"route_name\">Nom de la route</string>\n    <string name=\"empty_route\">Route vide</string>\n    <string name=\"search_apps\">Rechercher…</string>\n    <string name=\"show_system_apps\">Afficher les applications système</string>\n    <string name=\"service_failed\">Échec :</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"clipboard_empty\">Le presse-papiers est vide</string>\n    <string name=\"connect\">Connecter</string>\n    <string name=\"settings\">Préférences</string>\n    <string name=\"edit\">Modifier</string>\n    <string name=\"share\">Partager</string>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">%d élément supprimé</item>\n        <item quantity=\"other\">%d éléments supprimés</item>\n    </plurals>\n    <string name=\"ooc_warning\">Notice</string>\n    <string name=\"install_from_fdroid\">Installer depuis F-Droid</string>\n    <string name=\"subscription_update_message\">Mise à jour de %s…</string>\n    <string name=\"group_order\">Ordre</string>\n    <string name=\"group_order_origin\">Origine</string>\n    <string name=\"tcp_connections\">%d connexions TCP</string>\n    <string name=\"udp_connections\">%d connexions UDP</string>\n    <string name=\"copy_package_name\">Copier le nom du paquet</string>\n    <string name=\"route_for\">Règle pour %s</string>\n    <string name=\"no_statistics\">Aucune statistique pour l\\'instant</string>\n    <string name=\"ssh_public_key\">Clé publique</string>\n    <string name=\"generating\">Génération…</string>\n    <string name=\"route_reset\">Rétablir</string>\n    <string name=\"ssh_auth_type_none\">Aucun</string>\n    <string name=\"direct_dns\">DNS Direct</string>\n    <string name=\"username\">Nom d\\'utilisateur</string>\n    <string name=\"network\">Réseau</string>\n    <string name=\"disable\">Désactiver</string>\n    <string name=\"route_asset_no_update\">Aucune mise à jour</string>\n    <string name=\"password_opt\">Mot de passe (facultatif)</string>\n    <string name=\"only\">Seulement</string>\n    <string name=\"route_rules_official\">Officiel</string>\n    <string name=\"connecting\">Connexion…</string>\n    <string name=\"plugin_configure\">Configurer…</string>\n    <string name=\"ss_cat\">Préférences Shadowsocks</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (greffon Android pour Shadowsocks)</string>\n    <string name=\"group_order_by_name\">Par nom</string>\n    <string name=\"wireguard_local_address\">Adresse locale</string>\n    <string name=\"proxy_cat\">Préférences du serveur</string>\n    <string name=\"need_reload\">Recharger le service de proxy pour appliquer les modifications</string>\n    <string name=\"append_http_proxy\">Ajouter un proxy HTTP au VPN</string>\n    <string name=\"subscription_settings\">Préférences de souscription</string>\n    <string name=\"group_type\">Type du groupe</string>\n    <string name=\"subscription_type\">Type de souscription</string>\n    <string name=\"install_from_play_store\">Installer depuis le Play Store</string>\n    <string name=\"subscription_used\">%s utilisé</string>\n    <string name=\"group_order_by_delay\">Par délai</string>\n    <string name=\"route_bypass_ip\">Règle IP pour %s</string>\n    <string name=\"config_settings\">Préférences de la configuration</string>\n    <string name=\"action_from_link\">Depuis la souscription</string>\n    <string name=\"custom_config\">Configuration personnalisée</string>\n    <string name=\"connection_test_refused\">Connexion refusée</string>\n    <string name=\"open_app\">Ouvrir l\\'appli</string>\n    <string name=\"open_settings\">Ouvrir les préférences</string>\n    <string name=\"header_type\">Type d\\'entête</string>\n    <string name=\"group_settings\">Préférences de groupe</string>\n    <string name=\"protocol_settings\">Préférences du protocole</string>\n    <string name=\"route_bypass_domain\">Règle de domaine pour %s</string>\n    <string name=\"no_proxies_found_in_subscription\">Aucun proxy trouvé dans cette souscription</string>\n    <string name=\"profile_empty\">Veuillez sélectionner un profil</string>\n    <string name=\"no_proxies_found_in_file\">Aucun proxy trouvé dans ce fichier</string>\n    <string name=\"profile_config\">Configuration du profil</string>\n    <string name=\"translate_platform\">Plateforme de traduction</string>\n    <string name=\"uuid\">ID de l\\'utilisateur</string>\n    <string name=\"ws_path\">Chemin du WebSocket</string>\n    <string name=\"select_profile\">Sélectionner un profil</string>\n    <string name=\"update_settings\">Mettre à jour les préférences</string>\n    <string name=\"copy_label\">Copier le nom</string>\n    <string name=\"create_rule\">Créer une règle</string>\n    <string name=\"copy_success\">Copie avec succès !</string>\n    <string name=\"copy_failed\">Échec de la copie.</string>\n    <string name=\"app_no_launcher\">L\\'appli n\\'a pas d\\'interface.</string>\n    <string name=\"destination_override\">Écraser la destination</string>\n    <string name=\"resolve_destination\">Résoudre la destination</string>\n    <string name=\"add_profile_methods_manual_settings\">Préférences manuels</string>\n    <string name=\"backup\">Sauvegarde</string>\n    <string name=\"backup_settings\">Préférences</string>\n    <string name=\"backup_import\">Importer</string>\n    <string name=\"packet_encoding\">Codage du paquet</string>\n    <string name=\"auto_connect\">Connexion auto</string>\n    <string name=\"api_port\">Port de l\\'API</string>\n    <string name=\"hysteria_auth_type\">Type d\\'authentification</string>\n    <string name=\"ws_host\">Hôte WebSocket</string>\n    <string name=\"invert_selections\">Inverser la sélection</string>\n    <string name=\"clear_selections\">Effacer la sélection</string>\n    <string name=\"service_vpn\">Service VPN</string>\n    <string name=\"stopping\">En train de s\\'éteindre…</string>\n    <string name=\"add_profile\">Ajouter un profile</string>\n    <string name=\"circular_reference\">Référence circulaire</string>\n    <string name=\"action_create_group\">Groupe vide</string>\n    <string name=\"action_export_msg\">Export avec succès !</string>\n    <string name=\"action_import_msg\">Importation avec succès !</string>\n    <string name=\"vpn_connected\">Connecté, tapez pour vérifier la connexion</string>\n    <string name=\"cleartext_http_warning\">Le trafic HTTP en clair n\\'est pas sûr</string>\n    <string name=\"no_proxies_found\">Aucun proxy trouvé dans le lien</string>\n    <string name=\"no_proxies_found_in_clipboard\">Aucun proxy trouve dans le presse-papiers</string>\n    <string name=\"not_connected\">Non connecté</string>\n    <string name=\"connection_test\">Test de connexion</string>\n    <string name=\"trojan_provider\">Fournisseur de torjan</string>\n    <string name=\"ooc_subscription_token_invalid\">Jeton OOCv1 invalide</string>\n    <string name=\"update_when_connected_only\">Mettre à jour que quand connecté</string>\n    <string name=\"auto_update_delay\">Délai de mise à jour automatique (en minutes)</string>\n    <string name=\"auto_update\">Mise à jour auto</string>\n    <string name=\"subscription_traffic\">%s utilisé(s) / %s restant(s)</string>\n    <string name=\"subscription_import\">Importer une souscription</string>\n    <string name=\"profile_import\">Importer un profil</string>\n    <string name=\"warp_generate\">Générer une configuration</string>\n    <string name=\"stun_attest_loading\">Cela peut prendre quelques minutes…</string>\n    <string name=\"invalid_backup_file\">Fichier de sauvegarde invalide</string>\n    <string name=\"backup_import_summary\">L\\'importation écrasera les données existantes.</string>\n    <string name=\"tools_network\">Réseaux</string>\n    <string name=\"backup_importing\">Importation…</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-in/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"port_http\">Proksi Port HTTP</string>\n    <string name=\"port_proxy\">Proksi Port SOCKS5</string>\n    <string name=\"group_status_proxies_subscription\">%d Proksi | Diperbarui pada %s</string>\n    <string name=\"group_status_proxies\">%d Proksi</string>\n    <string name=\"protocol_param\">Param Protokol</string>\n    <string name=\"protocol\">Protokol</string>\n    <string name=\"enc_method\">Metode Enkrip</string>\n    <string name=\"password\">Kata sandi</string>\n    <string name=\"key_opt\">Kunci (Opsional)</string>\n    <string name=\"password_opt\">Kata sandi (Opsional)</string>\n    <string name=\"username_opt\">Nama pengguna (Opsional)</string>\n    <string name=\"username\">Nama pengguna</string>\n    <string name=\"server_port\">Remot Port</string>\n    <string name=\"server_address\">Server</string>\n    <string name=\"profile_name\">Nama Profil</string>\n    <string name=\"connection_test_url\">URL Tes Koneksi</string>\n    <string name=\"transproxy_mode\">Mode Transproksi</string>\n    <string name=\"require_transproxy\">Aktifkan Inbound Transproksi</string>\n    <string name=\"port_local_dns\">Port DNS lokal</string>\n    <string name=\"dns_hosts\">Tulis ulang DNS</string>\n    <string name=\"fakedns_message\">Dapat menyebabkan aplikasi lain harus dimulai ulang untuk menyambung kembali ke jaringan setelah proksi dihentikan</string>\n    <string name=\"enable_fakedns\">Aktifkan FakeDNS</string>\n    <string name=\"dns_routing_message\">Selesaikan domain di rute bypass dengan Direct DNS. Waspadai potensi kebocoran DNS</string>\n    <string name=\"enable_dns_routing\">Aktifkan DNS Routing</string>\n    <string name=\"direct_dns\">Direk DNS</string>\n    <string name=\"remote_dns\">Remot DNS</string>\n    <string name=\"cag_dns\">Pengaturan DNS</string>\n    <string name=\"connection_test_error_status_code\">Kode galat: #%d</string>\n    <string name=\"connection_test_fail\">Internet tidak tersedia</string>\n    <string name=\"connection_test_error\">Gagal: %s</string>\n    <string name=\"connection_test_available_http\">Sukses: handshake HTTP butuh %dms</string>\n    <string name=\"connection_test_available\">Sukses: handshake HTTPS butuh %dms</string>\n    <string name=\"connection_test_testing\">Mengetes…</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"speed_detail\">Proksi： %1$s↑ %2$s↓\n\\nDirek： %3$s↑ %4$s↓</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"allow_insecure_sum\">Nonaktifkan pengecekan sertifikat. Saat diaktifkan, konfigurasi ini seaman teks biasa</string>\n    <string name=\"allow_insecure\">Izinkan Koneksi Tidak Aman</string>\n    <string name=\"security_settings\">Pengaturan Keamanan</string>\n    <string name=\"show_direct_speed_sum\">Tampilkan lalu lintas kecepatan koneksi tanpa proksi di notifikasi</string>\n    <string name=\"show_direct_speed\">Tampilkan Kecepatan Direk</string>\n    <string name=\"show_stop_sum\">Jika Anda tidak ingin menggunakan Tombol Ubin sebagai pengalih</string>\n    <string name=\"show_stop\">Tampilkan Tombol Henti</string>\n    <string name=\"cag_misc\">Pengaturan Lainnya</string>\n    <string name=\"speed_interval\">Interval Pembaruan Notifikasi Kecepatan</string>\n    <string name=\"ws_browser_forwarding_sum\">Teruskan WebSocket yang sesuai melalui browser.</string>\n    <string name=\"ws_browser_forwarding\">Penerusan Peramban</string>\n    <string name=\"cag_ws\">Pengaturan WebSocket</string>\n    <string name=\"require_http\">Aktifkan HTTP inbound</string>\n    <string name=\"general_settings\">Pengaturan Aplikasi</string>\n    <string name=\"inbound_settings\">Pengaturan Inbound</string>\n    <string name=\"allow_access_sum\">Ikat server masuk ke 0.0.0.0</string>\n    <string name=\"allow_access\">Izinkan koneksi dari LAN</string>\n    <string name=\"cag_route\">Pengaturan Rute</string>\n    <string name=\"port_transproxy\">Port Transproxy</string>\n    <string name=\"service_mode_proxy\">Hanya proxy</string>\n    <string name=\"service_mode\">Mode Layanan</string>\n    <string name=\"group_duplicate\">Duplikasi:\n\\n%s</string>\n    <string name=\"group_deleted\">Dihapus:\n\\n%s</string>\n    <string name=\"group_changed\">Diperbarui:\n\\n%s</string>\n    <string name=\"group_added\">Ditambahkan:\n\\n%s</string>\n    <string name=\"group_delete_confirm_prompt\">Anda yakin ingin menghapus grup ini\\?</string>\n    <string name=\"group_diff\">Diff (%s)</string>\n    <string name=\"group_show_diff\">Diff</string>\n    <string name=\"group_updated\">%s: Diperbarui %d proxy</string>\n    <string name=\"group_no_difference\">%s: Tidak ada perbedaan</string>\n    <string name=\"group_status_empty\">Grup kosong</string>\n    <string name=\"group_status_empty_subscription\">Belum diperbarui</string>\n    <string name=\"group_edit_subscription\">Ubah langganan</string>\n    <string name=\"group_edit\">Ubah grup</string>\n    <string name=\"group_create_subscription\">Buat langganan</string>\n    <string name=\"group_create\">Buat grup</string>\n    <string name=\"group_name_required\">Nama grup diperlukan</string>\n    <string name=\"group_subscription_link\">Tautan Langganan</string>\n    <string name=\"group_update\">Perbarui</string>\n    <string name=\"group_name\">Nama grup</string>\n    <string name=\"tile_title\">Pengalih</string>\n    <string name=\"quick_toggle\">Beralih</string>\n    <string name=\"quick_enable\">Aktifkan</string>\n    <string name=\"quick_disable\">Nonaktifkan</string>\n    <string name=\"group_default\">Tidak bergrup</string>\n    <string name=\"document\">Dokumen</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"menu_about\">Tentang</string>\n    <string name=\"menu_group\">Grup</string>\n    <string name=\"menu_configuration\">Konfigurasi</string>\n    <string name=\"logcat\">Ekspor informasi debug</string>\n    <string name=\"version_x\">Versi (%s)</string>\n    <string name=\"app_version\">Versi</string>\n    <string name=\"oss_licenses\">Lisensi sumber terbuka</string>\n    <string name=\"telegram\">Channel Telegram</string>\n    <string name=\"github\">Kode sumber</string>\n    <string name=\"project\">Proyek</string>\n    <string name=\"app_desc\">Peranti proxy universal untuk Android, ditulis dalam Kotlin.</string>\n    <string name=\"ssh_auth_type_none\">Kosong</string>\n    <string name=\"no_statistics\">Belum ada statistik</string>\n    <string name=\"menu_tools\">Alat</string>\n    <string name=\"ssh_private_key\">Kunci Privat</string>\n    <string name=\"ssh_public_key\">Kunci Publik</string>\n    <string name=\"profile_empty\">Silakan pilih profil</string>\n    <string name=\"auto_connect\">Sambungkan Otomatis</string>\n    <string name=\"encryption\">Enkripsi</string>\n    <string name=\"select_profile\">Pilih Profil</string>\n    <string name=\"add_profile\">Tambah Profil</string>\n    <string name=\"share\">Bagikan</string>\n    <string name=\"settings\">Pengaturan</string>\n    <string name=\"clipboard_empty\">Papan klip kosong</string>\n    <string name=\"connect\">Sambungkan</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"stopping\">Mematikan…</string>\n    <string name=\"stop\">Berhenti</string>\n    <string name=\"service_failed\">Gagal:</string>\n    <string name=\"invalid_server\">Nama server tidak valid</string>\n    <string name=\"forward_success\">Proksi menyala.</string>\n    <string name=\"service_proxy\">Layanan Proksi</string>\n    <string name=\"service_vpn\">Layanan VPN</string>\n    <string name=\"scanning\">Memindai…</string>\n    <string name=\"search_apps\">Cari…</string>\n    <string name=\"route_name\">Nama rute</string>\n    <string name=\"route_manage_assets\">Atur rute aset</string>\n    <string name=\"route_assets\">Aset</string>\n    <string name=\"enable_mux\">Aktifkan Multiplexer</string>\n    <string name=\"traffic_sniffing\">Aktifkan Traffic Sniffing</string>\n    <string name=\"off\">Mati</string>\n    <string name=\"on\">Nyala</string>\n    <string name=\"route_opt_block_ads\">Blokir ADs</string>\n    <string name=\"route_asset_updated\">Diperbarui</string>\n    <string name=\"route_asset_no_update\">Tidak ada pembaruan</string>\n    <string name=\"route_asset_status\">Versi lokal: %s</string>\n    <string name=\"route_rules_official\">Resmi</string>\n    <string name=\"route_reset\">Setel ulang</string>\n    <string name=\"empty_route\">Rute Kosong</string>\n    <string name=\"route_profile\">Pilih Profil…</string>\n    <string name=\"route_block\">Blokir</string>\n    <string name=\"route_bypass\">Bypass</string>\n    <string name=\"route_proxy\">Proksi</string>\n    <string name=\"only\">Hanya</string>\n    <string name=\"prefer\">Lebih menyukai</string>\n    <string name=\"ipv6\">Rute IPv6</string>\n    <string name=\"edit_config\">Sunting Konfigurasi</string>\n    <string name=\"certificates\">Sertifikat</string>\n    <string name=\"tls\">Pakai TLS</string>\n    <string name=\"network\">Jaringan</string>\n    <string name=\"uuid\">ID Pengguna</string>\n    <string name=\"obfs_param\">Obfs Param</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"menu_traffic\">Trafik</string>\n    <string name=\"circular_reference\">Referensi melingkar</string>\n    <string name=\"config_settings\">Pengaturan Konfigurasi</string>\n    <string name=\"chain_settings\">Pengaturan Rantai</string>\n    <string name=\"balancer_strategy\">Strategi</string>\n    <string name=\"balancer_type\">Jenis</string>\n    <string name=\"balancer_settings\">Pengaturan Penyeimbang</string>\n    <string name=\"balancer\">Pengimbang</string>\n    <string name=\"custom_config\">Konfigurasi Kustom</string>\n    <string name=\"proxy_chain\">Rantai Proksi</string>\n    <string name=\"edit\">Sunting</string>\n    <string name=\"reboot_required\">Gagal memulai layanan VPN. Anda mungkin perlu me-reboot perangkat Anda.</string>\n    <string name=\"vpn_permission_denied\">Izin ditolak untuk membuat layanan VPN</string>\n    <string name=\"direct_boot_aware_summary\">Informasi profil yang Anda pilih akan kurang terlindungi</string>\n    <string name=\"direct_boot_aware\">Izinkan Beralih di Layar Kunci</string>\n    <string name=\"auto_connect_summary\">Aktifkan proxy pada pembaruan startup/aplikasi jika sudah berjalan sebelumnya</string>\n    <string name=\"show_system_apps\">Tampilkan aplikasi sistem</string>\n    <string name=\"bypass_apps\">Jalan pintas</string>\n    <string name=\"clear_selections\">Hapus pilihan</string>\n    <string name=\"invert_selections\">Balikkan pilihan</string>\n    <string name=\"proxied_apps_summary\">Konfigurasikan mode VPN untuk aplikasi yang dipilih</string>\n    <string name=\"proxied_apps\">Modus aplikasi VPN</string>\n    <string name=\"tcp_keep_alive_interval\">TCP menjaga interval pengiriman paket aktif</string>\n    <string name=\"mux_concurrency\">Koneksi Mux Concurrent</string>\n    <string name=\"domain_strategy\">Strategi Resolusi Domain</string>\n    <string name=\"route_opt_bypass_lan\">Lewati LAN</string>\n    <string name=\"route_not_asset\">Bukan file aset: kecuali .db, tapi %s</string>\n    <string name=\"route_rules_provider\">Penyedia Aset Aturan</string>\n    <string name=\"route_bypass_domain\">Aturan domain untuk %s</string>\n    <string name=\"empty_route_notice\">Tetapkan beberapa aturan sebelum menyimpan</string>\n    <string name=\"delete_route_prompt\">Apakah anda yakin ingin menghapus rute ini\\?</string>\n    <string name=\"route_add\">Buat Rute</string>\n    <string name=\"menu_route\">Rute</string>\n    <string name=\"metered_summary\">Sistem petunjuk untuk memperlakukan VPN sebagai terukur</string>\n    <string name=\"metered\">Petunjuk Terukur</string>\n    <string name=\"config_type\">Jenis Konfigurasi</string>\n    <string name=\"extra_headers\">Header Ekstra</string>\n    <string name=\"early_data_header_name\">Nama Header Data Awal</string>\n    <string name=\"alpn\">Negosiasi Protokol Lapisan Aplikasi</string>\n    <string name=\"sni\">Indikasi Nama Server</string>\n    <string name=\"grpc_service_name\">Nama Layanan gRPC</string>\n    <string name=\"http_path\">Jalur HTTP</string>\n    <string name=\"http_host\">HTTP Host</string>\n    <string name=\"ws_path\">Jalur Soket Web</string>\n    <string name=\"ws_host\">Host Soket Web</string>\n    <string name=\"header_type\">Jenis Tajuk</string>\n    <string name=\"security\">Enkripsi lapisan transportasi</string>\n    <string name=\"alter_id\">Ubah ID</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-it/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"logcat\">Esporta informazioni di debug</string>\n    <string name=\"version_x\">Versione (%s)</string>\n    <string name=\"app_version\">Versione</string>\n    <string name=\"oss_licenses\">Licenze open source</string>\n    <string name=\"telegram\">Canale di aggiornamento di Telegram</string>\n    <string name=\"github\">Codice sorgente</string>\n    <string name=\"project\">Progetto</string>\n    <string name=\"app_desc\">La toolchain proxy universale per Android, scritta in Kotlin.</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ja/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"allow_access_sum\">インバウンドサーバーを0.0.0.0にバインドします</string>\n    <string name=\"allow_access\">LANからの接続を許可する</string>\n    <string name=\"cag_route\">ルート設定</string>\n    <string name=\"port_transproxy\">トランスプロキシポート</string>\n    <string name=\"port_http\">HTTPプロキシポート</string>\n    <string name=\"port_proxy\">プロキシポート</string>\n    <string name=\"service_mode_proxy\">プロキシのみ</string>\n    <string name=\"service_mode\">サービスモード</string>\n    <string name=\"group_duplicate\">複製：\n\\n%s</string>\n    <string name=\"group_deleted\">削除しました：\n\\n%s</string>\n    <string name=\"group_changed\">更新しました：\n\\n%s</string>\n    <string name=\"group_added\">追加した：\n\\n%s</string>\n    <string name=\"group_delete_confirm_prompt\">このグループを削除してもよろしいですか？</string>\n    <string name=\"group_diff\">差分 (%s)</string>\n    <string name=\"group_show_diff\">差分</string>\n    <string name=\"group_updated\">%s：更新された %d プロキシ</string>\n    <string name=\"group_no_difference\">%s：違いなし</string>\n    <string name=\"group_status_proxies_subscription\">%d プロキシ| %s で更新</string>\n    <string name=\"group_status_proxies\">%d プロキシ</string>\n    <string name=\"group_status_empty_subscription\">まだ更新されていません</string>\n    <string name=\"group_status_empty\">空のグループ</string>\n    <string name=\"group_edit_subscription\">サブスクリプションの編集</string>\n    <string name=\"group_edit\">グループを編集</string>\n    <string name=\"group_create_subscription\">サブスクリプションを作成する</string>\n    <string name=\"group_create\">グループを作成します</string>\n    <string name=\"group_name_required\">グループ名が必要です</string>\n    <string name=\"group_subscription_link\">サブスクリプションリンク</string>\n    <string name=\"group_update\">アップデート</string>\n    <string name=\"group_name\">グループ名</string>\n    <string name=\"tile_title\">スイッチャー</string>\n    <string name=\"quick_toggle\">トグル</string>\n    <string name=\"quick_enable\">有効化</string>\n    <string name=\"quick_disable\">無効化</string>\n    <string name=\"group_default\">グループ化されていない</string>\n    <string name=\"document\">ドキュメント（英語）</string>\n    <string name=\"theme\">テーマ</string>\n    <string name=\"menu_about\">このアプリについて</string>\n    <string name=\"menu_group\">グループ</string>\n    <string name=\"menu_configuration\">設定</string>\n    <string name=\"logcat\">デバッグ情報のエクスポート</string>\n    <string name=\"version_x\">バージョン (%s)</string>\n    <string name=\"app_version\">バージョン</string>\n    <string name=\"oss_licenses\">オープンソースライセンス</string>\n    <string name=\"telegram\">Telegram 更新チャンネル</string>\n    <string name=\"github\">ソースコード</string>\n    <string name=\"project\">プロジェクト</string>\n    <string name=\"app_desc\">Kotlinで記述されたAndroid用のユニバーサルプロキシツールチェーン。</string>\n    <string name=\"cag_dns\">DNS設定</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"general_settings\">アブリ設定</string>\n    <string name=\"menu_route\">ルート</string>\n    <string name=\"uuid\">ユーザーID</string>\n    <string name=\"obfs_param\">Obfsパラメータ</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"protocol_param\">プロトコルパラメータ</string>\n    <string name=\"protocol\">プロトコル</string>\n    <string name=\"enc_method\">暗号化方式</string>\n    <string name=\"password\">パスワード</string>\n    <string name=\"password_opt\">パスワード（選択可能）</string>\n    <string name=\"username_opt\">ユーザー名（選択可能）</string>\n    <string name=\"username\">ユーザー名</string>\n    <string name=\"transproxy_mode\">透過プロキシモード</string>\n    <string name=\"server_port\">リモートポート</string>\n    <string name=\"server_address\">サーバー</string>\n    <string name=\"profile_name\">プロフィール名</string>\n    <string name=\"connection_test_url\">テスト用リンクを接続</string>\n    <string name=\"port_local_dns\">ローカルDNSポート</string>\n    <string name=\"dns_hosts\">ドメインの書き換え</string>\n    <string name=\"fakedns_message\">プロキシが停止した後、ネットワークに再接続するために他のアプリケーションを再起動する必要がある場合があります。</string>\n    <string name=\"enable_fakedns\">FakeDNSを有効化</string>\n    <string name=\"enable_dns_routing\">DNSルーティングを有効化</string>\n    <string name=\"direct_dns\">ダイレクトDNS</string>\n    <string name=\"remote_dns\">リモートDNS</string>\n    <string name=\"connection_test_error_status_code\">エラーコード: #%d</string>\n    <string name=\"connection_test_fail\">インターネットは利用できません</string>\n    <string name=\"connection_test_error\">失敗: %s</string>\n    <string name=\"connection_test_testing\">テスト中…</string>\n    <string name=\"speed_detail\">プロキシ： %1$s↑ %2$s↓\n\\nダイレクト： %3$s↑ %4$s↓</string>\n    <string name=\"security_settings\">セキュリティ設定</string>\n    <string name=\"show_direct_speed\">ダイレクト接続のスピードを表示</string>\n    <string name=\"show_stop_sum\">クィックタイトルを使えなくなりたいの場合</string>\n    <string name=\"show_stop\">中止バタンを表示</string>\n    <string name=\"ws_browser_forwarding\">ブラウザへ転送</string>\n    <string name=\"cag_ws\">WebSocket 設定</string>\n    <string name=\"require_http\">HTTPでインバウンドを開く</string>\n    <string name=\"inbound_settings\">インバウンド設定</string>\n    <string name=\"menu_traffic\">トラフィック</string>\n    <string name=\"show_direct_speed_sum\">プロキシなしの通信速度も通知に表示する</string>\n    <string name=\"allow_insecure_sum\">証明書のチェックを無効にします。この設定を有効にすると、平文と同等の安全性になります。</string>\n    <string name=\"connection_test_available\">成功: HTTPS ハンドシェイクに要した時間は %dms です。</string>\n    <string name=\"cag_misc\">その他の設定</string>\n    <string name=\"header_type\">ヘッダータイプ</string>\n    <string name=\"key_opt\">キー（オプション）</string>\n    <string name=\"network\">ネットワーク</string>\n    <string name=\"speed_interval\">速度通知のアップデート間隔</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ko/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"logcat\">디버그 정보 내보내기</string>\n    <string name=\"version_x\">버전(%s)</string>\n    <string name=\"app_version\">버전</string>\n    <string name=\"oss_licenses\">오픈 소스 라이선스</string>\n    <string name=\"telegram\">텔레그램 업데이트 채널</string>\n    <string name=\"github\">소스 코드</string>\n    <string name=\"project\">프로젝트</string>\n    <string name=\"app_desc\">코틀린에 기록 된 안드로이드에 대한 보편적 인 프록시 도구 체인.</string>\n    <string name=\"quick_toggle\">비녀장</string>\n    <string name=\"quick_enable\">활성화</string>\n    <string name=\"quick_disable\">비활성화</string>\n    <string name=\"group_default\">그룹 해제</string>\n    <string name=\"document\">문서</string>\n    <string name=\"theme\">테마</string>\n    <string name=\"menu_about\">대하여</string>\n    <string name=\"menu_group\">그룹</string>\n    <string name=\"menu_configuration\">구성</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-nb-rNO/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"apps_message\">%d programmer</string>\n    <string name=\"select_apps\">Velg programmer</string>\n    <string name=\"apps\">Programmer</string>\n    <string name=\"ooc_warning\">Advarsel</string>\n    <string name=\"download\">Last ned</string>\n    <string name=\"install_from_fdroid\">Installer fra F-Droid</string>\n    <string name=\"install_from_play_store\">Installer fra Play-butikken</string>\n    <string name=\"action_download\">Last ned</string>\n    <string name=\"action_learn_more\">Lær mer</string>\n    <string name=\"confirm\">Bekreft</string>\n    <string name=\"connection_test_timeout\">Tidsavbrudd</string>\n    <string name=\"connection_test_clear_results\">Tøm testresultater</string>\n    <string name=\"connection_test\">Tilkoblingstest</string>\n    <string name=\"unavailable\">Utilgjengelig</string>\n    <string name=\"api_port\">API-port</string>\n    <string name=\"follow_system\">Følg systemforvalg</string>\n    <string name=\"night_mode\">Nattmodus</string>\n    <string name=\"license\">Lisens</string>\n    <string name=\"apply\">Bruk</string>\n    <string name=\"no\">Nei</string>\n    <string name=\"yes\">Ja</string>\n    <string name=\"proxy_cat\">Tjenerinnstillinger</string>\n    <string name=\"plugin_configure\">Sett opp …</string>\n    <string name=\"ignore_battery_optimizations_sum\">Fjern noen begrensninger</string>\n    <string name=\"donate\">Doner</string>\n    <string name=\"subscriptions\">Abonnementer</string>\n    <string name=\"not_connected\">Ikke tilkoblet</string>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">Fjernet</item>\n        <item quantity=\"other\">%d elementer fjernet</item>\n    </plurals>\n    <string name=\"add_profile_methods_scan_qr_code\">Skann QR-kode</string>\n    <string name=\"share_qr_nfc\">QR-kode</string>\n    <string name=\"delete\">Fjern</string>\n    <string name=\"action_export_clipboard\">Eksporter til utklippstavle</string>\n    <string name=\"action_export_file\">Eksporter til fil</string>\n    <string name=\"show_system_apps\">Vis systemprogrammer</string>\n    <string name=\"scanning\">Skanner …</string>\n    <string name=\"search_apps\">Søk …</string>\n    <string name=\"off\">Av</string>\n    <string name=\"on\">På</string>\n    <string name=\"route_reset\">Tilbakestill</string>\n    <string name=\"route_profile\">Velg profil …</string>\n    <string name=\"route_proxy\">Mellomtjener</string>\n    <string name=\"only\">Kun</string>\n    <string name=\"prefer\">Foretrekk</string>\n    <string name=\"edit_config\">Rediger oppsett</string>\n    <string name=\"extra_headers\">Ekstra hoder</string>\n    <string name=\"encryption\">Kryptering</string>\n    <string name=\"tls\">Bruk TLS</string>\n    <string name=\"http_path\">HTTP-sti</string>\n    <string name=\"header_type\">Hodetype</string>\n    <string name=\"network\">Nettverk</string>\n    <string name=\"alter_id\">Alt-ID</string>\n    <string name=\"group_diff\">Forskjell (%s)</string>\n    <string name=\"group_show_diff\">Forskjell</string>\n    <string name=\"group_updated\">%s: Oppdaterte %d mellomtjenere</string>\n    <string name=\"group_no_difference\">%s: Ingen forskjell</string>\n    <string name=\"group_status_proxies_subscription\">%d mellomtjenere | Oppdatert %s</string>\n    <string name=\"group_status_proxies\">%d mellomtjenere</string>\n    <string name=\"group_status_empty_subscription\">Ikke oppdatert enda</string>\n    <string name=\"group_status_empty\">Tom gruppe</string>\n    <string name=\"group_edit_subscription\">Rediger abonnement</string>\n    <string name=\"group_edit\">Rediger gruppe</string>\n    <string name=\"group_create_subscription\">Opprett abonnement</string>\n    <string name=\"group_create\">Opprett gruppe</string>\n    <string name=\"group_name_required\">Gruppenavn påkrevd</string>\n    <string name=\"group_subscription_link\">Abonnementslenke</string>\n    <string name=\"group_name\">Gruppenavn</string>\n    <string name=\"group_default\">Ugruppert</string>\n    <string name=\"document\">Dokument</string>\n    <string name=\"theme\">Drakt</string>\n    <string name=\"menu_about\">Om</string>\n    <string name=\"menu_group\">Gruppe</string>\n    <string name=\"logcat\">Eksporter avlusingsinfo</string>\n    <string name=\"version_x\">Versjon (%s)</string>\n    <string name=\"app_version\">Versjon</string>\n    <string name=\"oss_licenses\">Friproglisenser</string>\n    <string name=\"telegram\">Telegram-oppdateringskanal</string>\n    <string name=\"github\">Kildekode</string>\n    <string name=\"project\">Prosjekt</string>\n    <string name=\"connecting\">Kobler til …</string>\n    <string name=\"undo\">Angre</string>\n    <string name=\"action_import_file\">Importer fra fil</string>\n    <string name=\"action_import\">Importer fra utklippstavle</string>\n    <string name=\"action_export\">Eksporter</string>\n    <string name=\"clear_profiles\">Tøm</string>\n    <string name=\"config_settings\">Oppsettsinnstillinger</string>\n    <string name=\"balancer_strategy\">Strategi</string>\n    <string name=\"balancer_type\">Type</string>\n    <string name=\"select_profile\">Velg profil</string>\n    <string name=\"add_profile\">Legg til profil</string>\n    <string name=\"share\">Del</string>\n    <string name=\"edit\">Rediger</string>\n    <string name=\"settings\">Innstillinger</string>\n    <string name=\"clipboard_empty\">Utklippstavlen er tom</string>\n    <string name=\"connect\">Koble til</string>\n    <string name=\"profile_empty\">Velg en profil</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"invalid_server\">Ugyldig tjenernavn</string>\n    <string name=\"service_proxy\">Mellomtjener-tjeneste</string>\n    <string name=\"service_vpn\">VPN-tjeneste</string>\n    <string name=\"http_host\">HTTP-vert</string>\n    <string name=\"uuid\">Bruker-ID</string>\n    <string name=\"port_http\">HTTP-mellomtjenerport</string>\n    <string name=\"port_proxy\">SOCKS5-mellomtjenerport</string>\n    <string name=\"service_mode_proxy\">Kun mellomtjener</string>\n    <string name=\"clear_profiles_message\">Er du sikker på at du vil slette denne gruppen\\?</string>\n    <string name=\"profile_import_message\">Bekreft at du vil importere profilen %s\\?</string>\n    <string name=\"profile_import\">Importer profil</string>\n    <string name=\"subscription_import_message\">Bekrefter du at du vil importere abonnement %s\\? Hvis du kommer fra en kilde som du ikke har tillit til, kan dette føre til at din IP og denne oppførselen lekker.</string>\n    <string name=\"subscription_import\">Importer abonnement</string>\n    <string name=\"subscription_traffic\">%s Brukt / %s Gjenstår</string>\n    <string name=\"subscription_used\">%s Brukt</string>\n    <string name=\"subscription_update_message\">Oppdaterer %s…</string>\n    <string name=\"subscription_update\">Abonnementsoppdatering</string>\n    <string name=\"service_subscription\">Oppdateringstjeneste for abonnement</string>\n    <string name=\"ooc_missing_protocol\">Abonnementet krever støtte for protokoll %s, men det kan ikke bli funnet. Profiler som ikke støttes, blir ignorert.</string>\n    <string name=\"update_subscription_warning\">Proxy er ikke tilkoblet. Er du sikker på at du vil fortsette å oppdatere\\?</string>\n    <string name=\"ooc_subscription_token_invalid\">Ugyldig OOCv1 -token</string>\n    <string name=\"profile_requiring_plugin\">Profil %s krever at %s plugin er installert, men det ble ikke funnet.</string>\n    <string name=\"missing_plugin\">Plugin mangler</string>\n    <string name=\"subscription_user_agent\">Bruker agent</string>\n    <string name=\"update_when_connected_only_sum\">Forhindre lekkasje av IP -adresse</string>\n    <string name=\"update_when_connected_only\">Oppdater bare når den er tilkoblet</string>\n    <string name=\"auto_update_delay\">Forsinkelse automatisk oppdatering (i minutter)</string>\n    <string name=\"auto_update\">Automatisk oppdatering</string>\n    <string name=\"update_settings\">Oppdater innstillinger</string>\n    <string name=\"raw\">Rå</string>\n    <string name=\"deduplication_sum\">Fjern dupliserte konfigurasjoner ved oppdatering</string>\n    <string name=\"force_resolve_sum\">Løs alle domenenavn til IP -adresser når du oppdaterer. Vert og SNI blir automatisk lagt til hvis mulig</string>\n    <string name=\"force_resolve\">Tving løsning</string>\n    <string name=\"delete_group_prompt\">Er du sikker på at du vil fjerne denne gruppen\\?</string>\n    <string name=\"subscription_type\">Abonnementstype</string>\n    <string name=\"group_type\">Gruppetype</string>\n    <string name=\"subscription_settings\">Abonnementsinnstillinger</string>\n    <string name=\"subscription\">Abonnement</string>\n    <string name=\"group_settings\">Gruppeinnstillinger</string>\n    <string name=\"group_basic\">Grunnleggende</string>\n    <string name=\"trojan_provider\">Trojansk leverandør</string>\n    <string name=\"protocol_settings\">Protokollinnstillinger</string>\n    <string name=\"append_http_proxy_sum\">HTTP -proxy brukes direkte fra (nettleser/ noen støttede apper), uten å gå gjennom den virtuelle NIC -enheten (Android 10+)</string>\n    <string name=\"append_http_proxy\">Legg til HTTP -proxy til VPN</string>\n    <string name=\"connection_test_unreachable\">Uoppnåelig</string>\n    <string name=\"connection_test_refused\">Tilkobling nektet</string>\n    <string name=\"connection_test_domain_not_found\">Domenet ble ikke funnet</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing er ikke tilgjengelig</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing utilgjengelig</string>\n    <string name=\"clear_traffic_statistics\">Slett trafikkstatistikk</string>\n    <string name=\"always_show_address_sum\">Vis alltid serveradressen på konfigurasjonskortet</string>\n    <string name=\"always_show_address\">Vis alltid adresse</string>\n    <string name=\"probe_interval\">Balanser observasjonsintervall</string>\n    <string name=\"random\">Tilfeldig</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"enable_log_sum\">For feilsøkingsformål</string>\n    <string name=\"enable_log\">Aktiver logg</string>\n    <string name=\"auto\">Automatisk</string>\n    <string name=\"disable\">Deaktiver</string>\n    <string name=\"enable\">Muliggjøre</string>\n    <string name=\"lines\">%d linjer</string>\n    <string name=\"route_warn\">Sørg for at du har lest dokumentasjonen før du legger til egendefinerte regler, hvis ikke kan du ikke koble deg til Internett.</string>\n    <string name=\"need_reload\">Last inn proxy -tjenesten på nytt for å bruke endringer</string>\n    <string name=\"unsaved_changes_prompt\">Endringene er ikke lagret. Vil du spare\\?</string>\n    <string name=\"ss_cat\">Innstillinger for Shadowsocks</string>\n    <string name=\"plugin_auto_connect_unlock_only\">Denne pluginen fungerer kanskje ikke med Auto Connect</string>\n    <string name=\"plugin_untrusted\">Advarsel: Det ser ikke ut til at denne pluginen kommer fra en kjent klarert kilde.</string>\n    <string name=\"plugin_unknown\">Ukjent plugin %s</string>\n    <string name=\"plugin_disabled\">Funksjonshemmet</string>\n    <string name=\"plugin\">Plugg inn</string>\n    <string name=\"ignore_battery_optimizations\">Ignorer batterioptimaliseringer</string>\n    <string name=\"donate_info\">Jeg elsker penger</string>\n    <string name=\"deduplication\">Deduplisering</string>\n    <string name=\"no_proxies_found_in_clipboard\">Ingen fullmakter ble funnet i utklippstavlen</string>\n    <string name=\"no_proxies_found_in_file\">Ingen fullmakter ble funnet i filen</string>\n    <string name=\"no_proxies_found_in_subscription\">Ingen fullmakter ble funnet i abonnementet</string>\n    <string name=\"no_proxies_found\">Ingen fullmakter ble funnet i lenken</string>\n    <string name=\"error_title\">Feil</string>\n    <string name=\"cleartext_http_warning\">Klartekst HTTP -trafikk er usikker</string>\n    <string name=\"vpn_connected\">Tilkoblet, trykk for å kontrollere tilkoblingen</string>\n    <string name=\"deprecated\">Utfaset</string>\n    <string name=\"insecure\">Utrygg</string>\n    <plurals name=\"added\">\n        <item quantity=\"one\">La til</item>\n        <item quantity=\"other\">%d fullmakter lagt til</item>\n    </plurals>\n    <string name=\"add_profile_methods_manual_settings\">Manuelle innstillinger</string>\n    <string name=\"delete_confirm_prompt\">Er du sikker på at du vil fjerne denne profilen\\?</string>\n    <string name=\"profile_config\">Profilkonfigurasjon</string>\n    <string name=\"file_manager_missing\">Enheten din mangler en standard filvelger for Android. Installer en, for eksempel materialfiler.</string>\n    <string name=\"action_import_err\">Kunne ikke importere.</string>\n    <string name=\"action_import_msg\">Importer vellykket!</string>\n    <string name=\"action_export_err\">Kunne ikke eksportere.</string>\n    <string name=\"action_export_msg\">Eksporter vellykket!</string>\n    <string name=\"action_scan_china_apps\">Skann apper i Kina</string>\n    <string name=\"action_from_link\">Fra abonnement</string>\n    <string name=\"action_create_group\">Tom gruppe</string>\n    <string name=\"circular_reference_sum\">Ruten kan ikke inneholde seg selv.</string>\n    <string name=\"circular_reference\">Sirkulær referanse</string>\n    <string name=\"chain_settings\">Kjedeinnstillinger</string>\n    <string name=\"balancer_settings\">Balanseinnstillinger</string>\n    <string name=\"balancer\">Balanser</string>\n    <string name=\"custom_config\">Egendefinert konfigur</string>\n    <string name=\"proxy_chain\">Mellomtjenerkjede</string>\n    <string name=\"reboot_required\">Kunne ikke starte VPN -tjenesten. Du må kanskje starte enheten på nytt.</string>\n    <string name=\"vpn_permission_denied\">Tillatelse nektet å opprette en VPN -tjeneste</string>\n    <string name=\"stopping\">Slår av…</string>\n    <string name=\"stop\">Stoppe</string>\n    <string name=\"service_failed\">Mislyktes:</string>\n    <string name=\"forward_success\">Mellomtjener startet.</string>\n    <string name=\"direct_boot_aware_summary\">Den valgte profilinformasjonen din blir mindre beskyttet</string>\n    <string name=\"direct_boot_aware\">Tillat veksling i låseskjerm</string>\n    <string name=\"auto_connect_summary\">Aktiver mellomtjener ved oppstart/programoppgradering hvis den kjørte før</string>\n    <string name=\"auto_connect\">Automatisk tilkobling</string>\n    <string name=\"bypass_apps\">Omgå</string>\n    <string name=\"clear_selections\">Fjern markeringene</string>\n    <string name=\"invert_selections\">Inverter valg</string>\n    <string name=\"proxied_apps_summary\">Konfigurer VPN -modus for utvalgte apper</string>\n    <string name=\"proxied_apps\">Apper VPN -modus</string>\n    <string name=\"tcp_keep_alive_interval\">TCP beholder det aktive pakkeleveringsintervallet</string>\n    <string name=\"mux_concurrency\">Mux samtidige tilkoblinger</string>\n    <string name=\"enable_mux\">Aktiver Multiplexer</string>\n    <string name=\"traffic_sniffing\">Aktiver trafikksniff</string>\n    <string name=\"domain_strategy\">Domeneløsningsstrategi</string>\n    <string name=\"route_opt_block_ads\">Blokker annonser</string>\n    <string name=\"route_opt_bypass_lan\">Omgå LAN</string>\n    <string name=\"route_not_asset\">Ikke en ressursfil: unntatt .db, men %s</string>\n    <string name=\"route_asset_updated\">Oppdatert</string>\n    <string name=\"route_asset_no_update\">Ingen oppdatering</string>\n    <string name=\"route_asset_status\">Lokal versjon: %s</string>\n    <string name=\"route_rules_official\">Offisielt</string>\n    <string name=\"route_rules_provider\">Leverandør av regelaktiver</string>\n    <string name=\"route_assets\">Eiendeler</string>\n    <string name=\"route_manage_assets\">Administrer ruteelementer</string>\n    <string name=\"route_bypass_ip\">IP -regel for %s</string>\n    <string name=\"route_bypass_domain\">Domeneregel for %s</string>\n    <string name=\"empty_route_notice\">Sett noen regler før du lagrer</string>\n    <string name=\"empty_route\">Tom rute</string>\n    <string name=\"route_block\">Blokkere</string>\n    <string name=\"route_bypass\">Omgå</string>\n    <string name=\"route_name\">Rutenavn</string>\n    <string name=\"delete_route_prompt\">Er du sikker på at du vil fjerne denne ruten\\?</string>\n    <string name=\"route_add\">Lag rute</string>\n    <string name=\"menu_route\">Rute</string>\n    <string name=\"metered_summary\">Tips system for å behandle VPN som målt</string>\n    <string name=\"metered\">Målt hint</string>\n    <string name=\"ipv6\">IPv6 -rute</string>\n    <string name=\"config_type\">Konfig Type</string>\n    <string name=\"early_data_header_name\">Tidlig navn på dataoverskrift</string>\n    <string name=\"certificates\">Sertifikater</string>\n    <string name=\"alpn\">Forhandling av applikasjonslagsprotokoll</string>\n    <string name=\"sni\">Servernavn indikasjon</string>\n    <string name=\"grpc_service_name\">gRPC -tjenestenavn</string>\n    <string name=\"ws_path\">WebSocket -bane</string>\n    <string name=\"ws_host\">WebSocket -vert</string>\n    <string name=\"security\">Kryptering av transportlag</string>\n    <string name=\"obfs_param\">Obs Stopp</string>\n    <string name=\"protocol_param\">Protokoll Param</string>\n    <string name=\"protocol\">Protokoll</string>\n    <string name=\"enc_method\">Krypter metode</string>\n    <string name=\"password\">Passord</string>\n    <string name=\"key_opt\">Nøkkel (valgfritt)</string>\n    <string name=\"password_opt\">Passord (valgfritt)</string>\n    <string name=\"username_opt\">Brukernavn (valgfritt)</string>\n    <string name=\"username\">Brukernavn</string>\n    <string name=\"server_port\">Ekstern port</string>\n    <string name=\"server_address\">Tjener</string>\n    <string name=\"profile_name\">Profil navn</string>\n    <string name=\"connection_test_url\">Tilkoblingstest -URL</string>\n    <string name=\"transproxy_mode\">Transproxy -modus</string>\n    <string name=\"require_transproxy\">Aktiver Transproxy Inbound</string>\n    <string name=\"port_local_dns\">Lokal DNS port</string>\n    <string name=\"dns_hosts\">Omskriving av domenet</string>\n    <string name=\"fakedns_message\">Kan føre til at andre applikasjoner må startes på nytt for å koble til nettverket igjen etter at proxy er stoppet</string>\n    <string name=\"enable_fakedns\">Aktiver FakeDNS</string>\n    <string name=\"dns_routing_message\">Løs domener i bypass -ruter med direkte DNS. Vær oppmerksom på potensielle DNS lekkasjer</string>\n    <string name=\"enable_dns_routing\">Aktiver DNS -ruting</string>\n    <string name=\"direct_dns\">Direkte DNS</string>\n    <string name=\"remote_dns\">Ekstern DNS</string>\n    <string name=\"cag_dns\">DNS innstillinger</string>\n    <string name=\"connection_test_error_status_code\">Feilkode: #%d</string>\n    <string name=\"connection_test_fail\">Internett utilgjengelig</string>\n    <string name=\"connection_test_error\">Mislyktes: %s</string>\n    <string name=\"connection_test_available_http\">Suksess: HTTP håndtrykk tok %dms</string>\n    <string name=\"connection_test_available\">Suksess: HTTPS håndtrykk tok %dms</string>\n    <string name=\"connection_test_testing\">Tester…</string>\n    <string name=\"speed_detail\">Proxy： %1$s↑ %2$s↓\n\\nDirekte： %3$s↑ %4$s↓</string>\n    <string name=\"allow_insecure_sum\">Deaktiver sertifikatkontroll. Når den er aktivert, er denne konfigurasjonen like sikker som ren tekst</string>\n    <string name=\"allow_insecure\">Tillat usikker</string>\n    <string name=\"security_settings\">Sikkerhetsinnstillinger</string>\n    <string name=\"show_direct_speed_sum\">Vis også trafikkhastigheten uten proxy i varselet</string>\n    <string name=\"show_direct_speed\">Vis direkte hastighet</string>\n    <string name=\"show_stop_sum\">Hvis du ikke vil bruke Quick Tile som bryter</string>\n    <string name=\"show_stop\">Vis stoppknapp</string>\n    <string name=\"cag_misc\">Diverse innstillinger</string>\n    <string name=\"speed_interval\">Oppdateringsintervall for hastighetsvarsling</string>\n    <string name=\"ws_browser_forwarding_sum\">Videresend tilsvarende WebSockets gjennom nettleseren.</string>\n    <string name=\"ws_browser_forwarding\">Videresending av nettleser</string>\n    <string name=\"cag_ws\">WebSocket -innstillinger</string>\n    <string name=\"require_http\">Aktiver HTTP inbound</string>\n    <string name=\"general_settings\">Appinnstillinger</string>\n    <string name=\"inbound_settings\">Innkommende innstillinger</string>\n    <string name=\"allow_access_sum\">Bind innkommende servere til 0.0.0.0</string>\n    <string name=\"allow_access\">Tillat tilkoblinger fra LAN</string>\n    <string name=\"cag_route\">Ruteinnstillinger</string>\n    <string name=\"port_transproxy\">Transproxy- port</string>\n    <string name=\"service_mode\">Servicemodus</string>\n    <string name=\"group_duplicate\">Duplisere:\n\\n%s</string>\n    <string name=\"group_deleted\">Slettet:\n\\n%s</string>\n    <string name=\"group_changed\">Oppdatert:\n\\n%s</string>\n    <string name=\"group_added\">La til:\n\\n%s</string>\n    <string name=\"group_delete_confirm_prompt\">Er du sikker på at du vil fjerne denne gruppen\\?</string>\n    <string name=\"group_update\">Oppdater</string>\n    <string name=\"tile_title\">Bytter</string>\n    <string name=\"quick_toggle\">Veksle</string>\n    <string name=\"quick_enable\">Aktiver</string>\n    <string name=\"quick_disable\">Deaktiver</string>\n    <string name=\"menu_configuration\">Konfigurasjon</string>\n    <string name=\"app_desc\">Den universelle proxy -verktøykjeden for Android, skrevet i Kotlin.</string>\n    <string name=\"group_filter\">Raffinering</string>\n    <string name=\"standard\">Standard</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"hysteria_obfs\">Tilsløringspassord</string>\n    <string name=\"hysteria_auth_payload\">Identitetbekreftelses-nyttelast</string>\n    <string name=\"experimental_settings\">Eksperimentelt</string>\n    <string name=\"hysteria_download_mbps\">Maks. nedlastingshastighet (i Mbps)</string>\n    <string name=\"hysteria_upload_mbps\">Maks. opplastingshastighet (i Mbps)</string>\n    <string name=\"hysteria_auth_type\">Identitetbekreftelsestype</string>\n    <string name=\"menu_traffic\">Trafikk</string>\n    <string name=\"hysteria_connection_receive_window\">Mottaksvindu for QUIC-tilkobling</string>\n    <string name=\"hysteria_stream_receive_window\">Mottaksvindu for QUIC-strøm</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (Shawdowsocks-programtillegg for Android)</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple-obfs (Shawdowsocks-programtillegg for Android)</string>\n    <string name=\"plugin_exists_but_on_shit_system\">Profilen %s krever %s-programtillegget, men den proprietære utstyrsfabrikanten (vanligvis i overvåkningsøyemed for profitt, eller skadevare) tuklet med Android-installasjonen din, noe som gjør programtillegget ubrukelig.</string>\n    <string name=\"hysteria_disable_mtu_discovery\">Skru av oppdagelse av maksimal overførelsesenhet i sti</string>\n    <string name=\"ssh_private_key_passphrase\">Passord for privat nøkkel</string>\n    <string name=\"wireguard_psk\">Forhåndsdelt nøkkel for likemann</string>\n    <string name=\"wireguard_public_key\">Offentlig nøkkel for likemann</string>\n    <string name=\"translate_platform\">Oversettelsesplattform</string>\n    <string name=\"route_need_vpn\">Rutingsregelen %s avhenger av at VPN er operativ, så den ignoreres.</string>\n    <string name=\"group_order_origin\">Opprinnelse</string>\n    <string name=\"group_order\">Rekkefølge</string>\n    <string name=\"tcp_connections\">%d TCP-tilkoblinger</string>\n    <string name=\"group_order_by_delay\">Etter forsinkelse</string>\n    <string name=\"group_order_by_name\">Etter navn</string>\n    <string name=\"wireguard_local_address\">Lokal adresse</string>\n    <string name=\"clear_logcat\">Tøm Logcat</string>\n    <string name=\"menu_log\">Logger</string>\n    <string name=\"menu_tools\">Verktøy</string>\n    <string name=\"ssh_private_key\">Privat nøkkel</string>\n    <string name=\"ssh_public_key\">Offentlig nøkkel</string>\n    <string name=\"ssh_auth_type_none\">Ingen</string>\n    <string name=\"no_statistics\">Ingen statistikk enda</string>\n    <string name=\"route_for\">Regel for %s</string>\n    <string name=\"app_no_launcher\">Programmet har ikke noe grensesnitt.</string>\n    <string name=\"copy_failed\">Kunne ikke kopiere.</string>\n    <string name=\"copy_success\">Kopiert.</string>\n    <string name=\"create_rule\">Opprett regel</string>\n    <string name=\"open_market\">Åpne markedet</string>\n    <string name=\"open_settings\">Åpne innstillinger</string>\n    <string name=\"open_app\">Åpne program</string>\n    <string name=\"copy_package_name\">Kopier pakkenavn</string>\n    <string name=\"copy_label\">Kopier navn</string>\n    <string name=\"udp_connections\">%d UDP-tilkoblinger</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"fab_color_progress\">@color/material_light_white</color>\n    <color name=\"preference_simple_menu_background\">#303030</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-nl/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"logcat\">Foutopsporing Informatie exporteren</string>\n    <string name=\"version_x\">Versie (%s)</string>\n    <string name=\"app_version\">Versie</string>\n    <string name=\"oss_licenses\">Open source-licenties</string>\n    <string name=\"telegram\">Telegram-update kanaal</string>\n    <string name=\"github\">Broncode</string>\n    <string name=\"project\">Project</string>\n    <string name=\"app_desc\">De universele proxy-toolchain voor Android, geschreven in Kotlin.</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"group_name\">Nome do grupo</string>\n    <string name=\"group_default\">Desagrupado</string>\n    <string name=\"document\">Documento</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"menu_about\">Sobre</string>\n    <string name=\"menu_group\">Grupo</string>\n    <string name=\"menu_configuration\">Configuração</string>\n    <string name=\"logcat\">Exportar informações de depuração</string>\n    <string name=\"version_x\">Versão (%s)</string>\n    <string name=\"app_version\">Versão</string>\n    <string name=\"oss_licenses\">Licenças de código aberto</string>\n    <string name=\"telegram\">Canal de atualização do telegram</string>\n    <string name=\"github\">Código fonte</string>\n    <string name=\"project\">Projeto</string>\n    <string name=\"app_desc\">Um conjunto de ferramentas de proxy universal para Android, escrito em Kotlin.</string>\n    <string name=\"group_subscription_link\">Link de inscrição</string>\n    <string name=\"group_update\">Atualizar</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-ru/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n<string name=\"abc_search_hint\">Введите запрос</string>\n<string name=\"abc_searchview_description_clear\">Удалить запрос</string>\n<string name=\"abc_searchview_description_search\">Поиск</string>\n<string name=\"abc_searchview_description_submit\">Отправить запрос</string>\n<string name=\"abc_searchview_description_voice\">Голосовой поиск</string>\n<string name=\"abc_shareactionprovider_share_with\">Поделиться с помощью</string>\n<string name=\"abc_toolbar_collapse_description\">Свернуть</string>\n<string name=\"acquire_wake_lock\">Использовать WakeLock</string>\n<string name=\"acquire_wake_lock_summary\">Сохранять CPU включенным</string>\n<string name=\"action_download\">СКАЧАТЬ</string>\n<string name=\"action_export\">Экспортировать</string>\n<string name=\"action_export_clipboard\">Экспорт в буфер обмена</string>\n<string name=\"action_export_err\">Не удалось экспортировать.</string>\n<string name=\"action_export_file\">Экспорт в файл</string>\n<string name=\"action_export_msg\">Успешно экспортировано!</string>\n<string name=\"action_import\">Импорт из буфера обмена</string>\n<string name=\"action_import_err\">Не удалось импортировать.</string>\n<string name=\"action_import_file\">Импортировать из файла</string>\n<string name=\"action_import_msg\">Успешно импортировано!</string>\n<string name=\"action_learn_more\">УЗНАТЬ БОЛЬШЕ</string>\n<string name=\"action_switch\">Переключить</string>\n<string name=\"add_profile\">Добавить профиль</string>\n<string name=\"add_profile_methods_manual_settings\">Ручные настройки</string>\n<string name=\"add_profile_methods_scan_qr_code\">Отсканировать QR-код</string>\n<string name=\"ads\">Реклама</string>\n<string name=\"allow_access\">Разрешить подключения из локальной сети</string>\n<string name=\"allow_access_sum\">Привязать входящие серверы к 0.0.0.0</string>\n<string name=\"allow_insecure\">Разрешить небезопасное подключение</string>\n<string name=\"allow_insecure_on_request_sum\">Отключить проверку сертификатов при обновлении подписок</string>\n<string name=\"allow_insecure_sum\">Отключить проверку сертификата. При включении эта функция настолько же безопасна как передача пароля в открытом виде</string>\n<string name=\"alpn\">Согласование протокола уровня приложений</string>\n<string name=\"alter_id\">Изменить ID</string>\n<string name=\"always_show_address\">Всегда показывать адрес</string>\n<string name=\"always_show_address_sum\">Всегда отображать адрес сервера на карте конфигурации</string>\n<string name=\"app_desc\">Универсальный набор инструментов прокси для Android, написанный на Kotlin.</string>\n<string name=\"app_name\">NekoBox</string>\n<string name=\"app_name_long\">NekoBox для Android</string>\n<string name=\"app_tls_version\">Минимальная версия TLS протокола</string>\n<string name=\"app_version\">Версия</string>\n<string name=\"append_http_proxy\">Добавить HTTP-прокси к VPN</string>\n<string name=\"append_http_proxy_sum\">HTTP-прокси будет использоваться напрямую из (браузера / некоторых поддерживаемых приложений), без использования виртуального сетевого интерфейса (Android 10+)</string>\n<string name=\"apply\">Применить</string>\n<string name=\"apps\">Приложения</string>\n<string name=\"apps_message\">%d приложений</string>\n<string name=\"auto\">Авто</string>\n<string name=\"auto_connect\">Автоматическое подключение</string>\n<string name=\"auto_connect_summary\">Включить прокси при запуске/обновлении приложения, если он был запущен до этого</string>\n<string name=\"auto_select_proxy_apps\">Автоматический выбор прокси-приложений</string>\n<string name=\"auto_select_proxy_apps_message\">Автоматический выбор прокси-приложений приведет к очистке текущего выбора.</string>\n<string name=\"auto_update\">Автоматическое обновление</string>\n<string name=\"auto_update_delay\">Задержка автоматического обновления (в минутах)</string>\n<string name=\"backup\">Бэкап</string>\n<string name=\"backup_groups_and_configurations\">Группы и конфигурации</string>\n<string name=\"backup_import\">Импортировать</string>\n<string name=\"backup_import_summary\">При импорте существующие данные будут перезаписаны.</string>\n<string name=\"backup_importing\">Импортирование…</string>\n<string name=\"backup_not_file\">Не файл резервной копии: ожидался .json, а не %s</string>\n<string name=\"backup_rules\">Правила маршрутизации</string>\n<string name=\"backup_settings\">Настройки</string>\n<string name=\"backup_summary\">Если правила маршрутизации резервируются без конфигураций, то пользовательские исходящие соединения будут потеряны.</string>\n<string name=\"bottomsheet_action_collapse\">Свернуть нижний экран</string>\n<string name=\"bottomsheet_action_expand\">Развернуть нижний экран</string>\n<string name=\"bottomsheet_action_expand_halfway\">Развернуть наполовину</string>\n<string name=\"bottomsheet_drag_handle_clicked\">Двойное нажатие на маркер перемещения</string>\n<string name=\"bottomsheet_drag_handle_content_description\">Маркер перемещения</string>\n<string name=\"bypass_apps\">Обход</string>\n<string name=\"bypass_lan_in_core\">Обход LAN в ядре</string>\n<string name=\"cag_dns\">Настройки DNS</string>\n<string name=\"cag_misc\">Прочие настройки</string>\n<string name=\"cag_route\">Настройки маршрута</string>\n<string name=\"cag_ws\">Настройки WebSocket</string>\n<string name=\"certificates\">Сертификаты</string>\n<string name=\"chain_settings\">Настройки цепочки</string>\n<string name=\"character_counter_content_description\">Введено символов: %1$d из %2$d</string>\n<string name=\"character_counter_overflowed_content_description\">Превышено ограничение на количество символов (%1$d из %2$d)</string>\n<string name=\"check_update_no\">Обновления не найдены.</string>\n<string name=\"check_update_preview\">Проверить наличие обновлений предварительной версии</string>\n<string name=\"check_update_release\">Проверить наличие обновлений релизнойной версии</string>\n<string name=\"circular_reference\">Циклическая ссылка</string>\n<string name=\"circular_reference_sum\">Маршрут не может содержать сам себя.</string>\n<string name=\"clear_logcat\">Очистить журнал</string>\n<string name=\"clear_profiles\">Очистить</string>\n<string name=\"clear_profiles_message\">Вы уверены, что хотите очистить эту группу?</string>\n<string name=\"clear_selections\">Очистить выбранное</string>\n<string name=\"clear_text_end_icon_content_description\">Очистить текстовое поле</string>\n<string name=\"clear_traffic_statistics\">Очистить статистику трафика</string>\n<string name=\"cleartext_http_warning\">Передача HTTP-трафика в виде открытого текста небезопасна</string>\n<string name=\"clipboard_empty\">Буфер обмена пуст</string>\n<string name=\"config_settings\">Настройки конфигурации</string>\n<string name=\"config_type\">Тип конфигурации</string>\n<string name=\"confirm\">Подтвердить</string>\n<string name=\"connect\">Подключиться</string>\n<string name=\"connecting\">Подключение…</string>\n<string name=\"connection_test_available\">HTTPS-соединение установлено за %d мс</string>\n<string name=\"connection_test_available_http\">HTTP-соединение установлено за %d мс</string>\n<string name=\"connection_test_clear_results\">Очистить результаты теста</string>\n<string name=\"connection_test_delete_unavailable\">Удалить недоступные</string>\n<string name=\"connection_test_domain_not_found\">Домен не найден</string>\n<string name=\"connection_test_error\">Ошибка: %s</string>\n<string name=\"connection_test_icmp_ping_unavailable\">ICMPing недоступен</string>\n<string name=\"connection_test_refused\">В соединении отказано</string>\n<string name=\"connection_test_tcp_ping\">TCP Пинг</string>\n<string name=\"connection_test_tcp_ping_unavailable\">TCPing недоступен</string>\n<string name=\"connection_test_testing\">Тестирование…</string>\n<string name=\"connection_test_timeout\">Тайм-аут</string>\n<string name=\"connection_test_unreachable\">Недоступен</string>\n<string name=\"connection_test_url\">URL-адрес проверки подключения</string>\n<string name=\"connection_test_url_test\">URL Тест</string>\n<string name=\"copy\">Копировать</string>\n<string name=\"copy_toast_msg\">Ссылка скопирована в буфер обмена.</string>\n<string name=\"create_shortcut\">Создать ярлык</string>\n<string name=\"custom_config\">Пользовательская конфигурация</string>\n<string name=\"custom_config_json\">Пользовательская конфигурация JSON</string>\n<string name=\"custom_outbound_json\">Пользовательский исходящий JSON</string>\n<string name=\"deduplication\">Дедупликация</string>\n<string name=\"deduplication_sum\">Удалить повторяющиеся конфигурации при обновлении</string>\n<string name=\"delete\">Удалить</string>\n<string name=\"delete_confirm_prompt\">Вы уверены, что хотите удалить этот профиль?</string>\n<string name=\"delete_group_prompt\">Вы уверены, что хотите удалить эту группу?</string>\n<string name=\"delete_route_prompt\">Вы уверены, что хотите удалить этот маршрут?</string>\n<string name=\"direct_boot_aware\">Разрешить переключение на экране блокировки</string>\n<string name=\"direct_boot_aware_summary\">Выбранная вами информация профиля будет менее защищена</string>\n<string name=\"direct_dns\">Прямой DNS</string>\n<string name=\"disable\">Отключить</string>\n<string name=\"dns_hosts\">Перезапись домена</string>\n<string name=\"dns_routing_message\">Разрешить домены в обходных маршрутах с помощью Direct DNS. Помните о потенциальных утечках DNS</string>\n<string name=\"document\">Документы</string>\n<string name=\"domain_strategy\">Правило разрешения доменов</string>\n<string name=\"domain_strategy_for_direct\">Правило домена для Прямого</string>\n<string name=\"domain_strategy_for_remote\">Правило домена для Удаленного</string>\n<string name=\"domain_strategy_for_server\">Правило домена для Адреса сервера</string>\n<string name=\"donate\">Пожертвовать</string>\n<string name=\"donate_info\">Я люблю деньги</string>\n<string name=\"download\">Скачать</string>\n<string name=\"early_data_header_name\">Имя заголовка ранних данных</string>\n<string name=\"ech_config\">Конфигурация ECH</string>\n<string name=\"edit\">Редактировать</string>\n<string name=\"edit_config\">Изменить конфигурацию</string>\n<string name=\"empty_route\">Пустой маршрут</string>\n<string name=\"empty_route_notice\">Установите какие-либо правила перед сохранением</string>\n<string name=\"enable\">Включить</string>\n<string name=\"enable_clash_api\">Включить Clash API</string>\n<string name=\"enable_clash_api_summary\">Предоставлять панель управления Clash API и YACD по адресу 127.0.0.1:9090</string>\n<string name=\"enable_dns_routing\">Включить маршрутизацию DNS</string>\n<string name=\"enable_ech\">Включить ECH</string>\n<string name=\"enable_ech_sum\">Включить зашифрованный клиент Hello</string>\n<string name=\"enable_fakedns\">Включить FakeDNS</string>\n<string name=\"enable_log\">Включить журнал</string>\n<string name=\"enable_log_sum\">В целях отладки</string>\n<string name=\"enable_mux\">Включить мультиплексор</string>\n<string name=\"enc_method\">Метод шифрования</string>\n<string name=\"encryption\">Шифрование</string>\n<string name=\"error_a11y_label\">Ошибка: недопустимое значение</string>\n<string name=\"error_icon_content_description\">Ошибка</string>\n<string name=\"error_title\">Ошибка</string>\n<string name=\"expand_button_title\">Дополнительно</string>\n<string name=\"exposed_dropdown_menu_content_description\">Показать раскрывающееся меню</string>\n<string name=\"extra_headers\">Дополнительные заголовки</string>\n<string name=\"fakedns_message\">Может вызвать необходимость перезапуска других приложений для повторного подключения к сети после остановки прокси</string>\n<string name=\"file_manager_missing\">На вашем устройстве отсутствует стандартный селектор файлов Android, установите его, например Material Files.</string>\n<string name=\"follow_system\">Использовать системную тему</string>\n<string name=\"force_resolve\">Принудительно разрешать доменные имена</string>\n<string name=\"force_resolve_sum\">При обновлении преобразовать все доменные имена в IP-адреса. Хост и SNI будут добавлены автоматически, если это возможно</string>\n<string name=\"forward_success\">Прокси запустился.</string>\n<string name=\"front_proxy\">Прямой прокси</string>\n<string name=\"general_settings\">Настройки приложения</string>\n<string name=\"generating\">Создание…</string>\n<string name=\"github\">Исходный код</string>\n<string name=\"global_allow_insecure\">Всегда разрешать небезопасные</string>\n<string name=\"group_added\">Добавлено:\n%s</string>\n<string name=\"group_basic\">Базовый</string>\n<string name=\"group_changed\">Обновлено: \n%s</string>\n<string name=\"group_create\">Создать группу</string>\n<string name=\"group_create_subscription\">Создать подписку</string>\n<string name=\"group_default\">Разгруппировано</string>\n<string name=\"group_delete_confirm_prompt\">Вы уверены, что хотите удалить эту группу?</string>\n<string name=\"group_deleted\">Удалено: \n%s</string>\n<string name=\"group_diff\">различия (%s)</string>\n<string name=\"group_duplicate\">Дубликат: \n%s</string>\n<string name=\"group_edit\">Редактировать группу</string>\n<string name=\"group_edit_subscription\">Редактировать подписку</string>\n<string name=\"group_filter\">Фильтр</string>\n<string name=\"group_name\">Имя группы</string>\n<string name=\"group_name_required\">Требуется название группы</string>\n<string name=\"group_no_difference\">%s: Не отличаются</string>\n<string name=\"group_not_subscription\">Тип группы не является подпиской</string>\n<string name=\"group_order\">Сортировка</string>\n<string name=\"group_order_by_delay\">По задержке</string>\n<string name=\"group_order_by_name\">По имени</string>\n<string name=\"group_order_origin\">По источнику</string>\n<string name=\"group_settings\">Настройки группы</string>\n<string name=\"group_show_diff\">Сравнить группы</string>\n<string name=\"group_status_empty\">Пустая группа</string>\n<string name=\"group_status_empty_subscription\">Еще не обновлено</string>\n<string name=\"group_status_proxies\">%d прокси</string>\n<string name=\"group_status_proxies_subscription\">%d прокси | Обновлено %s</string>\n<string name=\"group_subscription_link\">Ссылка для подписки</string>\n<string name=\"group_type\">Тип группы</string>\n<string name=\"group_update\">Обновить</string>\n<string name=\"group_updated\">%s: обновлено %d прокси</string>\n<string name=\"grpc_service_name\">Имя службы gRPC</string>\n<string name=\"header_type\">Тип заголовка</string>\n<string name=\"hop_interval\">Интервал переключения портов(второй)</string>\n<string name=\"http_host\">HTTP-хост</string>\n<string name=\"http_path\">HTTP-путь</string>\n<string name=\"http_upgrade_host\">HTTP-хост обновления</string>\n<string name=\"http_upgrade_path\">HTTP-путь обновления</string>\n<string name=\"hysteria_auth_payload\">Полезные данные аутентификации</string>\n<string name=\"hysteria_auth_type\">Тип аутентификации</string>\n<string name=\"hysteria_connection_receive_window\">Окно получения соединения QUIC</string>\n<string name=\"hysteria_disable_mtu_discovery\">Отключить обнаружение MTU пути</string>\n<string name=\"hysteria_download_mbps\">Максимальная скорость загрузки (в Мбит / с)</string>\n<string name=\"hysteria_obfs\">Обфускация пароля</string>\n<string name=\"hysteria_stream_receive_window\">Окно приема потока QUIC</string>\n<string name=\"hysteria_upload_mbps\">Максимальная скорость загрузки (в Мбит / с)</string>\n<string name=\"icon_content_description\">Значок диалогового окна</string>\n<string name=\"ignore_battery_optimizations\">Игнорировать оптимизации батареи</string>\n<string name=\"ignore_battery_optimizations_sum\">Снять некоторые ограничения</string>\n<string name=\"inbound_settings\">Входящие настройки</string>\n<string name=\"insecure\">Ненадежный</string>\n<string name=\"install_from_fdroid\">Установить с F-Droid</string>\n<string name=\"install_from_play_store\">Установить из Play Store</string>\n<string name=\"invalid_backup_file\">Недействительный файл резервной копии</string>\n<string name=\"invalid_server\">Неверное имя сервера</string>\n<string name=\"invert_selections\">Инвертировать выбранное</string>\n<string name=\"ipv6\">Маршрут IPv6</string>\n<string name=\"is_outbound_only\">Сделать JSON исходящим</string>\n<string name=\"item_view_role_description\">Вкладка</string>\n<string name=\"key_opt\">Ключ (необязательно)</string>\n<string name=\"landing_proxy\">Обратный прокси</string>\n<string name=\"license\">Лицензия</string>\n<string name=\"lines\">%d строк</string>\n<string name=\"log_level\">Тип журнала</string>\n<string name=\"log_level_help\">Удерживайте для настройки размера буфера.</string>\n<string name=\"logcat\">Экспорт отладочной информации</string>\n<string name=\"mal_close\">Закрыть</string>\n<string name=\"material_clock_toggle_content_description\">Выберите AM (до полудня) или PM (после полудня)</string>\n<string name=\"material_timepicker_am\">AM</string>\n<string name=\"material_timepicker_pm\">PM</string>\n<string name=\"menu_about\">О приложении</string>\n<string name=\"menu_configuration\">Конфигурация</string>\n<string name=\"menu_dashboard\">Панель sing-box</string>\n<string name=\"menu_group\">Группы</string>\n<string name=\"menu_log\">Журналы</string>\n<string name=\"menu_route\">Маршруты</string>\n<string name=\"menu_tools\">Инструменты</string>\n<string name=\"menu_traffic\">Трафик</string>\n<string name=\"metered\">Подсказка о соединении с лимитным тарифным планом</string>\n<string name=\"metered_summary\">Подсказывать системе, что VPN следует рассматривать как сеть с лимитным тарифным планом</string>\n<string name=\"minimize\">Свернуть</string>\n<string name=\"missing_plugin\">Отсутствующий плагин</string>\n<string name=\"move\">Переместить</string>\n<string name=\"mtrl_badge_numberless_content_description\">Новое уведомление</string>\n<string name=\"mtrl_checkbox_state_description_checked\">Флажок установлен.</string>\n<string name=\"mtrl_checkbox_state_description_indeterminate\">Флажки установлены частично.</string>\n<string name=\"mtrl_checkbox_state_description_unchecked\">Флажок не установлен.</string>\n<string name=\"mtrl_chip_close_icon_content_description\">Удалить \"%1$s\"</string>\n<string name=\"mtrl_exceed_max_badge_number_content_description\">Новых уведомлений больше %1$d</string>\n<string name=\"mtrl_picker_a11y_next_month\">Перейти к следующему месяцу</string>\n<string name=\"mtrl_picker_a11y_prev_month\">Перейти к предыдущему месяцу</string>\n<string name=\"mtrl_picker_cancel\">Отмена</string>\n<string name=\"mtrl_picker_confirm\">ОК</string>\n<string name=\"mtrl_picker_day_of_week_column_header\">Столбец со днями недели: %1$s</string>\n<string name=\"mtrl_picker_navigate_to_current_year_description\">Перейти к текущему году: %1$d</string>\n<string name=\"mtrl_picker_navigate_to_year_description\">Перейти к %1$s году</string>\n<string name=\"mtrl_picker_save\">Сохранить</string>\n<string name=\"mtrl_picker_toggle_to_calendar_input_mode\">Перейти в режим выбора дней</string>\n<string name=\"mtrl_picker_toggle_to_day_selection\">Нажмите, чтобы перейти к выбору дня</string>\n<string name=\"mtrl_picker_toggle_to_text_input_mode\">Перейти в режим ввода текста</string>\n<string name=\"mtrl_picker_toggle_to_year_selection\">Нажмите, чтобы перейти к выбору года</string>\n<string name=\"mtu_help\">Удерживайте для настройки своего MTU.</string>\n<string name=\"mux_concurrency\">Число одновременных подключений Mux</string>\n<string name=\"mux_preference\">Мультиплекс</string>\n<string name=\"mux_sum\">Протокол мультиплексирования предназначен для уменьшения задержки соединения TCP, а не для увеличения пропускной способности. Использование мультиплексирования для просмотра видео, загрузки или проверки скорости обычно неэффективно. Если сервер не поддерживает мультиплексирование, вы не сможете получить доступ к Интернету.</string>\n<string name=\"mux_type\">Протокол мультиплексирования</string>\n<string name=\"naive_insecure_concurrency\">Небезопасный параллелизм</string>\n<string name=\"naive_insecure_concurrency_summary\">Использовать N параллельных соединений для повышения отказоустойчивости в условиях ненадёжной сети. Большее число соединений увеличивает риск обнаружения туннелей и снижает безопасность. Этот проект стремится к максимальной защите от анализа трафика, следовательно, использование его небезопасным образом противоречит данной цели. \n \nЕсли придётся использовать данную функцию, попробуйте N=2, чтобы проверить, решит ли это проблему. Настоятельно не рекомендуется использовать более 4 соединений.</string>\n<string name=\"nat_result_hint\">Провести проверку NAT</string>\n<string name=\"nat_stun_server_hint\">STUN сервер</string>\n<string name=\"need_reload\">Перезагрузите прокси-сервис, чтобы применить изменения</string>\n<string name=\"need_restart\">Перезапустите приложение для применения изменений</string>\n<string name=\"network\">Сеть</string>\n<string name=\"network_change_reset_connections\">Сбросить исходящие соединения при изменении сети</string>\n<string name=\"night_mode\">Ночной режим</string>\n<string name=\"no\">Нет</string>\n<string name=\"no_proxies_found\">В ссылке не найдено прокси-серверов</string>\n<string name=\"no_proxies_found_in_clipboard\">В буфере обмена не найдено ни одного прокси</string>\n<string name=\"no_proxies_found_in_file\">В файле не найдено прокси-серверов</string>\n<string name=\"no_proxies_found_in_subscription\">В подписке не найдено прокси-серверов</string>\n<string name=\"no_statistics\">Статистики пока нет</string>\n<string name=\"not_connected\">Не подключен</string>\n<string name=\"not_set\">Не указано</string>\n<string name=\"obfs\">Obfs</string>\n<string name=\"obfs_param\">Параметры Obfs</string>\n<string name=\"off\">Выключенный</string>\n<string name=\"on\">Включено</string>\n<string name=\"only\">Исключительно</string>\n<string name=\"ooc_warning\">Предупреждение</string>\n<string name=\"oss_licenses\">Лицензии с открытым исходным кодом</string>\n<string name=\"packet_encoding\">Кодирование пакетов</string>\n<string name=\"padding\">Отступ</string>\n<string name=\"password\">Пароль</string>\n<string name=\"password_opt\">Пароль (необязательно)</string>\n<string name=\"password_toggle_content_description\">Показать пароль</string>\n<string name=\"plugin\">Плагин</string>\n<string name=\"plugin_auto_connect_unlock_only\">Этот плагин может не работать с автоподключением</string>\n<string name=\"plugin_configure\">Настроить…</string>\n<string name=\"plugin_disabled\">Отключено</string>\n<string name=\"plugin_exists_but_on_shit_system\">Профиль %s требует плагин %s, но поставщик вашего проприетарного оборудования (обычно это большие компании, занимающиеся слежкой и производители вредоносного ПО) изменил ваш Android, сделав плагин непригодным для использования.</string>\n<string name=\"plugin_unknown\">Неизвестный плагин %s</string>\n<string name=\"plugin_untrusted\">Предупреждение: похоже, что этот плагин не из известного надежного источника.</string>\n<string name=\"port_http\">Порт прокси HTTP</string>\n<string name=\"port_local_dns\">Локальный порт DNS</string>\n<string name=\"port_proxy\">Порт прокси SOCKS5</string>\n<string name=\"port_transproxy\">Транспрокси-порт</string>\n<string name=\"prefer\">Предпочитать</string>\n<string name=\"preference_copied\">Текст \"%1$s\" скопирован в буфер обмена</string>\n<string name=\"preview_version_hint\">Это приложение является предварительной версией и может содержать множество проблем. Если вы не хотите тестировать его, скачайте релизную версию с GitHub!</string>\n<string name=\"profile_config\">Конфигурация профиля</string>\n<string name=\"profile_empty\">Пожалуйста, выберите профиль</string>\n<string name=\"profile_import\">Импортировать профиль</string>\n<string name=\"profile_import_message\">Подтвердите, что хотите импортировать профиль %s?</string>\n<string name=\"profile_name\">Имя профиля</string>\n<string name=\"profile_requiring_plugin\">Профиль %s требует установки подключаемого модуля %s, но он не найден.</string>\n<string name=\"profile_traffic_statistics\">Статистика трафика профиля</string>\n<string name=\"profile_traffic_statistics_summary\">Когда отключено, использованный трафик не будет отображаться</string>\n<string name=\"project\">Проект</string>\n<string name=\"protocol\">Протокол</string>\n<string name=\"protocol_param\">Параметры протокола</string>\n<string name=\"protocol_settings\">Настройки протокола</string>\n<string name=\"protocol_version\">Версия протокола</string>\n<string name=\"proxied_apps\">Режим VPN для приложений</string>\n<string name=\"proxied_apps_summary\">Настроить режим VPN для выбранных приложений</string>\n<string name=\"proxy_cat\">Настройки сервера</string>\n<string name=\"proxy_chain\">Прокси-цепочка</string>\n<string name=\"quick_toggle\">Переключить</string>\n<string name=\"quick_enable\">Включить</string>\n<string name=\"quick_disable\">Выключить</string>\n<string name=\"reboot_required\">Не удалось запустить службу VPN. Возможно, вам потребуется перезагрузить устройство.</string>\n<string name=\"release_wake_lock\">Отключить WakeLock</string>\n<string name=\"remote_dns\">Удаленный DNS</string>\n<string name=\"remove_duplicate\">Удалить дубликаты серверов</string>\n<string name=\"reset_connections\">Сбросить соединения</string>\n<string name=\"reset_settings\">Восстановить настройки по умолчанию</string>\n<string name=\"reset_settings_message\">Восстановить настройки по умолчанию, такие данные, как узлы и группы, будут сохранены. Для полной очистки данных, очистите данные приложения непосредственно в системных настройках.</string>\n<string name=\"resolve_destination\">Определить адрес назначения</string>\n<string name=\"resolve_destination_summary\">Если адрес назначения является доменом, то он передается в соответствии с правилом IPv6.</string>\n<string name=\"route_add\">Создать маршрут</string>\n<string name=\"route_asset_no_update\">Обновлений нет</string>\n<string name=\"route_asset_status\">Локальная версия: %s</string>\n<string name=\"route_asset_updated\">Обновлено</string>\n<string name=\"route_assets\">Ресурсы</string>\n<string name=\"route_block\">Блокировать</string>\n<string name=\"route_bypass\">Обход</string>\n<string name=\"route_bypass_domain\">Правило домена для %s</string>\n<string name=\"route_bypass_ip\">Правило IP для %s</string>\n<string name=\"route_for\">Правило для %s</string>\n<string name=\"route_manage_assets\">Управление ресурсами маршрутов</string>\n<string name=\"route_name\">Название маршрута</string>\n<string name=\"route_need_vpn\">Правило маршрутизации `%s` зависит от VPN, поэтому игнорируется.</string>\n<string name=\"route_not_asset\">Не файл ресурса: ожидался .db, а не %s</string>\n<string name=\"route_opt_block_ads\">Блокировать рекламу</string>\n<string name=\"route_opt_block_analysis\">Блокировать аналитику</string>\n<string name=\"route_opt_block_quic\">Блокировать QUIC</string>\n<string name=\"route_opt_bypass_lan\">Обход LAN</string>\n<string name=\"route_play_store\">Правило Play Store для %s</string>\n<string name=\"route_profile\">Выбрать профиль…</string>\n<string name=\"route_proxy\">Прокси</string>\n<string name=\"route_reset\">Сброс</string>\n<string name=\"route_rules_official\">Официальный</string>\n<string name=\"route_rules_provider\">Поставщик ресурсов правил</string>\n<string name=\"route_warn\">Перед добавлением собственных правил убедитесь, что вы прочитали документацию, иначе подключение к Интернету может не удасться.</string>\n<string name=\"scanning\">Сканирование…</string>\n<string name=\"search_apps\">Поиск…</string>\n<string name=\"search_menu_title\">Поиск</string>\n<string name=\"searchview_clear_text_content_description\">Очистить текстовое поле</string>\n<string name=\"searchview_navigation_content_description\">Назад</string>\n<string name=\"security\">Шифрование транспортного уровня</string>\n<string name=\"security_settings\">Настройки безопасности</string>\n<string name=\"select_apps\">Выберите приложения</string>\n<string name=\"select_profile\">Выбрать профиль</string>\n<string name=\"server_address\">Сервер</string>\n<string name=\"server_port\">Удаленный порт</string>\n<string name=\"service_failed\">Не удалось:</string>\n<string name=\"service_mode\">Сервисный режим</string>\n<string name=\"service_mode_proxy\">Только прокси</string>\n<string name=\"service_proxy\">Прокси-сервис</string>\n<string name=\"service_subscription\">Служба обновления подписки</string>\n<string name=\"service_vpn\">VPN-сервис</string>\n<string name=\"set_panel_url\">Установить URL панель</string>\n<string name=\"settings\">Настройки</string>\n<string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (плагин Shadowsocks для Android)</string>\n<string name=\"shadowsocks_plugin_v2ray\">V2Ray (плагин Shadowsocks для Android)</string>\n<string name=\"share\">Поделиться</string>\n<string name=\"share_qr_nfc\">QR-код</string>\n<string name=\"share_subscription\">Поделиться подпиской</string>\n<string name=\"show_bottom_bar\">Показать нижнюю панель, как в SagerNet</string>\n<string name=\"show_direct_speed\">Показать прямую скорость</string>\n<string name=\"show_direct_speed_sum\">Также в уведомлении показывать скорость трафика без прокси</string>\n<string name=\"show_group_in_notification\">Показывать название группы в уведомлении</string>\n<string name=\"show_system_apps\">Показать системные приложения</string>\n<string name=\"side_sheet_accessibility_pane_title\">Боковая панель</string>\n<string name=\"sni\">SNI (индикация имени сервера)</string>\n<string name=\"sniff_override\">Результат проверки для адрес назначения</string>\n<string name=\"sniff_routing\">Перехватывать результат для Маршрутизации</string>\n<string name=\"speed\">%s/с</string>\n<string name=\"speed_detail\">Прокси ：%1$s↑ %2$s↓ \n\\nПрямой ：%3$s↑%4$s↓</string>\n<string name=\"speed_interval\">Интервал обновления уведомлений о скорости</string>\n<string name=\"ss_cat\">Настройки Shadowsocks</string>\n<string name=\"ssh_auth_type_none\">Без аутентификации</string>\n<string name=\"ssh_private_key\">Закрытый ключ</string>\n<string name=\"ssh_private_key_passphrase\">Парольная фраза закрытого ключа</string>\n<string name=\"ssh_public_key\">Открытый ключ</string>\n<string name=\"standard\">Стандарт</string>\n<string name=\"start\">Старт</string>\n<string name=\"status_bar_notification_info_overflow\">&gt;999</string>\n<string name=\"stop\">Остановить</string>\n<string name=\"stopping\">Выключение…</string>\n<string name=\"stun_attest_loading\">Пожалуйста, подождите…</string>\n<string name=\"stun_test\">Обнаружение поведения NAT</string>\n<string name=\"stun_test_summary\">Определить поведение сопоставления адресов NAT и поведение фильтрации NAT в соответствии с RFC 5780 при помощи STUN.</string>\n<string name=\"subscription\">Подписка</string>\n<string name=\"subscription_expire\">Истекает: %s</string>\n<string name=\"subscription_import\">Импортировать подписку</string>\n<string name=\"subscription_import_message\">Подтвердите, что хотите импортировать подписку %s? Если вы импортируете из ненадежного источника, это может привести к утечке вашего IP-адреса и такого поведения.</string>\n<string name=\"subscription_settings\">Настройки подписки</string>\n<string name=\"subscription_traffic\">%s Использовано / %s Осталось</string>\n<string name=\"subscription_type\">Тип подписки</string>\n<string name=\"subscription_update\">Обновление подписки</string>\n<string name=\"subscription_update_message\">Обновление %s …</string>\n<string name=\"subscription_used\">%s Использовано</string>\n<string name=\"subscription_user_agent\">UserAgent</string>\n<string name=\"subscriptions\">Подписки</string>\n<string name=\"tcp_connections\">%d TCP-соединений</string>\n<string name=\"tcp_keep_alive_interval\">Интервал доставки пакетов TCP keep-alive</string>\n<string name=\"telegram\">Канал обновлений в Telegram</string>\n<string name=\"test_concurrency\">Число параллельных подключений</string>\n<string name=\"theme\">Тема</string>\n<string name=\"tile_title\">Переключатель</string>\n<string name=\"tls\">Использовать TLS</string>\n<string name=\"tls_camouflage_settings\">Настройки защиты TLS</string>\n<string name=\"tools_network\">Сеть</string>\n<string name=\"traffic_sniffing\">Включить анализ трафика</string>\n<string name=\"trojan_provider\">Провайдер Trojan</string>\n<string name=\"tuic_congestion_controller\">Контроллер перегрузки</string>\n<string name=\"tuic_disable_sni\">Отключить SNI</string>\n<string name=\"tuic_reduce_rtt\">Включить 0-RTT QUIC-соединение</string>\n<string name=\"tuic_udp_relay_mode\">Режим UDP-ретранслятора</string>\n<string name=\"tun_implementation\">Реализация TUN</string>\n<string name=\"udp_connections\">%d UDP-соединений</string>\n<string name=\"unavailable\">Недоступен</string>\n<string name=\"undo\">Отменить</string>\n<string name=\"unsaved_changes_prompt\">Есть несохраненные изменения. Сохранить?</string>\n<string name=\"update_all_subscription\">Обновить все подписки</string>\n<string name=\"update_current_subscription\">Обновить подписку текущей группы</string>\n<string name=\"update_dialog_message\">Текущая версия: %1$s\\nДоступная версия: %2$s\\nХотите загрузить её?</string>\n<string name=\"update_dialog_title\">Доступна новая версия</string>\n<string name=\"update_settings\">Обновить настройки</string>\n<string name=\"update_subscription_warning\">Прокси-сервер не подключен, вы уверены, что хотите продолжить обновление?</string>\n<string name=\"update_when_connected_only\">Обновлять только при подключении</string>\n<string name=\"update_when_connected_only_sum\">Предотвратить утечку IP-адреса</string>\n<string name=\"use_selector\">Использовать селектор</string>\n<string name=\"username\">Имя пользователя</string>\n<string name=\"username_opt\">Имя пользователя (необязательно)</string>\n<string name=\"utls_fingerprint\">Отпечаток uTLS</string>\n<string name=\"uuid\">Логин пользователя</string>\n<string name=\"v7_preference_off\">ВЫКЛ.</string>\n<string name=\"v7_preference_on\">ВКЛ.</string>\n<string name=\"version_x\">Версия (%s)</string>\n<string name=\"vpn_connected\">Подключено, нажмите для проверки подключения</string>\n<string name=\"vpn_permission_denied\">Отказано в разрешении на создание службы VPN</string>\n<string name=\"wake_reset_connections\">Сбросить исходящие соединения при выходе из спящего режима</string>\n<string name=\"warp_generate\">Создать конфигурацию</string>\n<string name=\"warp_license\">CloudFlare Warp - это бесплатный VPN-провайдер WireGuard. Используя его, вы соглашаетесь с условиями использования.</string>\n<string name=\"wireguard_local_address\">Частный адрес</string>\n<string name=\"wireguard_psk\">Пароль сервера</string>\n<string name=\"wireguard_public_key\">Открытый ключ сервера</string>\n<string name=\"ws_browser_forwarding\">Пересылка через браузер</string>\n<string name=\"ws_browser_forwarding_sum\">Перенаправьте соответствующие веб-сокеты через браузер.</string>\n<string name=\"ws_host\">Хост WebSocket</string>\n<string name=\"ws_max_early_data\">Максимальный объём данных</string>\n<string name=\"ws_path\">Путь к WebSocket</string>\n<string name=\"xtls_flow\">Flow (подпротокол VLESS)</string>\n<string name=\"yes\">Да</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-tr/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"version_x\">Sürüm (%s)</string>\n    <string name=\"app_version\">Sürüm</string>\n    <string name=\"oss_licenses\">Açık kaynak lisansları</string>\n    <string name=\"telegram\">Telegram güncelleme kanalı</string>\n    <string name=\"github\">Kaynak kodu</string>\n    <string name=\"project\">Proje</string>\n    <string name=\"app_desc\">Android için Kotlin ile yazılmış evrensel vekil sunucu araç zinciri.</string>\n    <string name=\"enable_dns_routing\">DNS Yönlendirmesini Etkinleştir</string>\n    <string name=\"direct_dns\">Direkt DNS</string>\n    <string name=\"remote_dns\">Uzak DNS</string>\n    <string name=\"cag_dns\">DNS Ayarları</string>\n    <string name=\"connection_test_error_status_code\">Hata kodu: #%d</string>\n    <string name=\"connection_test_fail\">Internet Bağlantısı Yok</string>\n    <string name=\"connection_test_error\">Başarısız: %s</string>\n    <string name=\"connection_test_available_http\">Başarılı: HTTP el sıkışması %dms sonra gerçekleşti</string>\n    <string name=\"connection_test_available\">Başarılı: HTTPS el sıkışması %dms sonra gerçekleşti</string>\n    <string name=\"connection_test_testing\">Test ediliyor…</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"speed_detail\">Vekil Sunucu： %1$s↑ %2$s↓\n\\nDirekt： %3$s↑ %4$s↓</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"allow_insecure_sum\">Sertifika doğrulamasını devre dışı bırakır, Etkinleştirildikten sonra yapılandırma düz metin olarak korunur</string>\n    <string name=\"allow_insecure\">Güvenliksiz Bağlantıyı Etkinleştir</string>\n    <string name=\"security_settings\">Güvenlik Ayarları</string>\n    <string name=\"show_direct_speed_sum\">Vekil sunucu olmadan oluşan veri trafiği hızını da göster</string>\n    <string name=\"show_direct_speed\">Direkt Hızı Göster</string>\n    <string name=\"show_stop_sum\">Hızlı Ayarlar kısayolunu anahtar olarak kullanmak istemiyorsanız kullanın</string>\n    <string name=\"show_stop\">Durdur Butonunu Göster</string>\n    <string name=\"cag_misc\">Diğer Ayarlar</string>\n    <string name=\"speed_interval\">Hız Bildirimi Güncelleme Aralığı</string>\n    <string name=\"ws_browser_forwarding_sum\">İlgili WebSocket bağlantılarını tarayıcı üzerinden yönlendir.</string>\n    <string name=\"ws_browser_forwarding\">Tarayıcı Yönlendirmesi</string>\n    <string name=\"cag_ws\">WebSocket Ayarları</string>\n    <string name=\"require_http\">HTTP üzerinden istekleri etkinleştir</string>\n    <string name=\"general_settings\">Uygulama Ayarları</string>\n    <string name=\"inbound_settings\">Geliş Ayarları</string>\n    <string name=\"allow_access_sum\">Gelen sunucu isteklerini 0.0.0.0 üzerine bağla</string>\n    <string name=\"allow_access\">Yerel ağdan bağlantılara izin ver</string>\n    <string name=\"cag_route\">Rota Ayarları</string>\n    <string name=\"port_transproxy\">Transproxy Portu</string>\n    <string name=\"port_http\">HTTP Vekil Sunucu Portu</string>\n    <string name=\"port_proxy\">SOCKS5 Vekil Sunucu Portu</string>\n    <string name=\"service_mode_proxy\">Sadece vekil sunucu</string>\n    <string name=\"service_mode\">Servis Modu</string>\n    <string name=\"group_duplicate\">%s\n\\ntekrarlı</string>\n    <string name=\"group_deleted\">%s\n\\nsilindi</string>\n    <string name=\"group_changed\">%s\n\\ngüncellendi</string>\n    <string name=\"group_added\">%s\n\\neklendi</string>\n    <string name=\"group_delete_confirm_prompt\">Bu grubu silmek istediğinize emin misiniz\\?</string>\n    <string name=\"group_diff\">Fark (%s)</string>\n    <string name=\"group_show_diff\">Fark</string>\n    <string name=\"group_updated\">%s: %d vekil sunucu güncellendi</string>\n    <string name=\"group_no_difference\">%s: Farklılık yok</string>\n    <string name=\"group_status_proxies_subscription\">%d Vekil Sunucu | %s güncellendi</string>\n    <string name=\"group_status_proxies\">%d Vekil Sunucu</string>\n    <string name=\"group_status_empty_subscription\">Henüz güncellenmedi</string>\n    <string name=\"group_status_empty\">Boş grup</string>\n    <string name=\"group_edit_subscription\">Aboneliği düzenle</string>\n    <string name=\"group_edit\">Grubu düzenle</string>\n    <string name=\"group_create_subscription\">Abonelik oluştur</string>\n    <string name=\"group_create\">Grup oluştur</string>\n    <string name=\"group_subscription_link\">Abonelik Linki</string>\n    <string name=\"group_name_required\">Grup adı gerekli</string>\n    <string name=\"group_update\">Güncelle</string>\n    <string name=\"tile_title\">Değiştirici</string>\n    <string name=\"group_name\">Grup adı</string>\n    <string name=\"quick_toggle\">Aç/Kapat</string>\n    <string name=\"quick_enable\">Etkinleştir</string>\n    <string name=\"quick_disable\">Devre Dışı Bırak</string>\n    <string name=\"group_default\">Gruplandırılmamış</string>\n    <string name=\"document\">Döküman</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"menu_about\">Hakkında</string>\n    <string name=\"menu_group\">Grup</string>\n    <string name=\"menu_configuration\">Yapılandırma</string>\n    <string name=\"logcat\">Hata ayıklama bilgisini dışa aktar</string>\n    <string name=\"username\">Kullanıcı adı</string>\n    <string name=\"server_port\">Uzak Bağlantı Noktası</string>\n    <string name=\"server_address\">Sunucu</string>\n    <string name=\"profile_name\">Profil ismi</string>\n    <string name=\"connection_test_url\">Bağlantı Testi URL\\'si</string>\n    <string name=\"transproxy_mode\">Transproxy Modu</string>\n    <string name=\"require_transproxy\">Transproxy Gelenini Etkinleştir</string>\n    <string name=\"port_local_dns\">Yerel DNS Bağlantı Noktası</string>\n    <string name=\"dns_hosts\">Etki alanı yeniden yazma</string>\n    <string name=\"fakedns_message\">Proxy durdurulduktan sonra ağa yeniden bağlanmak için diğer uygulamaların yeniden başlatılmasına neden olabilir</string>\n    <string name=\"enable_fakedns\">Sahte DNS aktivasyonu</string>\n    <string name=\"dns_routing_message\">Doğrudan DNS ile geçiş yollarındaki etki alanlarını çözümleyin. Potansiyel DNS sızıntılarının farkında olun</string>\n    <string name=\"certificates\">Sertifikalar</string>\n    <string name=\"sni\">Görünecek Sunucu Adı</string>\n    <string name=\"tls\">TLS kullan</string>\n    <string name=\"http_path\">HTTP Konumu</string>\n    <string name=\"http_host\">HTTP Sunucu</string>\n    <string name=\"ws_path\">WebSocket Konumu</string>\n    <string name=\"ws_host\">WebSocket Sunucusu</string>\n    <string name=\"header_type\">Başlık Türü</string>\n    <string name=\"network\">Ağ</string>\n    <string name=\"security\">Taşıma katmanı şifrelemesi</string>\n    <string name=\"uuid\">Kullanıcı ID</string>\n    <string name=\"obfs_param\">Şaşırtmaca Parametresi</string>\n    <string name=\"protocol_param\">Protokol Parametresi</string>\n    <string name=\"protocol\">Protokol</string>\n    <string name=\"enc_method\">Şifreleme Metodu</string>\n    <string name=\"password\">Parola</string>\n    <string name=\"key_opt\">Anahtar (İsteğe bağlı)</string>\n    <string name=\"password_opt\">Parola (İsteğe bağlı)</string>\n    <string name=\"username_opt\">Kullanıcı adı (İsteğe bağlı)</string>\n    <string name=\"menu_traffic\">Trafik</string>\n    <string name=\"route_reset\">Sıfırla</string>\n    <string name=\"route_bypass_ip\">%s için IP kuralı</string>\n    <string name=\"route_bypass_domain\">%s için etki alanı kuralı</string>\n    <string name=\"empty_route_notice\">Kaydetmeden önce bazı kurallar ayarlayın</string>\n    <string name=\"empty_route\">Boş Rota</string>\n    <string name=\"route_profile\">Profil Seç…</string>\n    <string name=\"route_block\">Engelle</string>\n    <string name=\"route_proxy\">Vekil Sunucu</string>\n    <string name=\"route_name\">Rota Adı</string>\n    <string name=\"delete_route_prompt\">Bu rotayı kaldırmak istediğinize emin misiniz\\?</string>\n    <string name=\"route_add\">Rota Oluştur</string>\n    <string name=\"metered\">Kota Belirteci</string>\n    <string name=\"metered_summary\">Sisteme VPN\\'i kotalı olarak belirt</string>\n    <string name=\"menu_route\">Rota</string>\n    <string name=\"only\">Sadece</string>\n    <string name=\"prefer\">Öner</string>\n    <string name=\"ipv6\">IPv6 Rotası</string>\n    <string name=\"edit_config\">Yapılandırmayı Düzenle</string>\n    <string name=\"config_type\">Yapılandırma Türü</string>\n    <string name=\"extra_headers\">Ekstra Başlıklar</string>\n    <string name=\"encryption\">Şifreleme</string>\n    <string name=\"early_data_header_name\">Erken Veri Başlığı Adı</string>\n    <string name=\"bypass_apps\">Atlat</string>\n    <string name=\"route_opt_bypass_lan\">Yerel Ağı Atlat</string>\n    <string name=\"route_bypass\">Atlat</string>\n    <string name=\"route_need_vpn\">%s yönlendirme kuralı VPN\\'in etkin olmasına bağlıdır ve bu nedenle görmezden gelindi.</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (Shadowsocks Android Eklentisi)</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Basit Şaşırtmaca (Shadowsocks Android Eklentisi)</string>\n    <string name=\"hysteria_connection_receive_window\">QUIC Bağlantı Alım Penceresi</string>\n    <string name=\"hysteria_stream_receive_window\">QUIC Akış Alım Penceresi</string>\n    <string name=\"force_resolve_sum\">Güncelleme yaparken bütün alan adlarının IP adreslerini çöz. Mümkünse sunucu ve SNI otomatik olarak eklenir</string>\n    <string name=\"always_show_address_sum\">Yapılandırma kartının üzerinde her zaman sunucu adresini göster</string>\n    <string name=\"probe_interval\">Dengeleyici gözlem aralığı</string>\n    <string name=\"add_profile_methods_manual_settings\">Elle Ayar Ekle</string>\n    <string name=\"profile_config\">Profil yapılandırması</string>\n    <string name=\"file_manager_missing\">Cihazınızda dosya yöneticisi bulunamadı, lütfen bir tane kurun, örneğin Material Files.</string>\n    <string name=\"action_from_link\">Abonelikten</string>\n    <string name=\"circular_reference_sum\">Rota kendisini içinde barındıramaz.</string>\n    <string name=\"circular_reference\">Dairesel referans</string>\n    <string name=\"config_settings\">Yapılandırma Ayarları</string>\n    <string name=\"chain_settings\">Zincir Ayarları</string>\n    <string name=\"proxy_chain\">Vekil Sunucu Zinciri</string>\n    <string name=\"plugin_configure\">Yapılandır…</string>\n    <string name=\"custom_config\">Kişisel Yapılandırma</string>\n    <string name=\"route_for\">%s için kural</string>\n    <string name=\"app_no_launcher\">Bu uygulama herhangi bir arayüze sahip değil.</string>\n    <string name=\"copy_failed\">Kopyalama başarısız oldu.</string>\n    <string name=\"copy_success\">Kopyalama başarılı!</string>\n    <string name=\"create_rule\">Kural Oluştur</string>\n    <string name=\"open_market\">Mağazayı Aç</string>\n    <string name=\"open_settings\">Ayarları Aç</string>\n    <string name=\"open_app\">Uygulamayı Aç</string>\n    <string name=\"copy_package_name\">Paket Adını Kopyala</string>\n    <string name=\"copy_label\">İsmi Kopyala</string>\n    <string name=\"udp_connections\">%d UDP bağlantı</string>\n    <string name=\"tcp_connections\">%d TCP bağlantı</string>\n    <string name=\"plugin_exists_but_on_shit_system\">Profil %s %s eklentisine ihtiyaç duyuyor, ancak kapalı kaynak ekipman sağlayıcınız (genellikle sermaye devleri ve kötü amaçlı yazılım geliştiricileri) Android ile oynamış ve eklenti bu yüzden kullanılamıyor.</string>\n    <string name=\"group_order_by_delay\">Gecikme</string>\n    <string name=\"group_order_by_name\">İsim</string>\n    <string name=\"group_order_origin\">Köken</string>\n    <string name=\"group_order\">Sıralama</string>\n    <string name=\"experimental_settings\">Deneysel</string>\n    <string name=\"hysteria_download_mbps\">En Fazla İndirme Hızı (Mbps cinsinden)</string>\n    <string name=\"hysteria_upload_mbps\">En Fazla Yükleme Hızı (Mbps cinsinden)</string>\n    <string name=\"hysteria_auth_type\">Doğrulama Türü</string>\n    <string name=\"apps_message\">%d Uygulama</string>\n    <string name=\"select_apps\">Uygulama Seç</string>\n    <string name=\"apps\">Uygulamalar</string>\n    <string name=\"clear_profiles_message\">Bu grubu temizlemek istediğinize emin misiniz\\?</string>\n    <string name=\"profile_import_message\">%s profilini gerçekten içe aktarmak istiyor musunuz\\?</string>\n    <string name=\"profile_import\">Profili içe aktar</string>\n    <string name=\"subscription_import_message\">%s aboneliğini içe aktarmayı gerçekten istiyor musunuz\\? Eğer güvenilir olmayan bir kaynaktan geliyorsanız bu IP adresinizin ve davranışınızın sızdırılmasına sebep olabilir.</string>\n    <string name=\"subscription_import\">Aboneliği içe aktar</string>\n    <string name=\"subscription_traffic\">%s Kullanıldı / %s Kaldı</string>\n    <string name=\"subscription_used\">%s Kullanıldı</string>\n    <string name=\"group_filter\">Filtre</string>\n    <string name=\"subscription_update_message\">%s güncelleniyor …</string>\n    <string name=\"subscription_update\">Abonelik Güncellemesi</string>\n    <string name=\"service_subscription\">Abonelik Güncelleme Servisi</string>\n    <string name=\"ooc_missing_protocol\">Bu abonelik %s protokolü için destek gerektirir ama bulunamıyor. Desteklenmeyen profiller görmezden gelinir.</string>\n    <string name=\"ooc_warning\">Uyarı</string>\n    <string name=\"update_subscription_warning\">Vekil sunucu bağlanmadı, güncellemeye devam etmek istediğinize emin misiniz\\?</string>\n    <string name=\"ooc_subscription_token_invalid\">OOCv1 Anahtarı Geçersiz</string>\n    <string name=\"download\">İndir</string>\n    <string name=\"install_from_fdroid\">F-Droid\\'den Kur</string>\n    <string name=\"install_from_play_store\">Play Store\\'dan Kur</string>\n    <string name=\"action_download\">İNDİR</string>\n    <string name=\"action_learn_more\">DAHA FAZLASINI ÖĞREN</string>\n    <string name=\"profile_requiring_plugin\">Profil %s %s eklentisinin kurulmasını gerektirir ama kurulu değil.</string>\n    <string name=\"missing_plugin\">Eksik Eklenti</string>\n    <string name=\"confirm\">Doğrula</string>\n    <string name=\"subscription_user_agent\">Kullanıcı Kimliği</string>\n    <string name=\"update_when_connected_only_sum\">IP adresinin sızdırılmasını önle</string>\n    <string name=\"update_when_connected_only\">Sadece bağlanıldığında güncelle</string>\n    <string name=\"auto_update_delay\">Otomatik Güncelleme Gecikmesi (Dakika cinsinden)</string>\n    <string name=\"auto_update\">Otomatik Güncelle</string>\n    <string name=\"update_settings\">Güncelleme Ayarları</string>\n    <string name=\"raw\">Ham</string>\n    <string name=\"deduplication_sum\">Güncellerken tekrarlanan yapılandırmaları kaldır</string>\n    <string name=\"force_resolve\">Çözmeye Zorla</string>\n    <string name=\"delete_group_prompt\">Bu grubu kaldırmak istediğinize emin misiniz\\?</string>\n    <string name=\"subscription_type\">Abonelik Türü</string>\n    <string name=\"group_type\">Grup Türü</string>\n    <string name=\"subscription_settings\">Abonelik Ayarları</string>\n    <string name=\"subscription\">Abonelik</string>\n    <string name=\"group_settings\">Grup Ayarları</string>\n    <string name=\"group_basic\">Basit</string>\n    <string name=\"trojan_provider\">Trojan Sağlayıcısı</string>\n    <string name=\"protocol_settings\">Protokol Ayarları</string>\n    <string name=\"append_http_proxy_sum\">HTTP vekil sunucu direkt tarayıcı veya bazı destekli uygulamalar tarafından, sanal NIC aygıtından geçmeden kullanılır (Android 10+)</string>\n    <string name=\"append_http_proxy\">HTTP Vekil Sunucuyu VPN\\'e Dahil Et</string>\n    <string name=\"connection_test_timeout\">Zaman Aşımı</string>\n    <string name=\"connection_test_unreachable\">Erişilemez</string>\n    <string name=\"connection_test_refused\">Bağlantı reddedildi</string>\n    <string name=\"connection_test_domain_not_found\">Alan adı bulunamadı</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing kullanılamıyor</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing kullanılamıyor</string>\n    <string name=\"connection_test_clear_results\">Test sonuçlarını temizle</string>\n    <string name=\"connection_test\">Bağlantı testi</string>\n    <string name=\"clear_traffic_statistics\">Trafik istatistiklerini temizle</string>\n    <string name=\"always_show_address\">Her Zaman Adresi Göster</string>\n    <string name=\"unavailable\">Kullanılamıyor</string>\n    <string name=\"standard\">Standart</string>\n    <string name=\"api_port\">API Portu</string>\n    <string name=\"list\">Liste</string>\n    <string name=\"random\">Rastgele</string>\n    <string name=\"enable_log_sum\">Hata ayıklama amaçlıdır</string>\n    <string name=\"enable_log\">Kayıtları Etkinleştir</string>\n    <string name=\"auto\">Otomatik</string>\n    <string name=\"disable\">Devre Dışı Bırak</string>\n    <string name=\"enable\">Etkinleştir</string>\n    <string name=\"follow_system\">Sistemi Takip Et</string>\n    <string name=\"night_mode\">Gece Modu</string>\n    <string name=\"lines\">%d Satır</string>\n    <string name=\"route_warn\">Kişisel kurallar eklemeden önce dökümanı okuduğunuza emin olun, yoksa Internet\\'e bağlanamayabilirsiniz.</string>\n    <string name=\"license\">Lisans</string>\n    <string name=\"need_reload\">Değişiklikleri uygulamak için vekil sunucu servisini yeniden başlatın</string>\n    <string name=\"apply\">Uygula</string>\n    <string name=\"no\">Hayır</string>\n    <string name=\"yes\">Evet</string>\n    <string name=\"unsaved_changes_prompt\">Değişiklikler kaydedilmedi. Kaydetmek ister misiniz\\?</string>\n    <string name=\"ss_cat\">Shadowsocks Ayarları</string>\n    <string name=\"proxy_cat\">Sunucu Ayarları</string>\n    <string name=\"plugin_auto_connect_unlock_only\">Bu eklenti Otomatik Bağlan ile çalışmayabilir</string>\n    <string name=\"plugin_untrusted\">Uyarı: Bu eklenti bilinen güvenilir bir kaynaktan geliyora benzemiyor.</string>\n    <string name=\"plugin_unknown\">Bilinmeyen eklenti %s</string>\n    <string name=\"plugin_disabled\">Devre Dışı</string>\n    <string name=\"plugin\">Eklenti</string>\n    <string name=\"ignore_battery_optimizations_sum\">Bazı kısıtlamaları kaldır</string>\n    <string name=\"ignore_battery_optimizations\">Pil optimizasyonlarını devre dışı bırak</string>\n    <string name=\"donate_info\">Paraya aşığım</string>\n    <string name=\"donate\">Bağış yap</string>\n    <string name=\"deduplication\">Tekilleştirme</string>\n    <string name=\"no_proxies_found_in_clipboard\">Kopyalama panosunda vekil sunucu bulunamadı</string>\n    <string name=\"no_proxies_found_in_file\">Dosya içinde vekil sunucu bulunamadı</string>\n    <string name=\"no_proxies_found_in_subscription\">Abonelik içinde vekil sunucu bulunamadı</string>\n    <string name=\"no_proxies_found\">Link içinde vekil sunucu bulunamadı</string>\n    <string name=\"error_title\">Hata</string>\n    <string name=\"cleartext_http_warning\">Düz metin HTTP trafiği güvenli değil</string>\n    <string name=\"subscriptions\">Abonelikler</string>\n    <string name=\"not_connected\">Bağlanmadı</string>\n    <string name=\"vpn_connected\">Bağlandı, bağlantıyı test etmek için dokunun</string>\n    <string name=\"connecting\">Bağlanıyor…</string>\n    <string name=\"insecure\">Güvenli Değil</string>\n    <string name=\"deprecated\">Geliştirmesi Durdurulmuş</string>\n    <string name=\"undo\">Geri Al</string>\n    <plurals name=\"added\">\n        <item quantity=\"one\">Eklendi</item>\n        <item quantity=\"other\">%d vekil sunucu eklendi</item>\n    </plurals>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">Kaldırıldı</item>\n        <item quantity=\"other\">%d öğe kaldırıldı</item>\n    </plurals>\n    <string name=\"add_profile_methods_scan_qr_code\">QR Kodu Tara</string>\n    <string name=\"share_qr_nfc\">QR kodu</string>\n    <string name=\"delete_confirm_prompt\">Bu profili kaldırmak istediğinize emin misiniz\\?</string>\n    <string name=\"delete\">Kaldır</string>\n    <string name=\"action_import_err\">İçe aktarma başarısız oldu.</string>\n    <string name=\"action_import_msg\">Başarıyla içe aktarıldı!</string>\n    <string name=\"action_export_err\">Dışa aktarma başarısız oldu.</string>\n    <string name=\"action_export_msg\">Başarıyla dışa aktarıldı!</string>\n    <string name=\"action_import_file\">Dosyadan aktar</string>\n    <string name=\"action_import\">Kopyalama Panosundan Aktar</string>\n    <string name=\"action_export\">Dışa Aktar</string>\n    <string name=\"action_export_clipboard\">Kopyalama Panosuna Aktar</string>\n    <string name=\"action_export_file\">Dosyaya aktar</string>\n    <string name=\"action_scan_china_apps\">Çin Uygulamalarını Tara</string>\n    <string name=\"action_create_group\">Boş grup</string>\n    <string name=\"clear_profiles\">Temizle</string>\n    <string name=\"balancer_strategy\">Strateji</string>\n    <string name=\"balancer_type\">Tür</string>\n    <string name=\"balancer_settings\">Dengeleyici Ayarları</string>\n    <string name=\"balancer\">Dengeleyici</string>\n    <string name=\"select_profile\">Profil Seç</string>\n    <string name=\"add_profile\">Profil Ekle</string>\n    <string name=\"share\">Paylaş</string>\n    <string name=\"edit\">Düzenle</string>\n    <string name=\"settings\">Ayarlar</string>\n    <string name=\"clipboard_empty\">Kopyalama panosu boş</string>\n    <string name=\"connect\">Bağlan</string>\n    <string name=\"profile_empty\">Lütfen bir profil seçin</string>\n    <string name=\"reboot_required\">VPN servisi oluşturma başarısız oldu. Cihazınızı yeniden başlatmanız gerekiyor olabilir.</string>\n    <string name=\"vpn_permission_denied\">VPN servisi oluşturabilmek için gereken izin isteği reddedildi</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"stopping\">Kapatılıyor…</string>\n    <string name=\"stop\">Durdur</string>\n    <string name=\"service_failed\">Başarısız:</string>\n    <string name=\"invalid_server\">Geçersiz sunucu adı</string>\n    <string name=\"forward_success\">Vekil sunucu başlatıldı.</string>\n    <string name=\"service_proxy\">Vekil Sunucu Servisi</string>\n    <string name=\"service_vpn\">VPN Servisi</string>\n    <string name=\"direct_boot_aware_summary\">Seçili profil bilginiz daha az korunacak</string>\n    <string name=\"direct_boot_aware\">Kilit Ekranında Açıp Kapatmaya İzin Ver</string>\n    <string name=\"auto_connect_summary\">Eğer önceden çalışıyorsa açılışta/uygulama güncellemesinde tekrar vekil sunucuyu etkinleştir</string>\n    <string name=\"auto_connect\">Otomatik Bağlan</string>\n    <string name=\"show_system_apps\">Sistem uygulamalarını göster</string>\n    <string name=\"clear_selections\">Seçimleri temizle</string>\n    <string name=\"invert_selections\">Seçimleri ters çevir</string>\n    <string name=\"scanning\">Taranıyor…</string>\n    <string name=\"search_apps\">Ara…</string>\n    <string name=\"off\">Kapalı</string>\n    <string name=\"on\">Açık</string>\n    <string name=\"proxied_apps_summary\">VPN modunu seçili uygulamalar için ayarla</string>\n    <string name=\"proxied_apps\">Uygulama VPN modu</string>\n    <string name=\"mux_concurrency\">Çoklayıcı Eşzamanlı Bağlantı Sayısı</string>\n    <string name=\"enable_mux\">Çoklayıcıyı Etkinleştir</string>\n    <string name=\"traffic_sniffing\">Trafik İzlemeyi Etkinleştir</string>\n    <string name=\"domain_strategy\">Alan Adı Çözümleme Stratejisi</string>\n    <string name=\"route_opt_block_ads\">Reklamları engelle</string>\n    <string name=\"route_asset_updated\">Güncellendi</string>\n    <string name=\"route_asset_no_update\">Güncelleme yok</string>\n    <string name=\"route_asset_status\">Yerel sürüm: %s</string>\n    <string name=\"route_rules_official\">Resmi</string>\n    <string name=\"alter_id\">Alternatif ID</string>\n    <string name=\"obfs\">Şaşırtmaca</string>\n    <string name=\"tcp_keep_alive_interval\">TCP aktif paket teslim koruma aralığı</string>\n    <string name=\"route_not_asset\">Varlık dosyası değil: .db beklendi ama %s</string>\n    <string name=\"route_rules_provider\">Rota Varlıkları Sağlayıcısı</string>\n    <string name=\"route_assets\">Varlıklar</string>\n    <string name=\"route_manage_assets\">Rota Varlıklarını Yönet</string>\n    <string name=\"alpn\">Uygulama Katmanı Protokol Aşımı</string>\n    <string name=\"grpc_service_name\">gRPC ServisAdı</string>\n    <string name=\"hysteria_disable_mtu_discovery\">Yol MTU Keşfini Devre Dışı Bırak</string>\n    <string name=\"hysteria_auth_payload\">Doğrulama Yükü</string>\n    <string name=\"hysteria_obfs\">Şaşırtmaca Parolası</string>\n    <string name=\"no_statistics\">Henüz istatistik yok</string>\n    <string name=\"clear_logcat\">Günlüğü Temizle</string>\n    <string name=\"menu_log\">Günlükler</string>\n    <string name=\"menu_tools\">Araçlar</string>\n    <string name=\"translate_platform\">Çeviri Platformu</string>\n    <string name=\"ssh_private_key_passphrase\">Gizli Anahtar Parolası</string>\n    <string name=\"ssh_private_key\">Gizli Anahtar</string>\n    <string name=\"ssh_public_key\">Umumi Anahtar</string>\n    <string name=\"ssh_auth_type_none\">Hiçbiri</string>\n    <string name=\"wireguard_psk\">Eş Ön Paylaşımlı Anahtarı</string>\n    <string name=\"wireguard_public_key\">Eş Umumi Anahtarı</string>\n    <string name=\"wireguard_local_address\">Yerel Adres</string>\n    <string name=\"resolve_destination_summary\">Eğer hedef adres bir alan adıysa, IPv6 stratejisine göre iletilir.</string>\n    <string name=\"resolve_destination\">Hedefi Çözümle</string>\n    <string name=\"destination_override_summary\">Sadece yönlendirme için değil, hedef adresi geçersiz kılmak için de yakalanmış alan adını kullan</string>\n    <string name=\"destination_override\">Hedefi Geçersiz Kıl</string>\n    <string name=\"tun_implementation\">TUN Tanımlaması</string>\n    <string name=\"generating\">Oluşturuluyor…</string>\n    <string name=\"warp_generate\">Yapılandırma Oluştur</string>\n    <string name=\"cloudflare_wrap\">Warp</string>\n    <string name=\"profile_traffic_statistics_summary\">Devre dışı bırakıldığında kullanılan trafik sayılmayacak</string>\n    <string name=\"pcap_notice\">Pcap dosyaları %s içine kaydedilecek</string>\n    <string name=\"profile_traffic_statistics\">Profil Trafiği İstatistikleri</string>\n    <string name=\"app_statistics_disabled\">Uygulama Trafiği istatistikleri devre dışı</string>\n    <string name=\"route_play_store\">%s için Play Store kuralı</string>\n    <string name=\"route_opt_block_analysis\">Çözümlemeyi engelle</string>\n    <string name=\"naive_insecure_concurrency\">Güvensiz Eşzamanlılık</string>\n    <string name=\"naive_insecure_concurrency_summary\">Kötü ağ koşullarında daha sağlam olmak için N eşzamanlı tünel bağlantısı kullan. Daha fazla bağlantı, tünellemeyi algılamayı kolaylaştırır ve daha az güvenli hale getirir. Bu proje, trafik çözümlemesine karşı en güçlü güvenlik için çaba göstermektedir. Güvensiz bir şekilde kullanmak amacını bozar.\n\\n\n\\nBunu kullanmanız gerekiyorsa, sorunlarınızı çözüp çözmediğini görmek için önce N=2 değerini deneyin. Burada 4\\'ten fazla bağlantı kullanmamanızı şiddetle tavsiye ederiz.</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-uk/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_name\" translatable=\"false\">NekoBox</string>\n    <string name=\"app_name_long\" translatable=\"false\">NekoBox for Android</string>\n    <string name=\"app_desc\">Універсальний набір інструментів проксі для Android, написаний на Kotlin.</string>\n    <string name=\"project\">Проєкт</string>\n    <string name=\"github\">Вихідний код</string>\n    <string name=\"telegram\">Канал оновлень в Telegram</string>\n    <string name=\"oss_licenses\">Ліцензії з відкритим кодом</string>\n    <string name=\"app_version\">Версія</string>\n    <string name=\"version_x\">Версія (%s)</string>\n    <string name=\"logcat\">Експорт інформації про налагодження</string>\n    <string name=\"menu_configuration\">Конфігурація</string>\n    <string name=\"menu_group\">Група</string>\n    <string name=\"menu_about\">Про додаток</string>\n    <string name=\"theme\">Тема</string>\n    <string name=\"document\">Документація</string>\n    <string name=\"group_default\">Без групи</string>\n    <string name=\"quick_toggle\">Перемикач</string>\n    <string name=\"quick_enable\">Увімкнути</string>\n    <string name=\"quick_disable\">Вимкнути</string>\n    <string name=\"tile_title\">Перемикач</string>\n    <string name=\"menu_traffic\">Трафік</string>\n    <string name=\"menu_dashboard\">Панель керування sing-box</string>\n    <!-- externel -->\n    <string name=\"action_copy\">Копіювати</string>\n    <string name=\"action_open\">Відкрити</string>\n    <!-- group -->\n    <string name=\"group_name\">Назва групи</string>\n    <string name=\"group_update\">Оновити</string>\n    <string name=\"group_subscription_link\">Посилання на підписку</string>\n    <string name=\"group_name_required\">Потрібна назва групи</string>\n    <string name=\"group_create\">Створити групу</string>\n    <string name=\"group_create_subscription\">Створити підписку</string>\n    <string name=\"update_all_subscription\">Оновити всі підписки</string>\n    <string name=\"group_edit\">Редагувати групу</string>\n    <string name=\"group_edit_subscription\">Редагувати підписку</string>\n    <string name=\"group_status_empty\">Порожня група</string>\n    <string name=\"group_status_empty_subscription\">Ще не оновлено</string>\n    <string name=\"group_status_proxies\">%d проксі</string>\n    <string name=\"group_status_proxies_subscription\">%d проксі | Оновлено %s</string>\n    <string name=\"group_no_difference\">%s: Немає різниці</string>\n    <string name=\"group_updated\">%s: Оновлено %d проксі</string>\n    <string name=\"group_show_diff\">Різниця</string>\n    <string name=\"group_diff\">Різниця (%s)</string>\n    <string name=\"group_delete_confirm_prompt\">Ви впевнені, що хочете видалити цю групу?</string>\n    <string name=\"group_added\">Додано: \\n%s</string>\n    <string name=\"group_changed\">Оновлено: \\n%s</string>\n    <string name=\"group_deleted\">Видалено: \\n%s</string>\n    <string name=\"group_duplicate\">Дублікат: \\n%s</string>\n    <!-- misc -->\n    <string name=\"service_mode\">Режим роботи</string>\n    <string name=\"service_mode_proxy\">Тільки проксі</string>\n    <string name=\"service_mode_vpn\" translatable=\"false\">VPN</string>\n    <string name=\"port_proxy\">Порт проксі-сервера SOCKS5</string>\n    <string name=\"port_http\">Проксі-порт HTTP</string>\n    <string name=\"port_transproxy\">Транспроксі-порт</string>\n    <string name=\"cag_route\">Налаштування маршруту</string>\n    <string name=\"allow_access\">Дозволити підключення з локальної мережі</string>\n    <string name=\"allow_access_sum\">Прив’язати вхідні сервери до 0.0.0.0</string>\n    <string name=\"inbound_settings\">Вхідні налаштування</string>\n    <string name=\"general_settings\">Налаштування програми</string>\n    <string name=\"require_http\">Увімкнути вхідний протокол HTTP</string>\n    <string name=\"cag_ws\">Налаштування WebSocket</string>\n    <string name=\"ws_max_early_data\" translatable=\"false\">Max early data</string>\n    <string name=\"ws_browser_forwarding\">Переадресація браузера</string>\n    <string name=\"ws_browser_forwarding_sum\">Пересилайте відповідні WebSockets через браузер.</string>\n    <string name=\"speed_interval\">Інтервал оновлення сповіщення про швидкість</string>\n    <string name=\"cag_misc\">Різні налаштування</string>\n    <string name=\"show_stop\">Показати кнопку зупинки</string>\n    <string name=\"show_stop_sum\">Якщо ви не хочете використовувати Quick Tile як перемикач</string>\n    <string name=\"show_direct_speed\">Показати пряму швидкість</string>\n    <string name=\"show_direct_speed_sum\">У сповіщенні також покажіть швидкість трафіку без проксі-сервера</string>\n    <string name=\"security_settings\">Налаштування безпеки TLS</string>\n    <string name=\"allow_insecure\">Дозволити незахищене</string>\n    <string name=\"allow_insecure_sum\">Вимкнути перевірку сертифікатів. Якщо ввімкнено, ця конфігурація є такою ж безпечною, як і відкритий текст</string>\n    <string name=\"traffic\" translatable=\"false\">%1$s↑ %2$s↓</string>\n    <string name=\"speed_detail\">Проксі: %1$s↑ %2$s↓\\nПрямий: %3$s↑ %4$s↓</string>\n    <string name=\"speed\">%s/с</string>\n    <string name=\"connection_test_testing\">Тестування…</string>\n    <string name=\"connection_test_available\">Успіх: рукостискання HTTPS зайняло %dмс</string>\n    <string name=\"connection_test_available_http\">Успіх: рукостискання HTTP зайняло %dмс</string>\n    <string name=\"connection_test_error\">Не вдалося: %s</string>\n    <string name=\"connection_test_fail\">Інтернет недоступний</string>\n    <string name=\"connection_test_error_status_code\">Код помилки: #%d</string>\n    <string name=\"cag_dns\">Налаштування DNS</string>\n    <string name=\"remote_dns\">Віддалений DNS</string>\n    <string name=\"direct_dns\">Прямий DNS</string>\n    <string name=\"enable_dns_routing\">Увімкнути маршрутизацію DNS</string>\n    <string name=\"dns_routing_message\">Вирішуйте домени в обхідних маршрутах за допомогою Direct DNS. Будьте в курсі можливих витоків DNS</string>\n    <string name=\"enable_fakedns\">Увімкнути FakeDNS</string>\n    <string name=\"fakedns_message\">Може призвести до перезапуску інших програм для повторного підключення до мережі після зупинки проксі</string>\n    <string name=\"dns_hosts\">Переписування домену</string>\n    <string name=\"port_local_dns\">Локальний порт DNS</string>\n    <string name=\"require_transproxy\">Увімкнути вхідний сигнал Transproxy</string>\n    <string name=\"transproxy_mode\">Режим Transproxy</string>\n    <string name=\"connection_test_url\">URL-адреса перевірки з\\'єднання</string>\n    <!-- proxy category -->\n    <string name=\"profile_name\">Ім\\'я профілю</string>\n    <string name=\"server_address\">Сервер</string>\n    <string name=\"server_port\">Віддалений порт</string>\n    <string name=\"username\">Ім\\'я користувача</string>\n    <string name=\"username_opt\">Ім\\'я користувача (необов’язково)</string>\n    <string name=\"password_opt\">Пароль (необов’язково)</string>\n    <string name=\"key_opt\">Ключ (необов’язково)</string>\n    <string name=\"password\">Пароль</string>\n    <string name=\"enc_method\">Метод шифрування</string>\n    <string name=\"protocol\">Протокол</string>\n    <string name=\"protocol_param\">Параметр протоколу</string>\n    <string name=\"obfs\">Obfs</string>\n    <string name=\"obfs_param\">Параметр Obfs</string>\n    <string name=\"uuid\">ідентифікатор користувача</string>\n    <string name=\"alter_id\">Змінити ідентифікатор</string>\n    <string name=\"security\">Шифрування транспортного рівня</string>\n    <string name=\"network\">Мережа</string>\n    <string name=\"header_type\">Тип заголовка</string>\n    <string name=\"ws_host\">Хост WebSocket</string>\n    <string name=\"ws_path\">Шлях WebSocket</string>\n    <string name=\"http_host\">Хост HTTP</string>\n    <string name=\"http_path\">Шлях HTTP</string>\n    <string name=\"grpc_service_name\">ім\\'я служби gRPC</string>\n    <string name=\"tls\">Використовувати TLS</string>\n    <string name=\"sni\">Індикація імені сервера</string>\n    <string name=\"alpn\">Переговори про протокол шару додатків</string>\n    <string name=\"certificates\">Сертифікати</string>\n    <string name=\"early_data_header_name\">Назва раннього заголовка даних</string>\n    <string name=\"encryption\">Шифрування</string>\n    <string name=\"extra_headers\">Додаткові заголовки</string>\n    <string name=\"config_type\">Тип конфігурації</string>\n    <string name=\"edit_config\">Редагувати конфігурацію</string>\n    <!-- feature category -->\n    <string name=\"ipv6\">Маршрут IPv6</string>\n    <string name=\"prefer\">Віддавати перевагу</string>\n    <string name=\"only\">Тільки</string>\n    <string name=\"metered\">Лімітне підключення</string>\n    <string name=\"metered_summary\">Повідомляти системі, що VPN-з\\'єднання є лімітним</string>\n    <string name=\"menu_route\">Маршрут</string>\n    <string name=\"route_add\">Створити маршрут</string>\n    <string name=\"delete_route_prompt\">Ви впевнені, що хочете видалити цей маршрут?</string>\n    <string name=\"route_name\">Назва маршруту</string>\n    <string name=\"route_proxy\">Проксі</string>\n    <string name=\"route_bypass\">В обхід</string>\n    <string name=\"route_block\">Блокувати</string>\n    <string name=\"route_profile\">Вибрати профіль…</string>\n    <string name=\"empty_route\">Порожній маршрут</string>\n    <string name=\"empty_route_notice\">Встановіть правила перед збереженням</string>\n    <string name=\"route_bypass_domain\">Правило домену для %s</string>\n    <string name=\"route_play_store\">Правило Play Store для %s</string>\n    <string name=\"route_bypass_ip\">Правило IP для %s</string>\n    <string name=\"route_reset\">Скинути</string>\n    <string name=\"route_manage_assets\">Керування ресурсами маршрутів</string>\n    <string name=\"route_assets\">Ресурси</string>\n    <string name=\"route_rules_provider\">Постачальник ресурсів правил</string>\n    <string name=\"route_rules_official\">Офіційний</string>\n    <string name=\"route_asset_status\">Локальна версія: %s</string>\n    <string name=\"route_asset_no_update\">Немає оновлень</string>\n    <string name=\"route_asset_updated\">Оновлено</string>\n    <string name=\"route_not_asset\">Це не файл ресурсів: очікувався .db, але отримано %s</string>\n    <string name=\"route_opt_bypass_lan\">Обходити локальну мережу</string>\n    <string name=\"route_opt_block_ads\">Блокувати рекламу</string>\n    <string name=\"route_opt_block_analysis\">Блокувати аналітику</string>\n    <string name=\"route_opt_block_quic\">Блокувати QUIC</string>\n    <string name=\"domain_strategy\">Стратегія визначення доменів</string>\n    <string name=\"traffic_sniffing\">Увімкнути аналіз трафіку</string>\n    <string name=\"enable_mux\">Увімкнути мультиплексор</string>\n    <string name=\"mux_sum\">Mux призначений для зменшення затримки рукостискання TCP, а не для збільшення пропускної здатності з\\'єднання. Використання Mux для перегляду відео, завантаження або тестування швидкості зазвичай є контрпродуктивним. Якщо сервер його не підтримує, ви не зможете отримати доступ до Інтернету.</string>\n    <string name=\"mux_concurrency\">Паралельні з\\'єднання Mux</string>\n    <string name=\"tcp_keep_alive_interval\">Інтервал доставки пакетів TCP keep-alive</string>\n    <string name=\"proxied_apps\">Програми в режимі VPN</string>\n    <string name=\"proxied_apps_summary\">Налаштувати режим VPN для вибраних програм</string>\n    <string name=\"on\">Увімк</string>\n    <string name=\"off\">Вимк</string>\n    <string name=\"search_apps\">Пошук…</string>\n    <string name=\"scanning\">Сканування…</string>\n    <string name=\"invert_selections\">Інвертувати вибір</string>\n    <string name=\"clear_selections\">Очистити вибір</string>\n    <string name=\"bypass_apps\">В обхід</string>\n    <string name=\"show_system_apps\">Показувати системні програми</string>\n    <string name=\"auto_connect\">Автоматичне підключення</string>\n    <string name=\"auto_connect_summary\">Вмикати проксі під час запуску/оновлення програми, якщо він працював раніше</string>\n    <string name=\"direct_boot_aware\">Дозволити перемикання на екрані блокування</string>\n    <string name=\"direct_boot_aware_summary\">Інформація про ваш вибраний профіль буде менш захищеною</string>\n    <!-- notification category -->\n    <string name=\"service_vpn\">Служба VPN</string>\n    <string name=\"service_proxy\">Служба проксі</string>\n    <string name=\"forward_success\">Проксі запущено.</string>\n    <string name=\"invalid_server\">Неправильне ім\\'я сервера</string>\n    <string name=\"service_failed\">Не вдалося: </string>\n    <string name=\"stop\">Зупинити</string>\n    <string name=\"stopping\">Зупинка…</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"vpn_permission_denied\">Дозвіл на створення служби VPN відхилено</string>\n    <string name=\"reboot_required\">Не вдалося запустити службу VPN. Можливо, вам доведеться перезавантажити пристрій.</string>\n    <!-- alert category -->\n    <string name=\"profile_empty\">Будь ласка, виберіть профіль</string>\n    <string name=\"connect\">Підключити</string>\n    <string name=\"clipboard_empty\">Буфер обміну порожній</string>\n    <!-- menu category -->\n    <string name=\"settings\">Налаштування</string>\n    <string name=\"edit\">Редагувати</string>\n    <string name=\"share\">Поділитися</string>\n    <string name=\"add_profile\">Додати профіль</string>\n    <string name=\"select_profile\">Вибрати профіль</string>\n    <string name=\"action_socks\" translatable=\"false\">SOCKS</string>\n    <string name=\"action_http\" translatable=\"false\">HTTP</string>\n    <string name=\"action_shadowsocks\" translatable=\"false\">Shadowsocks</string>\n    <string name=\"action_shadowsocksr\" translatable=\"false\">ShadowsocksR</string>\n    <string name=\"action_vmess\" translatable=\"false\">VMess</string>\n    <string name=\"action_trojan\" translatable=\"false\">Trojan</string>\n    <string name=\"action_trojan_go\" translatable=\"false\">Trojan Go</string>\n    <string name=\"action_mieru\" translatable=\"false\">Mieru</string>\n    <string name=\"action_naive\" translatable=\"false\">Naïve</string>\n    <string name=\"action_anytls\" translatable=\"false\">AnyTLS</string>\n    <string name=\"action_hysteria\" translatable=\"false\">Hysteria</string>\n    <string name=\"action_ssh\" translatable=\"false\">SSH</string>\n    <string name=\"action_wireguard\" translatable=\"false\">WireGuard</string>\n    <string name=\"action_tuic\" translatable=\"false\">TUIC</string>\n    <string name=\"proxy_chain\">Ланцюжок проксі</string>\n    <string name=\"custom_config\">Власна конфігурація</string>\n    <string name=\"balancer\">Балансувальник</string>\n    <string name=\"balancer_settings\">Налаштування балансувальника</string>\n    <string name=\"balancer_type\">Тип</string>\n    <string name=\"balancer_strategy\">Стратегія</string>\n    <string name=\"chain_settings\">Налаштування ланцюжка</string>\n    <string name=\"config_settings\">Налаштування конфігурації</string>\n    <string name=\"circular_reference\">Циклічне посилання</string>\n    <string name=\"circular_reference_sum\">Маршрут не може містити самого себе.</string>\n    <string name=\"clear_profiles\">Очистити</string>\n    <string name=\"action_create_group\">Порожня група</string>\n    <string name=\"action_from_link\">З підписки</string>\n    <string name=\"action_scan_china_apps\">Сканувати китайські програми</string>\n    <string name=\"action_export_file\">Експортувати у файл</string>\n    <string name=\"action_export_clipboard\">Експортувати в буфер обміну</string>\n    <string name=\"action_export\">Експорт</string>\n    <string name=\"action_import\">Імпортувати з буфера обміну</string>\n    <string name=\"action_import_file\">Імпортувати з файлу</string>\n    <string name=\"action_export_msg\">Експортовано успішно!</string>\n    <string name=\"action_export_err\">Не вдалося експортувати.</string>\n    <string name=\"action_import_msg\">Імпортовано успішно!</string>\n    <string name=\"action_import_err\">Не вдалося імпортувати.</string>\n    <string name=\"file_manager_missing\">На вашому пристрої відсутній стандартний файловий менеджер Android, будь ласка, встановіть його, наприклад, Material Files.</string>\n    <!-- share -->\n    <!-- profile -->\n    <string name=\"profile_config\">Конфігурація профілю</string>\n    <string name=\"delete\">Видалити</string>\n    <string name=\"delete_confirm_prompt\">Ви впевнені, що хочете видалити цей профіль?</string>\n    <string name=\"share_qr_nfc\">QR-код</string>\n    <string name=\"add_profile_methods_scan_qr_code\">Сканувати QR-код</string>\n    <string name=\"add_profile_methods_manual_settings\">Ручні налаштування</string>\n    <plurals name=\"removed\">\n        <item quantity=\"one\">Видалено</item>\n        <item quantity=\"few\">Видалено %d елементи</item>\n        <item quantity=\"many\">Видалено %d елементів</item>\n        <item quantity=\"other\">Видалено %d елемента</item>\n    </plurals>\n    <plurals name=\"added\">\n        <item quantity=\"one\">Додано</item>\n        <item quantity=\"few\">Додано %d проксі</item>\n        <item quantity=\"many\">Додано %d проксі</item>\n        <item quantity=\"other\">Додано %d проксі</item>\n    </plurals>\n    <string name=\"undo\">Скасувати</string>\n    <string name=\"insecure\">Незахищений</string>\n    <string name=\"deprecated\">Застарілий</string>\n    <!-- tasker -->\n    <!-- status -->\n    <string name=\"connecting\">Підключення…</string>\n    <string name=\"vpn_connected\">Підключено, торкніться, щоб перевірити з\\'єднання</string>\n    <string name=\"not_connected\">Не підключено</string>\n    <!-- subscriptions -->\n    <string name=\"subscriptions\">Підписки</string>\n    <string name=\"cleartext_http_warning\">HTTP-трафік у відкритому вигляді є незахищеним</string>\n    <string name=\"error_title\">Помилка</string>\n    <string name=\"no_proxies_found\">Проксі за посиланням не знайдено</string>\n    <string name=\"no_proxies_found_in_subscription\">У підписці не знайдено проксі</string>\n    <string name=\"no_proxies_found_in_file\">У файлі не знайдено проксі</string>\n    <string name=\"no_proxies_found_in_clipboard\">У буфері обміну не знайдено проксі</string>\n    <string name=\"deduplication\">Дедуплікація</string>\n    <string name=\"donate\">Пожертвувати</string>\n    <string name=\"donate_info\">Я люблю гроші</string>\n    <string name=\"ignore_battery_optimizations\">Ігнорувати оптимізацію батареї</string>\n    <string name=\"ignore_battery_optimizations_sum\">Зняти деякі обмеження</string>\n    <!-- plugin -->\n    <string name=\"plugin\">Плагін</string>\n    <string name=\"plugin_configure\">Налаштувати…</string>\n    <string name=\"plugin_disabled\">Вимкнено</string>\n    <string name=\"plugin_unknown\">Невідомий плагін %s</string>\n    <string name=\"plugin_untrusted\">Попередження: цей плагін, здається, не з надійного джерела.</string>\n    <string name=\"plugin_auto_connect_unlock_only\">Цей плагін може не працювати з автоматичним підключенням</string>\n    <string name=\"proxy_cat\">Налаштування сервера</string>\n    <string name=\"ss_cat\">Налаштування Shadowsocks</string>\n    <string name=\"unsaved_changes_prompt\">Зміни не збережено. Ви хочете зберегти?</string>\n    <string name=\"yes\">Так</string>\n    <string name=\"no\">Ні</string>\n    <string name=\"apply\">Застосувати</string>\n    <string name=\"need_reload\">Перезавантажте службу проксі, щоб застосувати зміни</string>\n    <string name=\"license\">Ліцензія</string>\n    <string name=\"route_warn\">Переконайтеся, що ви прочитали документацію перед додаванням власних правил, інакше ви можете не підключитися до Інтернету.</string>\n    <string name=\"lines\">%d рядків</string>\n    <string name=\"night_mode\">Нічний режим</string>\n    <string name=\"follow_system\">Як у системі</string>\n    <string name=\"enable\">Увімкнути</string>\n    <string name=\"disable\">Вимкнути</string>\n    <string name=\"auto\">Авто</string>\n    <string name=\"enable_log\">Увімкнути журнал</string>\n    <string name=\"enable_log_sum\">Для налагодження</string>\n    <string name=\"list\">Список</string>\n    <string name=\"random\">Випадковий</string>\n    <string name=\"leastPing\" translatable=\"false\">Ping</string>\n    <string name=\"api_port\">Порт API</string>\n    <string name=\"probe_interval\">Інтервал спостереження балансувальника</string>\n    <string name=\"standard\">Стандартний</string>\n    <string name=\"v2rayn\" translatable=\"false\">V2RayN</string>\n    <string name=\"available\" translatable=\"false\">%dмс</string>\n    <string name=\"unavailable\">Недоступний</string>\n    <string name=\"always_show_address\">Завжди показувати адресу</string>\n    <string name=\"always_show_address_sum\">Завжди відображати адресу сервера на картці конфігурації</string>\n    <string name=\"clear_traffic_statistics\">Очистити статистику трафіку</string>\n    <string name=\"connection_test\">Тест з\\'єднання</string>\n    <string name=\"connection_test_clear_results\">Очистити результати тесту</string>\n    <string name=\"connection_test_tcp_ping\" translatable=\"false\">TCPing</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing недоступний</string>\n    <string name=\"connection_test_icmp_ping\" translatable=\"false\">ICMPing</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing недоступний</string>\n    <string name=\"connection_test_url_test\" translatable=\"false\">URL Test</string>\n    <string name=\"connection_test_domain_not_found\">Домен не знайдено</string>\n    <string name=\"connection_test_refused\">З\\'єднання відхилено</string>\n    <string name=\"connection_test_unreachable\">Недоступний</string>\n    <string name=\"connection_test_timeout\">Час очікування минув</string>\n    <string name=\"append_http_proxy\">Додати HTTP-проксі до VPN</string>\n    <string name=\"append_http_proxy_sum\">HTTP-проксі буде використовуватися безпосередньо (браузером / деякими підтримуваними програмами), не проходячи через віртуальний мережевий пристрій (Android 10+)</string>\n    <string name=\"protocol_settings\">Налаштування протоколу</string>\n    <string name=\"trojan_provider\">Постачальник Trojan</string>\n    <string name=\"group_basic\">Основні</string>\n    <string name=\"group_settings\">Налаштування групи</string>\n    <string name=\"subscription\">Підписка</string>\n    <string name=\"subscription_settings\">Налаштування підписки</string>\n    <string name=\"group_type\">Тип групи</string>\n    <string name=\"subscription_type\">Тип підписки</string>\n    <string name=\"delete_group_prompt\">Ви впевнені, що хочете видалити цю групу?</string>\n    <string name=\"force_resolve\">Примусове визначення</string>\n    <string name=\"force_resolve_sum\">Визначати всі доменні імена в IP-адреси під час оновлення. Хост і SNI будуть автоматично додані, якщо це можливо</string>\n    <string name=\"deduplication_sum\">Видаляти дублікати конфігурацій під час оновлення</string>\n    <string name=\"raw\">Raw</string>\n    <string name=\"update_settings\">Налаштування оновлення</string>\n    <string name=\"auto_update\">Автоматичне оновлення</string>\n    <string name=\"auto_update_delay\">Затримка автооновлення (у хвилинах)</string>\n    <string name=\"update_when_connected_only\">Оновлювати лише при підключенні</string>\n    <string name=\"update_when_connected_only_sum\">Запобігати витоку IP-адреси</string>\n    <string name=\"subscription_user_agent\">UserAgent</string>\n    <string name=\"confirm\">Підтвердити</string>\n    <string name=\"missing_plugin\">Відсутній плагін</string>\n    <string name=\"profile_requiring_plugin\">Профіль %s вимагає встановлення плагіна %s, але його не знайдено.</string>\n    <string name=\"action_learn_more\">ДІЗНАТИСЯ БІЛЬШЕ</string>\n    <string name=\"action_download\">ЗАВАНТАЖИТИ</string>\n    <string name=\"install_from_play_store\">Встановити з Play Store</string>\n    <string name=\"install_from_fdroid\">Встановити з F-Droid</string>\n    <string name=\"download\">Завантажити</string>\n    <string name=\"ooc_subscription_token\" translatable=\"false\">OOCv1 API Token</string>\n    <string name=\"ooc_subscription_token_invalid\">Неправильний токен OOCv1</string>\n    <string name=\"update_subscription_warning\">Проксі не підключено, ви впевнені, що хочете продовжити оновлення?</string>\n    <string name=\"ooc_warning\">Попередження</string>\n    <string name=\"ooc_missing_protocol\">Підписка вимагає підтримки протоколу %s, але його не знайдено. Непідтримувані профілі будуть проігноровані.</string>\n    <string name=\"service_subscription\">Служба оновлення підписок</string>\n    <string name=\"subscription_update\">Оновлення підписки</string>\n    <string name=\"subscription_update_message\">Оновлення %s …</string>\n    <string name=\"group_filter\">Фільтр</string>\n    <string name=\"subscription_used\">Використано %s</string>\n    <string name=\"subscription_traffic\">Використано %s / Залишилося %s</string>\n    <string name=\"subscription_expire\">Закінчується: %s</string>\n    <string name=\"subscription_import\">Імпортувати підписку</string>\n    <string name=\"subscription_import_message\">Підтвердьте, що ви хочете імпортувати підписку %s? Якщо ви робите це з ненадійного джерела, це може призвести до витоку вашої IP-адреси та даних про цю дію.</string>\n    <string name=\"profile_import\">Імпортувати профіль</string>\n    <string name=\"profile_import_message\">Підтвердьте, що ви хочете імпортувати профіль %s?</string>\n    <string name=\"clear_profiles_message\">Ви впевнені, що хочете очистити цю групу?</string>\n    <string name=\"apps\">Програми</string>\n    <string name=\"select_apps\">Вибрати програми</string>\n    <string name=\"apps_message\">%d програм</string>\n    <string name=\"hysteria_obfs\">Пароль обфускації</string>\n    <string name=\"hysteria_auth_type\">Тип автентифікації</string>\n    <string name=\"hysteria_auth_payload\">Дані автентифікації</string>\n    <string name=\"hysteria_upload_mbps\">Макс. швидкість вивантаження (у Мбіт/с)</string>\n    <string name=\"hysteria_download_mbps\">Макс. швидкість завантаження (у Мбіт/с)</string>\n    <string name=\"experimental_settings\">Експериментальні</string>\n    <string name=\"hysteria_stream_receive_window\">Вікно прийому потоку QUIC</string>\n    <string name=\"hysteria_connection_receive_window\">Вікно прийому з\\'єднання QUIC</string>\n    <string name=\"hysteria_disable_mtu_discovery\">Вимкнути виявлення Path MTU</string>\n    <string name=\"group_order\">Порядок</string>\n    <string name=\"group_order_origin\">За замовчуванням</string>\n    <string name=\"group_order_by_name\">За назвою</string>\n    <string name=\"group_order_by_delay\">За затримкою</string>\n    <string name=\"plugin_exists_but_on_shit_system\">Профіль %s вимагає плагін %s, але ваш виробник обладнання (зазвичай гіганти-капіталісти зі спостереження та виробники шкідливого ПЗ) змінив вашу систему Android, зробивши плагін непридатним для використання.</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (плагін для Shadowsocks)</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (плагін для Shadowsocks)</string>\n    <string name=\"traffic_uplink\" translatable=\"false\">%s | %s/с ↑</string>\n    <string name=\"traffic_downlink\" translatable=\"false\">%s | %s/с ↓</string>\n    <string name=\"traffic_uplink_total\" translatable=\"false\">Всього %s ↑</string>\n    <string name=\"traffic_downlink_total\" translatable=\"false\">Всього %s ↓</string>\n    <string name=\"tcp_connections\">%d TCP-з\\'єднань</string>\n    <string name=\"udp_connections\">%d UDP-з\\'єднань</string>\n    <string name=\"copy_label\">Копіювати назву</string>\n    <string name=\"copy_package_name\">Копіювати назву пакета</string>\n    <string name=\"open_app\">Відкрити програму</string>\n    <string name=\"open_settings\">Відкрити налаштування</string>\n    <string name=\"open_market\">Відкрити в маркеті</string>\n    <string name=\"create_rule\">Створити правило</string>\n    <string name=\"copy_success\">Скопійовано успішно!</string>\n    <string name=\"copy_failed\">Не вдалося скопіювати.</string>\n    <string name=\"app_no_launcher\">Програма не має інтерфейсу.</string>\n    <string name=\"route_for\">Правило для %s</string>\n    <string name=\"route_need_vpn\">Правило маршрутизації %s залежить від VPN, щоб набути чинності, тому воно ігнорується.</string>\n    <string name=\"profile_traffic_statistics\">Статистика трафіку профілю</string>\n    <string name=\"profile_traffic_statistics_summary\">Якщо вимкнено, використаний трафік не буде враховуватися</string>\n    <string name=\"no_statistics\">Статистики ще немає</string>\n    <string name=\"app_statistics_disabled\">Статистика трафіку програм вимкнена</string>\n    <string name=\"ssh_auth_type_none\">Немає</string>\n    <string name=\"ssh_public_key\">Публічний ключ</string>\n    <string name=\"ssh_private_key\">Приватний ключ</string>\n    <string name=\"ssh_private_key_passphrase\">Парольна фраза приватного ключа</string>\n    <string name=\"translate_platform\">Платформа для перекладу</string>\n    <string name=\"menu_tools\">Інструменти</string>\n    <string name=\"menu_log\">Журнали</string>\n    <string name=\"clear_logcat\">Очистити Logcat</string>\n    <string name=\"wireguard_local_address\">Локальна адреса</string>\n    <string name=\"wireguard_public_key\">Публічний ключ піра</string>\n    <string name=\"wireguard_psk\">Pre-Shared Key піра</string>\n    <string name=\"cloudflare_wrap\" translatable=\"false\">Cloudflare Warp</string>\n    <string name=\"warp_license\">CloudFlare Warp – це безкоштовний VPN-провайдер, що використовує WireGuard. Використовуючи його, ви погоджуєтеся з Умовами надання послуг.</string>\n    <string name=\"warp_generate\">Згенерувати конфігурацію</string>\n    <string name=\"generating\">Генерація…</string>\n    <string name=\"tun_implementation\">Реалізація TUN</string>\n    <string name=\"destination_override\">Перевизначити призначення</string>\n    <string name=\"destination_override_summary\">Використовувати перехоплений домен для перезапису адреси призначення, а не лише для маршрутизації</string>\n    <string name=\"resolve_destination\">Визначати призначення</string>\n    <string name=\"resolve_destination_summary\">Якщо адреса призначення є доменом, вона передається далі на основі стратегії IPv6 (конфліктує з FakeDNS)</string>\n    <string name=\"pcap\" translatable=\"false\">Pcap</string>\n    <string name=\"pcap_notice\">Файли Pcap будуть збережені в %s</string>\n    <string name=\"naive_insecure_concurrency\">Небезпечна паралельність</string>\n    <string name=\"naive_insecure_concurrency_summary\">Використовуйте N паралельних тунельних з\\'єднань для більшої стійкості в поганих мережевих умовах. Більша кількість з\\'єднань полегшує виявлення тунелювання та робить його менш безпечним. Цей проєкт прагне до найсильнішого захисту від аналізу трафіку. Використання його небезпечним способом суперечить його меті. \\n\\nЯкщо ви все ж таки повинні це використовувати, спробуйте спочатку N=2, щоб побачити, чи це вирішить ваші проблеми. Наполегливо не рекомендується використовувати більше 4 з\\'єднань.</string>\n    <string name=\"stun_test\">Виявлення поведінки NAT</string>\n    <string name=\"stun_test_summary\">Визначити поведінку відображення NAT клієнта та поведінку фільтрації NAT, визначену в RFC 3478, за допомогою STUN.</string>\n    <string name=\"start\">Почати</string>\n    <string name=\"stun_attest_loading\">Це може зайняти кілька хвилин…</string>\n    <string name=\"nat_stun_server_hint\">STUN-сервер</string>\n    <string name=\"nat_result_hint\">Результат перевірки NAT</string>\n    <string name=\"tools_network\">Мережа</string>\n    <string name=\"mtu\" translatable=\"false\">MTU</string>\n    <string name=\"backup\">Резервне копіювання</string>\n    <string name=\"backup_groups_and_configurations\">Групи та конфігурації</string>\n    <string name=\"backup_rules\">Правила маршрутизації</string>\n    <string name=\"backup_settings\">Налаштування</string>\n    <string name=\"backup_summary\">Якщо налаштування маршрутизації не будуть збережені разом з конфігураціями, власні вихідні з\\'єднання будуть втрачені.</string>\n    <string name=\"backup_not_file\">Не є файлом резервної копії: очікувався .json, але отримано %s</string>\n    <string name=\"invalid_backup_file\">Неправильний файл резервної копії</string>\n    <string name=\"backup_import\">Імпорт</string>\n    <string name=\"backup_import_summary\">Імпортування перезапише наявні дані.</string>\n    <string name=\"backup_importing\">Імпортування…</string>\n    <string name=\"packet_encoding\">Кодування пакетів</string>\n    <string name=\"acquire_wake_lock\">Захоплювати WakeLock</string>\n    <string name=\"release_wake_lock\">Відпускати WakeLock</string>\n    <string name=\"acquire_wake_lock_summary\">Не давати процесору заснути</string>\n    <string name=\"action_switch\">Перемкнути</string>\n    <string name=\"tuic_udp_relay_mode\">Режим ретрансляції UDP</string>\n    <string name=\"tuic_congestion_controller\">Контролер перевантаження</string>\n    <string name=\"tuic_disable_sni\">Вимкнути SNI</string>\n    <string name=\"tuic_reduce_rtt\">Увімкнути 0-RTT QUIC рукостискання</string>\n    <string name=\"please_update\">Ваша програма занадто стара (%s) і перестане працювати %s. Будь ласка, оновіть її!</string>\n    <string name=\"please_update_force\">Ваша програма занадто стара (%s) і перестала працювати %s. Будь ласка, оновіть її!</string>\n    <string name=\"connection_test_delete_unavailable\">Очистити недоступні</string>\n    <string name=\"move\">Перемістити</string>\n    <string name=\"exe_prefer_provider\">Пріоритетний постачальник плагіна</string>\n    <string name=\"create_shortcut\">Створити ярлик</string>\n    <string name=\"app_tls_version\">Мінімальна версія TLS для підписки</string>\n    <string name=\"hop_interval\">Інтервал зміни портів (секунди)</string>\n    <string name=\"domain_strategy_for_remote\">Стратегія доменів для віддаленого</string>\n    <string name=\"domain_strategy_for_direct\">Стратегія доменів для прямого</string>\n    <string name=\"domain_strategy_for_server\">Стратегія доменів для адреси сервера</string>\n    <string name=\"show_bottom_bar\">Показувати нижню панель, як у SagerNet</string>\n    <string name=\"utls_fingerprint\">Відбиток uTLS</string>\n    <string name=\"custom_outbound_json\">Власний JSON вихідного з\\'єднання</string>\n    <string name=\"custom_config_json\">Власний JSON конфігурації</string>\n    <string name=\"is_outbound_only\">Встановлений JSON є лише вихідним з\\'єднанням</string>\n    <string name=\"save\">Зберегти</string>\n    <string name=\"set_panel_url\">Встановити URL панелі</string>\n    <string name=\"enable_clash_api\">Увімкнути Clash API</string>\n    <string name=\"log_level\">Рівень журналу</string>\n    <string name=\"enable_clash_api_summary\">Надати Clash API та панель yacd за адресою 127.0.0.1:9090</string>\n    <string name=\"xtls_flow\">Flow (підпротокол VLESS)</string>\n    <string name=\"ads\">Реклама</string>\n    <string name=\"bypass_lan_in_core\">Обходити ЛВС в ядрі</string>\n    <string name=\"need_restart\">Перезапустіть програму, щоб застосувати зміни</string>\n    <string name=\"use_selector\">Використовувати селектор</string>\n    <string name=\"front_proxy\">Передній проксі</string>\n    <string name=\"landing_proxy\">Проксі-призначення</string>\n    <string name=\"action_shadowtls\" translatable=\"false\">ShadowTLS</string>\n    <string name=\"protocol_version\">Версія протоколу</string>\n    <string name=\"share_subscription\">Поділитися підпискою</string>\n    <string name=\"show_group_in_notification\">Показувати назву групи у сповіщенні</string>\n    <string name=\"reset_connections\">Скинути з\\'єднання</string>\n    <string name=\"remove_duplicate\">Видалити дублікати серверів</string>\n    <string name=\"mtu_help\">Довге натискання на налаштування, щоб встановити власний MTU.</string>\n    <string name=\"log_level_help\">Довге натискання на налаштування, щоб встановити розмір буфера.</string>\n    <string name=\"tls_camouflage_settings\">Налаштування маскування TLS</string>\n    <string name=\"test_concurrency\">Паралелізм тесту</string>\n    <string name=\"mux_type\">Протокол Mux</string>\n    <string name=\"sniff_routing\">Результат аналізу для маршрутизації</string>\n    <string name=\"sniff_override\">Результат аналізу для призначення</string>\n    <string name=\"resolve_server\">Визначати адресу сервера відповідно до політики IPv6</string>\n    <string name=\"auto_select_proxy_apps\">Автоматичний вибір програм для проксі</string>\n    <string name=\"auto_select_proxy_apps_message\">Автоматично вибрати програми для проксі, це очистить ваш поточний вибір.</string>\n    <string name=\"enable_ech\">Увімкнути ECH</string>\n    <string name=\"enable_ech_sum\">Увімкнути Encrypted Client Hello</string>\n    <string name=\"ech_settings\">Налаштування ECH</string>\n    <string name=\"ech_config\">Конфігурація ECH</string>\n    <string name=\"http_upgrade_host\">Хост HTTPUpgrade</string>\n    <string name=\"http_upgrade_path\">Шлях HTTPUpgrade</string>\n    <string name=\"update_current_subscription\">Оновити підписку поточної групи</string>\n    <string name=\"group_not_subscription\">Тип групи не є підпискою</string>\n    <string name=\"allow_insecure_on_request_sum\">Вимкнути перевірку сертифіката під час оновлення підписок</string>\n    <string name=\"global_allow_insecure\">Завжди дозволяти незахищене</string>\n    <string name=\"mux_preference\">Мультиплексування</string>\n    <string name=\"padding\">Заповнення (Padding)</string>\n    <string name=\"network_change_reset_connections\">Скидати вихідні з\\'єднання при зміні мережі</string>\n    <string name=\"wake_reset_connections\">Скидати вихідні з\\'єднання при виході пристрою зі сну</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rCN/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_desc\">适用于 Android 之通用代理工具, 使用 Kotlin 编写。</string>\n    <string name=\"project\">项目</string>\n    <string name=\"github\">源代码</string>\n    <string name=\"telegram\">Telegram 更新频道</string>\n    <string name=\"oss_licenses\">开放源代码协议</string>\n    <string name=\"app_version\">版本</string>\n    <string name=\"logcat\">导出调试信息</string>\n    <string name=\"menu_configuration\">配置</string>\n    <string name=\"menu_group\">分组</string>\n    <string name=\"menu_about\">关于</string>\n    <string name=\"group_default\">未分组</string>\n    <string name=\"quick_toggle\">切换</string>\n    <string name=\"quick_enable\">启用</string>\n    <string name=\"quick_disable\">禁用</string>\n    <!-- group -->\n    <string name=\"group_name\">分组名</string>\n    <string name=\"group_update\">更新</string>\n    <string name=\"group_subscription_link\">订阅链接</string>\n    <string name=\"group_name_required\">需要分组名称</string>\n    <string name=\"group_create\">创建分组</string>\n    <string name=\"group_create_subscription\">添加订阅</string>\n    <string name=\"group_edit\">编辑</string>\n    <string name=\"group_edit_subscription\">编辑订阅</string>\n    <string name=\"group_status_empty\">空</string>\n    <string name=\"group_status_empty_subscription\">从未更新</string>\n    <string name=\"group_status_proxies\">%d 个配置</string>\n    <string name=\"group_status_proxies_subscription\">%d 个配置 | 更新于 %s</string>\n    <string name=\"group_no_difference\">%s: 没有变化</string>\n    <string name=\"group_updated\">%s: 更新了 %d 个节点</string>\n    <string name=\"group_show_diff\">变化</string>\n    <string name=\"group_delete_confirm_prompt\">您确定要删除这个分组吗\\?</string>\n    <string name=\"group_added\">新增:\n        \\n%s</string>\n    <string name=\"group_changed\">更新:\n        \\n%s</string>\n    <string name=\"group_deleted\">删除:\n        \\n%s</string>\n    <!-- misc -->\n    <string name=\"service_mode\">运行模式</string>\n    <string name=\"service_mode_proxy\">仅代理</string>\n    <string name=\"port_proxy\">代理端口</string>\n    <string name=\"port_http\">HTTP 代理端口</string>\n    <string name=\"port_local_dns\">本地 DNS 端口</string>\n    <string name=\"allow_access\">允许来自局域网的连接</string>\n    <string name=\"allow_access_sum\">将入站绑定至 0.0.0.0</string>\n    <string name=\"cag_dns\">DNS 设置</string>\n    <string name=\"cag_route\">路由设置</string>\n    <string name=\"inbound_settings\">入站设置</string>\n    <string name=\"general_settings\">软件设置</string>\n    <string name=\"require_http\">启用 HTTP 入站</string>\n    <string name=\"cag_ws\">WebSocket 设置</string>\n    <string name=\"ws_browser_forwarding\">浏览器转发</string>\n    <string name=\"ws_browser_forwarding_sum\">转发相应的 WebSockets 到浏览器.</string>\n    <string name=\"speed_interval\">速度通知更新间隔</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"speed_detail\">代理： %1$s↑ %2$s↓\n        \\n直连： %3$s↑ %4$s↓</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_testing\">测试中…</string>\n    <string name=\"connection_test_available\">连接成功: HTTPS 握手耗时 %dms</string>\n    <string name=\"connection_test_error\">失败: %s</string>\n    <string name=\"connection_test_fail\">网络不可用</string>\n    <string name=\"connection_test_error_status_code\">错误代码: #%d</string>\n    <!-- proxy category -->\n    <string name=\"profile_name\">配置名称</string>\n    <string name=\"server_address\">服务器</string>\n    <string name=\"server_port\">服务器端口</string>\n    <string name=\"username_opt\">用户名 (可选)</string>\n    <string name=\"password_opt\">密码 (可选)</string>\n    <string name=\"password\">密码</string>\n    <string name=\"enc_method\">加密方式</string>\n    <string name=\"protocol\">协议</string>\n    <string name=\"protocol_param\">协议参数</string>\n    <string name=\"obfs\">混淆</string>\n    <string name=\"obfs_param\">混淆参数</string>\n    <string name=\"uuid\">用户ID</string>\n    <string name=\"alter_id\">替代 ID</string>\n    <string name=\"security\">传输层加密</string>\n    <string name=\"network\">传输协议</string>\n    <string name=\"header_type\">伪装类型</string>\n    <string name=\"tls\">使用 TLS</string>\n    <string name=\"sni\">服务器名称指示</string>\n    <string name=\"encryption\">加密</string>\n    <!-- feature category -->\n    <string name=\"ipv6\">IPv6 路由</string>\n    <string name=\"metered\">按流量计费</string>\n    <string name=\"metered_summary\">让系统认为此 VPN 按流量计费</string>\n    <string name=\"menu_route\">路由</string>\n    <string name=\"proxied_apps\">分应用代理</string>\n    <string name=\"proxied_apps_summary\">代理被勾选应用的流量</string>\n    <string name=\"on\">打开</string>\n    <string name=\"off\">关闭</string>\n    <string name=\"search_apps\">搜索…</string>\n    <string name=\"scanning\">扫描中…</string>\n    <string name=\"invert_selections\">反选</string>\n    <string name=\"clear_selections\">清空</string>\n    <string name=\"bypass_apps\">绕过</string>\n    <string name=\"show_system_apps\">显示系统应用</string>\n    <string name=\"auto_connect\">自动连接</string>\n    <string name=\"auto_connect_summary\">手机启动或更新后代理会自动重新连接</string>\n    <string name=\"direct_boot_aware\">允许在解锁前启动</string>\n    <string name=\"direct_boot_aware_summary\">您正在使用的服务器可能会受到更少的保护</string>\n    <!-- notification category -->\n    <string name=\"service_vpn\">VPN 服务</string>\n    <string name=\"service_proxy\">代理服务</string>\n    <string name=\"forward_success\">代理已启动。</string>\n    <string name=\"invalid_server\">无效的服务器</string>\n    <string name=\"service_failed\">失败:</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"stopping\">正在停止…</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"vpn_permission_denied\">VPN 服务权限请求被拒绝</string>\n    <string name=\"reboot_required\">无法启动 VPN 服务 , 您可能需要重启您的设备.</string>\n    <!-- alert category -->\n    <string name=\"profile_empty\">请选择一个服务器配置</string>\n    <string name=\"connect\">连接</string>\n    <string name=\"clipboard_empty\">剪切板没有内容</string>\n    <!-- menu category -->\n    <string name=\"settings\">设置</string>\n    <string name=\"edit\">编辑</string>\n    <string name=\"share\">分享</string>\n    <string name=\"add_profile\">添加服务器配置</string>\n    <string name=\"action_create_group\">空白组</string>\n    <string name=\"action_from_link\">添加订阅</string>\n    <string name=\"action_scan_china_apps\">扫描中国应用</string>\n    <string name=\"action_export_file\">导出到文件</string>\n    <string name=\"action_export_clipboard\">导出到剪切板</string>\n    <string name=\"action_import\">从剪切板导入</string>\n    <string name=\"action_import_file\">从文件中导入</string>\n    <string name=\"action_export_msg\">导出成功!</string>\n    <string name=\"action_export_err\">导出失败.</string>\n    <string name=\"action_import_msg\">导入成功!</string>\n    <string name=\"action_import_err\">导入失败.</string>\n    <!-- share -->\n    <!-- profile -->\n    <string name=\"profile_config\">服务器配置</string>\n    <string name=\"delete\">删除</string>\n    <string name=\"delete_confirm_prompt\">您确定要删除这个服务器配置吗\\?</string>\n    <string name=\"share_qr_nfc\">二维码</string>\n    <string name=\"add_profile_methods_scan_qr_code\">扫描二维码</string>\n    <string name=\"add_profile_methods_manual_settings\">手动输入</string>\n    <plurals name=\"removed\">\n        <item quantity=\"other\">已删除 %d 个配置</item>\n    </plurals>\n    <plurals name=\"added\">\n        <item quantity=\"other\">已添加 %d 个配置</item>\n    </plurals>\n    <string name=\"undo\">还原</string>\n    <!-- tasker -->\n    <!-- status -->\n    <string name=\"connecting\">正在连接…</string>\n    <string name=\"vpn_connected\">已连接 , 点击此处测试连接</string>\n    <string name=\"not_connected\">未连接</string>\n    <!-- subscriptions -->\n    <string name=\"subscriptions\">订阅</string>\n    <string name=\"cleartext_http_warning\">纯文本 HTTP 流量并不安全</string>\n    <string name=\"error_title\">错误</string>\n    <string name=\"no_proxies_found\">未在此链接内找到服务器配置</string>\n    <!-- plugin -->\n    <string name=\"plugin\">插件</string>\n    <string name=\"plugin_configure\">设置…</string>\n    <string name=\"plugin_disabled\">已停用</string>\n    <string name=\"plugin_unknown\">未知插件: %s</string>\n    <string name=\"plugin_untrusted\">请注意: 此插件的不来自可靠的来源.</string>\n    <string name=\"plugin_auto_connect_unlock_only\">此插件可能不支持自动连接</string>\n    <string name=\"proxy_cat\">服务器设置</string>\n    <string name=\"unsaved_changes_prompt\">是否要保存修改？</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"apply\">应用</string>\n    <string name=\"license\">授权协议</string>\n    <string name=\"route_proxy\">代理</string>\n    <string name=\"route_bypass\">绕过</string>\n    <string name=\"route_block\">屏蔽</string>\n    <string name=\"route_opt_bypass_lan\">绕过局域网地址</string>\n    <string name=\"route_opt_block_ads\">屏蔽广告</string>\n    <string name=\"route_opt_block_quic\">屏蔽 QUIC</string>\n    <string name=\"domain_strategy\">域名解析策略</string>\n    <string name=\"traffic_sniffing\">启用流量探测</string>\n    <string name=\"disable\">禁用</string>\n    <string name=\"cag_misc\">杂项</string>\n    <string name=\"enable_mux\">启用多路复用</string>\n    <string name=\"mux_concurrency\">复用最大并发</string>\n    <string name=\"ws_host\">WebSocket 主机</string>\n    <string name=\"ws_path\">WebSocket 路径</string>\n    <string name=\"http_host\">HTTP 主机</string>\n    <string name=\"http_path\">HTTP 路径</string>\n    <string name=\"show_bottom_bar\">像 SagerNet 一样显示底栏</string>\n    <string name=\"utls_fingerprint\">uTLS 指纹</string>\n    <string name=\"grpc_service_name\">gRPC 服务名称</string>\n    <string name=\"alpn\">应用层协议协商</string>\n    <string name=\"group_duplicate\">重复:\n        \\n%s</string>\n    <string name=\"deduplication\">去重</string>\n    <string name=\"version_x\">版本 (%s)</string>\n    <string name=\"show_stop\">显示停止按钮</string>\n    <string name=\"show_stop_sum\">如果您不想使用快捷图块</string>\n    <string name=\"ss_cat\">Shadowsocks 设置</string>\n    <string name=\"insecure\">不安全</string>\n    <string name=\"deprecated\">已废弃</string>\n    <string name=\"show_direct_speed\">显示直连速度</string>\n    <string name=\"show_direct_speed_sum\">在通知中也显示不经过代理的流量速度</string>\n    <string name=\"select_profile\">选择配置</string>\n    <string name=\"proxy_chain\">链式代理</string>\n    <string name=\"chain_settings\">链设置</string>\n    <string name=\"circular_reference\">循环引用</string>\n    <string name=\"circular_reference_sum\">路由中不能包含自己.</string>\n    <string name=\"mux_sum\">多路复用是为了减少 TCP 的握手延迟而设计, 而非提高连接的吞吐量. 用于看视频、下载或者测速通常都有反效果。如果服务器不支持，则无法上网。</string>\n    <string name=\"tcp_keep_alive_interval\">TCP 保持活跃数据包发送间隔</string>\n    <string name=\"route_add\">新建路由规则</string>\n    <string name=\"delete_route_prompt\">您确定要移除此路由吗\\?</string>\n    <string name=\"route_name\">路由名称</string>\n    <string name=\"route_profile\">选择配置…</string>\n    <string name=\"empty_route\">空路由</string>\n    <string name=\"empty_route_notice\">在保存前设置一些规则</string>\n    <string name=\"route_warn\">在添加自定义规则之前，请确保您已经阅读了文档，否则您将可能无法连接到互联网。</string>\n    <string name=\"route_bypass_domain\">%s 域名规则</string>\n    <string name=\"route_bypass_ip\">%s IP 规则</string>\n    <string name=\"no_proxies_found_in_file\">未在此文件内找到服务器配置</string>\n    <string name=\"no_proxies_found_in_clipboard\">未在剪切板内找到服务器配置</string>\n    <string name=\"route_reset\">重置</string>\n    <string name=\"clear_profiles\">清空</string>\n    <string name=\"file_manager_missing\">您的设备缺少 Android 标准文件选择器, 请安装一个, 如 Material Files.</string>\n    <string name=\"need_reload\">重载代理服务以应用修改</string>\n    <string name=\"theme\">主题颜色</string>\n    <string name=\"donate\">捐款</string>\n    <string name=\"donate_info\">猫猫很可爱 请给猫猫钱</string>\n    <string name=\"extra_headers\">附加标头</string>\n    <string name=\"username\">用户名</string>\n    <string name=\"key_opt\">密码 (可选)</string>\n    <string name=\"group_diff\">变化 (%s)</string>\n    <string name=\"require_transproxy\">启用透明代理入站</string>\n    <string name=\"transproxy_mode\">透明代理模式</string>\n    <string name=\"port_transproxy\">透明代理端口</string>\n    <string name=\"connection_test_url\">连接测试链接</string>\n    <string name=\"connection_test_available_http\">连接成功: HTTP 握手耗时 %dms</string>\n    <string name=\"certificates\">证书 (链)</string>\n    <string name=\"bypass_lan_in_core\">在核心中绕过 LAN</string>\n    <string name=\"ignore_battery_optimizations\">忽略电池优化</string>\n    <string name=\"ignore_battery_optimizations_sum\">移除一些限制</string>\n    <string name=\"document\">文档</string>\n    <string name=\"early_data_header_name\">前置数据标头</string>\n    <string name=\"config_settings\">配置设置</string>\n    <string name=\"custom_config\">自定义配置</string>\n    <string name=\"night_mode\">夜间模式</string>\n    <string name=\"enable\">启用</string>\n    <string name=\"auto\">自动</string>\n    <string name=\"follow_system\">跟随系统</string>\n    <string name=\"security_settings\">TLS 安全设置</string>\n    <string name=\"allow_insecure\">允许不安全的连接</string>\n    <string name=\"allow_insecure_sum\">禁用证书检查. 启用后该配置安全性相当于明文</string>\n    <string name=\"config_type\">配置类型</string>\n    <string name=\"edit_config\">编辑配置</string>\n    <string name=\"lines\">%d 行</string>\n    <string name=\"only\">仅</string>\n    <string name=\"prefer\">优先</string>\n    <string name=\"route_not_asset\">不是资源文件: 预期 .db 为扩展名, 但 %s</string>\n    <string name=\"route_asset_no_update\">已为最新</string>\n    <string name=\"route_asset_updated\">已更新</string>\n    <string name=\"route_asset_status\">本地版本: %s</string>\n    <string name=\"route_rules_provider\">路由资源更新源</string>\n    <string name=\"route_assets\">资源</string>\n    <string name=\"route_manage_assets\">管理路由资源</string>\n    <string name=\"enable_log\">启用日志</string>\n    <string name=\"enable_log_sum\">调试用途</string>\n    <string name=\"route_rules_official\">官方</string>\n    <string name=\"balancer\">负载均衡</string>\n    <string name=\"balancer_settings\">负载均衡设置</string>\n    <string name=\"balancer_type\">类型</string>\n    <string name=\"balancer_strategy\">策略</string>\n    <string name=\"list\">列表</string>\n    <string name=\"random\">随机</string>\n    <string name=\"api_port\">API 端口</string>\n    <string name=\"probe_interval\">负载均衡观测间隔</string>\n    <string name=\"standard\">标准</string>\n    <string name=\"unavailable\">不可用</string>\n    <string name=\"always_show_address\">始终显示地址</string>\n    <string name=\"always_show_address_sum\">始终在配置卡片上显示服务器地址</string>\n    <string name=\"clear_traffic_statistics\">清空流量统计数据</string>\n    <string name=\"connection_test\">连接测试</string>\n    <string name=\"connection_test_clear_results\">清理测试结果</string>\n    <string name=\"connection_test_domain_not_found\">域名不存在</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing 不可用</string>\n    <string name=\"connection_test_refused\">连接重置</string>\n    <string name=\"connection_test_timeout\">超时</string>\n    <string name=\"connection_test_unreachable\">不可达</string>\n    <string name=\"tile_title\">开关</string>\n    <string name=\"append_http_proxy\">追加 HTTP 代理至 VPN</string>\n    <string name=\"append_http_proxy_sum\">浏览器 / 一些支持的应用 将直接使用 HTTP 代理, 而不经过虚拟网卡设备 (Android 10+)</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing 不可用</string>\n    <string name=\"protocol_settings\">协议设置</string>\n    <string name=\"trojan_provider\">Trojan 提供程序</string>\n    <string name=\"group_basic\">基本</string>\n    <string name=\"group_settings\">分组设置</string>\n    <string name=\"subscription\">订阅</string>\n    <string name=\"subscription_settings\">订阅设置</string>\n    <string name=\"group_type\">分组类型</string>\n    <string name=\"subscription_type\">订阅类型</string>\n    <string name=\"delete_group_prompt\">您确定要删除该分组吗 \\?</string>\n    <string name=\"force_resolve\">强制解析</string>\n    <string name=\"force_resolve_sum\">更新时解析所有域名到 IP 地址. 如果可能, 主机与服务器名称指示值将被自动覆盖</string>\n    <string name=\"deduplication_sum\">更新时移除重复配置</string>\n    <string name=\"raw\">原始</string>\n    <string name=\"update_settings\">更新设置</string>\n    <string name=\"auto_update\">自动更新</string>\n    <string name=\"auto_update_delay\">自动更新间隔 (分)</string>\n    <string name=\"update_when_connected_only\">仅在连接时更新</string>\n    <string name=\"update_when_connected_only_sum\">防止 IP 地址泄漏</string>\n    <string name=\"subscription_user_agent\">用户代理</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"missing_plugin\">缺少插件</string>\n    <string name=\"profile_requiring_plugin\">配置 %s 需要插件 %s, 但找不到.</string>\n    <string name=\"action_learn_more\">了解更多</string>\n    <string name=\"action_download\">下载</string>\n    <string name=\"install_from_play_store\">从 Play Store 安装</string>\n    <string name=\"install_from_fdroid\">从 F-Droid 安装</string>\n    <string name=\"download\">下载</string>\n    <string name=\"ooc_subscription_token_invalid\">无效的 OOCv1 Token</string>\n    <string name=\"ooc_warning\">警告</string>\n    <string name=\"ooc_missing_protocol\">该订阅要求 %s 协议支持, 但找不到. 不支持的配置将被忽略.</string>\n    <string name=\"service_subscription\">订阅更新服务</string>\n    <string name=\"subscription_update\">订阅更新</string>\n    <string name=\"subscription_update_message\">正更新 %s …</string>\n    <string name=\"update_subscription_warning\">代理未连接, 您确定要继续更新吗\\?</string>\n    <string name=\"group_filter\">筛选</string>\n    <string name=\"subscription_used\">已用 %s</string>\n    <string name=\"subscription_traffic\">已用 %s / 剩余 %s</string>\n    <string name=\"subscription_import\">导入订阅</string>\n    <string name=\"subscription_import_message\">确认要导入订阅 %s？如果来源不可信, 您的 IP 和行为将被泄露.</string>\n    <string name=\"profile_import\">导入配置</string>\n    <string name=\"profile_import_message\">确认要导入配置 %s？</string>\n    <string name=\"no_proxies_found_in_subscription\">未在此订阅内找到服务器配置</string>\n    <string name=\"action_export\">导出</string>\n    <string name=\"clear_profiles_message\">您确定要清空该分组吗\\?</string>\n    <string name=\"remote_dns\">远程 DNS</string>\n    <string name=\"direct_dns\">直连 DNS</string>\n    <string name=\"enable_dns_routing\">启用 DNS 路由</string>\n    <string name=\"dns_routing_message\">使用直连 DNS 解析绕过路由中的 domains. 注意潜在的 DNS 泄漏</string>\n    <string name=\"enable_fakedns\">启用 FakeDNS</string>\n    <string name=\"fakedns_message\">可能导致其他应用程序在代理停止后需要重新启动以重新连接到网络</string>\n    <string name=\"dns_hosts\">域名重写</string>\n    <string name=\"apps\">应用</string>\n    <string name=\"select_apps\">选择应用</string>\n    <string name=\"apps_message\">%d 个应用</string>\n    <string name=\"hysteria_obfs\">混淆密码</string>\n    <string name=\"hysteria_auth_type\">认证类型</string>\n    <string name=\"hysteria_auth_payload\">认证密码</string>\n    <string name=\"hysteria_upload_mbps\">最大上行 (Mbps)</string>\n    <string name=\"hysteria_download_mbps\">最大下行 (Mbps)</string>\n    <string name=\"experimental_settings\">实验性</string>\n    <string name=\"hysteria_stream_receive_window\">QUIC 流接收窗口</string>\n    <string name=\"hysteria_connection_receive_window\">QUIC 连接接收窗口</string>\n    <string name=\"hysteria_disable_mtu_discovery\">禁用路径最大传输单元发现</string>\n    <string name=\"group_order\">排序</string>\n    <string name=\"group_order_origin\">原始</string>\n    <string name=\"group_order_by_name\">以名称</string>\n    <string name=\"group_order_by_delay\">以延时</string>\n    <string name=\"plugin_exists_but_on_shit_system\">配置 %s 需要插件\n        %s，但你的专有设备供应商（通常也是监视资本主义巨头和恶意软件制造商）篡改了你的安卓系统，使该插件无法使用。</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray （Shadowsocks Android 插件）</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs （Shadowsocks Android 插件）</string>\n    <string name=\"menu_traffic\">流量</string>\n    <string name=\"route_for\">%s 路由</string>\n    <string name=\"app_no_launcher\">该应用没有界面。</string>\n    <string name=\"copy_failed\">复制失败。</string>\n    <string name=\"copy_success\">复制成功！</string>\n    <string name=\"create_rule\">创建路由</string>\n    <string name=\"open_market\">打开应用市场</string>\n    <string name=\"open_settings\">打开设置</string>\n    <string name=\"open_app\">打开应用</string>\n    <string name=\"copy_package_name\">复制包名</string>\n    <string name=\"copy_label\">复制应用名</string>\n    <string name=\"udp_connections\">%d UDP 连接</string>\n    <string name=\"tcp_connections\">%d TCP 连接</string>\n    <string name=\"route_need_vpn\">路由规则 %s 依赖 VPN 以生效，所以其已被忽略。</string>\n    <string name=\"no_statistics\">暂无数据</string>\n    <string name=\"ssh_private_key_passphrase\">私钥密码句</string>\n    <string name=\"ssh_private_key\">私钥</string>\n    <string name=\"ssh_public_key\">公钥</string>\n    <string name=\"ssh_auth_type_none\">无</string>\n    <string name=\"clear_logcat\">清空日志</string>\n    <string name=\"menu_log\">日志</string>\n    <string name=\"menu_tools\">工具</string>\n    <string name=\"translate_platform\">翻译平台</string>\n    <string name=\"wireguard_local_address\">本地地址</string>\n    <string name=\"wireguard_public_key\">节点公钥</string>\n    <string name=\"wireguard_psk\">节点预共享密钥</string>\n    <string name=\"resolve_destination_summary\">如果目标地址是一个域, 则根据 IPv6 策略传出（与 FakeDNS 冲突）</string>\n    <string name=\"resolve_destination\">解析目标地址</string>\n    <string name=\"destination_override_summary\">使用探测的域来覆盖目标地址, 而不是仅用于路由</string>\n    <string name=\"destination_override\">覆盖目标地址</string>\n    <string name=\"tun_implementation\">TUN 实现</string>\n    <string name=\"generating\">正在生成…</string>\n    <string name=\"warp_generate\">生成配置</string>\n    <string name=\"warp_license\">CloudFlare Warp 是一个免费的 WireGuard VPN 提供源. 使用它, 代表您同意其服务条款。</string>\n    <string name=\"profile_traffic_statistics_summary\">禁用后将不统计使用的流量</string>\n    <string name=\"app_statistics_disabled\">应用流量统计已禁用</string>\n    <string name=\"pcap_notice\">Pcap 文件将被保存到 %s</string>\n    <string name=\"profile_traffic_statistics\">配置流量统计</string>\n    <string name=\"stun_test_summary\">使用 STUN 确定客户端的 RFC 3478 中定义的 NAT 过滤行为。</string>\n    <string name=\"stun_test\">NAT 行为发现</string>\n    <string name=\"start\">开始</string>\n    <string name=\"stun_attest_loading\">这可能需要几分钟的时间…</string>\n    <string name=\"route_play_store\">%s Play 商店规则</string>\n    <string name=\"route_opt_block_analysis\">屏蔽跟踪器</string>\n    <string name=\"naive_insecure_concurrency\">不安全并发</string>\n    <string name=\"naive_insecure_concurrency_summary\">使用N个并发的隧道连接，在恶劣的网络条件下更加强大。更多的连接使隧道更容易被发现，安全性更低。这个项目力求对流量分析有最强的安全性。以不安全的方式使用它就违背了它的目的。\n        \\n\n        \\n如果你必须使用它，先试试N=2，看看是否能解决你的问题。强烈建议不要在这里使用超过4个连接。</string>\n    <string name=\"tools_network\">网络</string>\n    <string name=\"backup\">备份</string>\n    <string name=\"backup_groups_and_configurations\">分组和配置</string>\n    <string name=\"backup_import\">导入</string>\n    <string name=\"backup_summary\">如果路由规则与配置一起备份，则自定义出站将丢失。</string>\n    <string name=\"backup_settings\">设置</string>\n    <string name=\"backup_not_file\">不是一个备份文件，预期 .json，但 %s</string>\n    <string name=\"backup_rules\">路由规则</string>\n    <string name=\"nat_result_hint\">NAT 测试结果</string>\n    <string name=\"nat_stun_server_hint\">Stun 服务器</string>\n    <string name=\"invalid_backup_file\">无效的备份文件</string>\n    <string name=\"backup_import_summary\">导入将覆盖现有的数据。</string>\n    <string name=\"backup_importing\">正导入…</string>\n    <string name=\"action_copy\">复制</string>\n    <string name=\"action_open\">打开</string>\n    <string name=\"please_update\">您的 APP (%s) 太旧了喵，%s 再不更新就没的用了喵～</string>\n    <string name=\"please_update_force\">您的 APP (%s) 版本过旧，已于 %s 停止工作，请立即升级。</string>\n    <string name=\"connection_test_delete_unavailable\">清理不可用配置</string>\n    <string name=\"packet_encoding\">包编码</string>\n    <string name=\"action_switch\">切换</string>\n    <string name=\"acquire_wake_lock\">获取唤醒锁</string>\n    <string name=\"release_wake_lock\">释放唤醒锁</string>\n    <string name=\"acquire_wake_lock_summary\">保持 CPU 开启</string>\n    <string name=\"move\">移动</string>\n    <string name=\"subscription_expire\">过期: %s</string>\n    <string name=\"update_all_subscription\">更新所有订阅</string>\n    <string name=\"exe_prefer_provider\">首选插件提供者</string>\n    <string name=\"create_shortcut\">创建快捷方式</string>\n    <string name=\"app_tls_version\">订阅最低 TLS 版本</string>\n    <string name=\"hop_interval\">端口跳跃间隔(秒)</string>\n    <string name=\"domain_strategy_for_remote\">域名策略（远程）</string>\n    <string name=\"domain_strategy_for_direct\">域名策略（直连）</string>\n    <string name=\"domain_strategy_for_server\">域名策略（服务器地址）</string>\n    <string name=\"tuic_disable_sni\">禁用 SNI</string>\n    <string name=\"tuic_reduce_rtt\">启用 0-RTT QUIC 握手</string>\n    <string name=\"tuic_congestion_controller\">拥塞控制</string>\n    <string name=\"tuic_udp_relay_mode\">UDP 转发模式</string>\n    <string name=\"menu_dashboard\">sing-box 仪表板</string>\n    <string name=\"custom_outbound_json\">自定义出站 JSON</string>\n    <string name=\"custom_config_json\">自定义配置 JSON</string>\n    <string name=\"is_outbound_only\">设置的 JSON 是出站</string>\n    <string name=\"save\">保存</string>\n    <string name=\"set_panel_url\">设置面板 URL</string>\n    <string name=\"enable_clash_api\">启用 Clash API</string>\n    <string name=\"enable_clash_api_summary\">在 127.0.0.1:9090 提供 clash api 和 yacd 仪表板</string>\n    <string name=\"xtls_flow\">Flow（VLESS 子协议）</string>\n    <string name=\"log_level\">日志级别</string>\n    <string name=\"ads\">推广</string>\n    <string name=\"need_restart\">重新启动应用程序以应用更改</string>\n    <string name=\"use_selector\">启用 selector （免重载切换节点）</string>\n    <string name=\"front_proxy\">前置代理</string>\n    <string name=\"landing_proxy\">落地代理</string>\n    <string name=\"protocol_version\">协议版本</string>\n    <string name=\"share_subscription\">分享订阅</string>\n    <string name=\"show_group_in_notification\">在通知中显示组名</string>\n    <string name=\"reset_connections\">重置连接</string>\n    <string name=\"remove_duplicate\">删除重复的服务器</string>\n    <string name=\"tls_camouflage_settings\">TLS 伪装设置</string>\n    <string name=\"mtu_help\">长按设置项以设置自定义 MTU。</string>\n    <string name=\"log_level_help\">长按设置项以设置缓冲区大小。</string>\n    <string name=\"test_concurrency\">测试并发</string>\n    <string name=\"mux_type\">Mux 协议</string>\n    <string name=\"sniff_routing\">探测结果用于路由判断</string>\n    <string name=\"sniff_override\">探测结果用于目标地址</string>\n    <string name=\"resolve_server\">根据 IPv6 策略解析服务器地址</string>\n    <string name=\"auto_select_proxy_apps\">自动选择需要代理的应用</string>\n    <string name=\"auto_select_proxy_apps_message\">自动选择需要代理的应用，这将清除您当前的选择。</string>\n    <string name=\"enable_ech\">启用 ECH 技术支持</string>\n    <string name=\"enable_ech_sum\">启用 ECH</string>\n    <string name=\"ech_settings\">ECH 设置</string>\n    <string name=\"ech_config\">ECH 配置</string>\n    <string name=\"http_upgrade_host\">HTTPUpgrade 主机</string>\n    <string name=\"http_upgrade_path\">HTTPUpgrade 路径</string>\n    <string name=\"update_current_subscription\">更新当前组订阅</string>\n    <string name=\"group_not_subscription\">组类型不是订阅</string>\n    <string name=\"allow_insecure_on_request_sum\">更新订阅的时候允许不安全的连接</string>\n    <string name=\"global_allow_insecure\">总是跳过 TLS 证书验证</string>\n    <string name=\"network_change_reset_connections\">当网络发生变化时重置出站连接</string>\n    <string name=\"wake_reset_connections\">当设备从睡眠状态唤醒时重置出站连接</string>\n    <string name=\"preview_version_hint\">本应用为预览版，可能存在诸多问题。若您不愿参与测试，请前往GitHub下载正式发布版本！</string>\n    <string name=\"check_update_preview\">检查预览版更新</string>\n    <string name=\"check_update_release\">检查正式版更新</string>\n    <string name=\"update_dialog_title\">发现新版本</string>\n    <string name=\"update_dialog_message\">当前版本：%1$s\\n可升级版本：%2$s\\n是否前往下载？</string>\n    <string name=\"check_update_no\">检查成功，但没有更新。</string>\n    <string name=\"reset_settings\">恢复默认设置</string>\n    <string name=\"reset_settings_message\">恢复默认设置，但节点、分组等数据将保留。如需完全清除数据，请在系统设置中直接清除应用数据。</string>\n    <string name=\"minimize\">最小化</string>\n    <string name=\"app_list_permission_denied\">无法读取已安装的应用。\\n这通常是由于系统限制了应用的读取权限（例如某些中国厂商系统）。\\n请在系统设置中授予权限。</string>\n    <string name=\"open_app_settings\">打开系统设置</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rHK/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"github\">源碼</string>\n    <string name=\"app_version\">版本</string>\n    <string name=\"version_x\">版本 (%s)</string>\n    <string name=\"menu_about\">關於</string>\n    <string name=\"telegram\">Telegram 更新頻道</string>\n    <string name=\"app_desc\">以 Kotlin 編寫的 Android 通用代理工具鏈。</string>\n    <string name=\"group_update\">更新</string>\n    <string name=\"theme\">主題</string>\n    <string name=\"document\">文件</string>\n    <string name=\"project\">項目</string>\n    <string name=\"oss_licenses\">開放源碼授權條款</string>\n    <string name=\"menu_group\">分組</string>\n    <string name=\"group_name\">分組名稱</string>\n    <string name=\"group_status_empty_subscription\">尚未更新</string>\n    <string name=\"group_no_difference\">%s: 沒有變化</string>\n    <string name=\"group_updated\">%s: 更新了 %d 個代理</string>\n    <string name=\"group_added\">已新增：\n\\n%s</string>\n    <string name=\"group_changed\">已更新：\n\\n%s</string>\n    <string name=\"group_deleted\">已移除：\n\\n%s</string>\n    <string name=\"connection_test_testing\">測試中……</string>\n    <string name=\"connection_test_available\">連接成功: HTTPS 握手需時 %dms</string>\n    <string name=\"tls\">使用 TLS</string>\n    <string name=\"server_address\">伺服器</string>\n    <string name=\"server_port\">伺服器端口</string>\n    <string name=\"username\">用户名稱</string>\n    <string name=\"username_opt\">用户名稱 （可選）</string>\n    <string name=\"key_opt\">鑰匙（可選）</string>\n    <string name=\"password\">密碼</string>\n    <string name=\"enc_method\">加密方式</string>\n    <string name=\"protocol\">協議</string>\n    <string name=\"route_opt_block_analysis\">封鎖追蹤器</string>\n    <string name=\"domain_strategy\">域名解析策略</string>\n    <string name=\"share_qr_nfc\">二維碼</string>\n    <string name=\"plugin\">插件</string>\n    <string name=\"action_download\">下載</string>\n    <string name=\"install_from_fdroid\">從 F-Droid 安裝</string>\n    <string name=\"menu_tools\">工具</string>\n    <string name=\"tools_network\">網絡</string>\n    <string name=\"backup\">備份</string>\n    <string name=\"auto_connect_summary\">在開機或本程式更新後將自動重新啓用代理</string>\n    <string name=\"delete\">移除</string>\n    <string name=\"enable\">啟用</string>\n    <string name=\"donate_info\">貓貓好得意 請比貓貓錢</string>\n    <string name=\"route_bypass\">繞過</string>\n    <string name=\"route_block\">封鎖</string>\n    <string name=\"add_profile_methods_scan_qr_code\">掃描二維碼</string>\n    <string name=\"install_from_play_store\">從 Play Store 安裝</string>\n    <string name=\"auto_update\">自動更新</string>\n    <string name=\"auto_connect\">自動連接</string>\n    <string name=\"disable\">禁用</string>\n    <string name=\"auto\">自動</string>\n    <string name=\"action_scan_china_apps\">尋找國產應用程式</string>\n    <string name=\"download\">下載</string>\n    <string name=\"apply\">套用</string>\n    <string name=\"random\">隨機</string>\n    <string name=\"connection_test\">連接測試</string>\n    <string name=\"group_create\">創建分組</string>\n    <string name=\"group_delete_confirm_prompt\">你確定要移除這個分組嗎？</string>\n    <string name=\"group_status_proxies\">%d 個代理</string>\n    <string name=\"group_status_proxies_subscription\">%d 個代理 | 於 %s 更新</string>\n    <string name=\"group_diff\">變化 (%s)</string>\n    <string name=\"group_show_diff\">變化</string>\n    <string name=\"group_duplicate\">重複：\n\\n%s</string>\n    <string name=\"general_settings\">應用程式設定</string>\n    <string name=\"cag_misc\">其他設定</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_error\">失敗：%s</string>\n    <string name=\"connection_test_available_http\">連接成功: HTTP 握手需時 %dms</string>\n    <string name=\"password_opt\">密碼（可選）</string>\n    <string name=\"start\">開始</string>\n    <string name=\"quick_toggle\">切換</string>\n    <string name=\"quick_enable\">啟用</string>\n    <string name=\"quick_disable\">停用</string>\n    <string name=\"group_edit\">編輯</string>\n    <string name=\"cag_ws\">WebSocket 設定</string>\n    <string name=\"show_direct_speed\">顯示直連速度</string>\n    <string name=\"security_settings\">安全設定</string>\n    <string name=\"speed_detail\">代理： %1$s↑ %2$s↓\n\\n直連： %3$s↑ %4$s↓</string>\n    <string name=\"cag_dns\">DNS 設定</string>\n    <string name=\"direct_dns\">直連 DNS</string>\n    <string name=\"enable_dns_routing\">啟用 DNS 路由</string>\n    <string name=\"dns_hosts\">域名重寫</string>\n    <string name=\"transproxy_mode\">透明代理模式</string>\n    <string name=\"encryption\">加密</string>\n    <string name=\"route_rules_official\">官方</string>\n    <string name=\"route_opt_block_ads\">封鎖廣告</string>\n    <string name=\"route_asset_no_update\">沒有更新</string>\n    <string name=\"insecure\">不安全</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing 不可用</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string name=\"app_desc\">適用於 Android 的通用代理工具鏈，由 Kotlin 編寫。</string>\n    <string name=\"project\">計畫</string>\n    <string name=\"github\">原始碼</string>\n    <string name=\"telegram\">Telegram 更新頻道</string>\n    <string name=\"oss_licenses\">開放原始碼授權條款</string>\n    <string name=\"app_version\">版本</string>\n    <string name=\"logcat\">匯出 debug 所需資訊</string>\n    <string name=\"menu_configuration\">設定檔</string>\n    <string name=\"menu_group\">群組</string>\n    <string name=\"menu_about\">關於</string>\n    <string name=\"quick_toggle\">切換</string>\n    <string name=\"quick_enable\">啟用</string>\n    <string name=\"quick_disable\">停用</string>\n    <!-- group -->\n    <string name=\"group_name\">群組名稱</string>\n    <string name=\"group_update\">更新</string>\n    <string name=\"group_subscription_link\">訂閱連結</string>\n    <string name=\"group_name_required\">需要群組名稱</string>\n    <string name=\"group_create\">建立新組</string>\n    <string name=\"group_create_subscription\">建立新訂閱</string>\n    <string name=\"group_edit\">編輯</string>\n    <string name=\"group_edit_subscription\">編輯訂閱</string>\n    <string name=\"group_status_empty\">空白群組</string>\n    <string name=\"group_status_empty_subscription\">尚未更新</string>\n    <string name=\"group_status_proxies\">%d 個代理</string>\n    <string name=\"group_status_proxies_subscription\">%d 個代理 | 更新於 %s</string>\n    <string name=\"group_no_difference\">%s: 沒有變化</string>\n    <string name=\"group_updated\">%s: 更新了 %d 個代理</string>\n    <string name=\"group_show_diff\">變化</string>\n    <string name=\"group_delete_confirm_prompt\">您真的希望刪除這個群組嗎？</string>\n    <string name=\"group_added\">新增：\n\\n%s</string>\n    <string name=\"group_changed\">更新：\n\\n%s</string>\n    <string name=\"group_deleted\">刪除：\n\\n%s</string>\n    <!-- misc -->\n    <string name=\"service_mode\">執行模式</string>\n    <string name=\"service_mode_proxy\">代理</string>\n    <string name=\"port_proxy\">代理連接埠</string>\n    <string name=\"port_http\">HTTP 代理連接埠</string>\n    <string name=\"port_local_dns\">本地 DNS 連接埠</string>\n    <string name=\"allow_access\">允許來自區域網路的連線</string>\n    <string name=\"allow_access_sum\">繫結傳入伺服器至 0.0.0.0</string>\n    <string name=\"cag_dns\">DNS 設定</string>\n    <string name=\"cag_route\">路由設定</string>\n    <string name=\"inbound_settings\">傳入設定</string>\n    <string name=\"general_settings\">應用程式設定</string>\n    <string name=\"require_http\">啟用 HTTP 傳入伺服器</string>\n    <string name=\"cag_ws\">WebSocket 設定</string>\n    <string name=\"ws_browser_forwarding\">瀏覽器轉發</string>\n    <string name=\"ws_browser_forwarding_sum\">轉傳對應的 WebSocket 連線至瀏覽器。</string>\n    <string name=\"speed_interval\">速度通知更新間隔</string>\n    <string name=\"traffic\">%1$s↑ %2$s↓</string>\n    <string name=\"speed_detail\">代理： %1$s↑ %2$s↓\n\\n直連： %3$s↑ %4$s↓</string>\n    <string name=\"speed\">%s/s</string>\n    <string name=\"connection_test_testing\">正在測試…</string>\n    <string name=\"connection_test_available\">成功：HTTPS 握手花費 %dms</string>\n    <string name=\"connection_test_error\">失敗：%s</string>\n    <string name=\"connection_test_fail\">網路不可用</string>\n    <string name=\"connection_test_error_status_code\">錯誤碼：#%d</string>\n    <!-- proxy category -->\n    <string name=\"profile_name\">設定檔名稱</string>\n    <string name=\"server_address\">伺服器</string>\n    <string name=\"server_port\">遠端連接埠</string>\n    <string name=\"username_opt\">使用者名稱（可選）</string>\n    <string name=\"password_opt\">密碼（可選）</string>\n    <string name=\"password\">密碼</string>\n    <string name=\"enc_method\">加密方式</string>\n    <string name=\"protocol\">設定</string>\n    <string name=\"protocol_param\">通訊協定參數</string>\n    <string name=\"obfs\">混淆</string>\n    <string name=\"obfs_param\">混淆參數</string>\n    <string name=\"uuid\">使用者 ID</string>\n    <string name=\"alter_id\">替代 ID</string>\n    <string name=\"network\">網路</string>\n    <string name=\"header_type\">標頭類型</string>\n    <string name=\"tls\">使用 TLS</string>\n    <string name=\"sni\">伺服器名稱指示</string>\n    <string name=\"encryption\">加密</string>\n    <!-- feature category -->\n    <string name=\"metered\">計量付費提示</string>\n    <string name=\"metered_summary\">提示系統將 VPN 視為計量付費</string>\n    <string name=\"menu_route\">路由</string>\n    <string name=\"proxied_apps\">應用程式 VPN 模式</string>\n    <string name=\"proxied_apps_summary\">為被選取的應用程式設定 VPN 模式</string>\n    <string name=\"on\">啟用</string>\n    <string name=\"off\">停用</string>\n    <string name=\"search_apps\">搜尋…</string>\n    <string name=\"scanning\">正在掃描…</string>\n    <string name=\"invert_selections\">反向選取</string>\n    <string name=\"clear_selections\">清除選取</string>\n    <string name=\"bypass_apps\">略過</string>\n    <string name=\"show_system_apps\">顯示系統應用程式</string>\n    <string name=\"auto_connect\">自動連線</string>\n    <string name=\"auto_connect_summary\">如果代理在開機或應用更新前正在執行則自動啟動</string>\n    <string name=\"direct_boot_aware\">允許螢幕鎖定時切換</string>\n    <string name=\"direct_boot_aware_summary\">您選取的設定檔資訊將受到較少保護</string>\n    <!-- notification category -->\n    <string name=\"service_vpn\">VPN 服務</string>\n    <string name=\"service_proxy\">代理服務</string>\n    <string name=\"forward_success\">SagerNet 已啟動。</string>\n    <string name=\"invalid_server\">伺服器名稱無效</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"stopping\">正在關閉…</string>\n    <string name=\"vpn_error\">%s</string>\n    <string name=\"vpn_permission_denied\">建立 VPN 服務的權限被拒</string>\n    <string name=\"reboot_required\">無法啟動 VPN 服務，您可能需要重新啟動您的裝置。</string>\n    <!-- alert category -->\n    <string name=\"profile_empty\">請選取一個設定檔</string>\n    <string name=\"connect\">連線</string>\n    <string name=\"clipboard_empty\">剪貼簿為空白</string>\n    <!-- menu category -->\n    <string name=\"settings\">設定</string>\n    <string name=\"edit\">編輯</string>\n    <string name=\"share\">分享</string>\n    <string name=\"add_profile\">新增設定檔</string>\n    <string name=\"action_create_group\">空白群組</string>\n    <string name=\"action_from_link\">來自訂閱</string>\n    <string name=\"action_scan_china_apps\">掃描中國應用程式</string>\n    <string name=\"action_export_file\">匯出至檔案</string>\n    <string name=\"action_export_clipboard\">匯出至剪貼簿</string>\n    <string name=\"action_import\">自剪貼簿匯入</string>\n    <string name=\"action_import_file\">自檔案匯入</string>\n    <string name=\"action_export_msg\">匯出成功！</string>\n    <string name=\"action_export_err\">匯出失敗。</string>\n    <string name=\"action_import_msg\">匯入成功！</string>\n    <string name=\"action_import_err\">匯入失敗。</string>\n    <!-- share -->\n    <!-- profile -->\n    <string name=\"profile_config\">設定檔設定</string>\n    <string name=\"delete\">刪除</string>\n    <string name=\"delete_confirm_prompt\">您確定要刪除此設定檔嗎？</string>\n    <string name=\"share_qr_nfc\">QR 碼</string>\n    <string name=\"add_profile_methods_scan_qr_code\">掃描 QR 碼</string>\n    <string name=\"add_profile_methods_manual_settings\">手動設定</string>\n    <plurals name=\"removed\">\n        <item quantity=\"other\">已移除 %d 個項目</item>\n    </plurals>\n    <plurals name=\"added\">\n        <item quantity=\"other\">已新增 %d 個代理</item>\n    </plurals>\n    <string name=\"undo\">復原</string>\n    <!-- tasker -->\n    <!-- status -->\n    <string name=\"connecting\">正在連線…</string>\n    <string name=\"vpn_connected\">已連線，輕觸以檢查連線能力</string>\n    <string name=\"not_connected\">未連線</string>\n    <!-- subscriptions -->\n    <string name=\"subscriptions\">訂閱</string>\n    <string name=\"cleartext_http_warning\">純文字 HTTP 流量並不安全</string>\n    <string name=\"error_title\">錯誤</string>\n    <string name=\"no_proxies_found\">此連結不包含任何代理</string>\n    <!-- plugin -->\n    <string name=\"plugin\">外掛程式</string>\n    <string name=\"plugin_configure\">設定…</string>\n    <string name=\"plugin_disabled\">已停用</string>\n    <string name=\"plugin_unknown\">未知外掛程式 %s</string>\n    <string name=\"plugin_untrusted\">警告：此外掛程式並非來自可靠的可信來源。</string>\n    <string name=\"plugin_auto_connect_unlock_only\">此外掛程式可能不支援自動連線</string>\n    <string name=\"proxy_cat\">伺服器設定</string>\n    <string name=\"unsaved_changes_prompt\">要儲存變更嗎？</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"apply\">套用</string>\n    <string name=\"license\">授權條款</string>\n    <string name=\"document\">文件</string>\n    <string name=\"early_data_header_name\">前置資料標頭</string>\n    <string name=\"certificates\">憑證</string>\n    <string name=\"alpn\">應用層通訊協定協商</string>\n    <string name=\"grpc_service_name\">gRPC 服務名稱</string>\n    <string name=\"http_path\">HTTP 路徑</string>\n    <string name=\"http_host\">HTTP 主機</string>\n    <string name=\"ws_path\">WebSocket 路徑</string>\n    <string name=\"ws_host\">WebSocket 主機</string>\n    <string name=\"security\">傳輸層安全性協定</string>\n    <string name=\"key_opt\">金鑰（選用）</string>\n    <string name=\"username\">使用者名稱</string>\n    <string name=\"connection_test_url\">連線測試 URL</string>\n    <string name=\"transproxy_mode\">透明代理模式</string>\n    <string name=\"require_transproxy\">啟用透明代理傳入伺服器</string>\n    <string name=\"dns_hosts\">網域重寫</string>\n    <string name=\"dns_routing_message\">使用在路由中被略過的直連 DNS 解析網域。請注意潛在的 DNS 洩漏</string>\n    <string name=\"enable_dns_routing\">啟用 DNS 路由</string>\n    <string name=\"direct_dns\">直連 DNS</string>\n    <string name=\"remote_dns\">遠端 DNS</string>\n    <string name=\"connection_test_available_http\">成功：HTTP 握手花費 %d 毫秒</string>\n    <string name=\"allow_insecure_sum\">停用憑證檢查。當啟用時，此設定檔將和純文字同等安全</string>\n    <string name=\"allow_insecure\">允許不安全的連線</string>\n    <string name=\"security_settings\">安全設定</string>\n    <string name=\"show_direct_speed_sum\">在通知中一併顯示未被代理的流量速度</string>\n    <string name=\"show_direct_speed\">顯示直連速度</string>\n    <string name=\"show_stop_sum\">如果您不想使用快速設定圖塊進行切換</string>\n    <string name=\"show_stop\">顯示停止按鈕</string>\n    <string name=\"cag_misc\">其他設定</string>\n    <string name=\"port_transproxy\">透明代理連接埠</string>\n    <string name=\"group_duplicate\">重複：\n\\n%s</string>\n    <string name=\"group_diff\">差異（%s）</string>\n    <string name=\"tile_title\">切換器</string>\n    <string name=\"group_default\">未分組</string>\n    <string name=\"version_x\">版本 (%s)</string>\n    <string name=\"extra_headers\">額外標頭</string>\n    <string name=\"config_type\">設定類型</string>\n    <string name=\"theme\">主題</string>\n    <string name=\"edit_config\">編輯設定</string>\n    <string name=\"ipv6\">IPv6 路由</string>\n    <string name=\"prefer\">優先</string>\n    <string name=\"only\">僅</string>\n    <string name=\"route_add\">建立路由規則</string>\n    <string name=\"delete_route_prompt\">您真的希望移除此路由規則嗎？</string>\n    <string name=\"route_name\">路由名稱</string>\n    <string name=\"route_proxy\">代理</string>\n    <string name=\"route_bypass\">繞過</string>\n    <string name=\"route_block\">封鎖</string>\n    <string name=\"route_profile\">選取設定檔…</string>\n    <string name=\"empty_route\">空白路由規則</string>\n    <string name=\"empty_route_notice\">在儲存前設定一些規則</string>\n    <string name=\"route_bypass_domain\">%s 網域規則</string>\n    <string name=\"route_bypass_ip\">%s IP 規則</string>\n    <string name=\"route_reset\">重設</string>\n    <string name=\"route_manage_assets\">管理路由資源</string>\n    <string name=\"route_assets\">資源</string>\n    <string name=\"route_rules_provider\">規則資源提供者</string>\n    <string name=\"route_rules_official\">官方</string>\n    <string name=\"route_asset_status\">本地版本：%s</string>\n    <string name=\"route_asset_no_update\">無更新</string>\n    <string name=\"route_asset_updated\">已更新</string>\n    <string name=\"route_not_asset\">非資源檔案：預期副檔名為 .db，但 %s</string>\n    <string name=\"route_opt_bypass_lan\">略過區域網路位址</string>\n    <string name=\"route_opt_block_ads\">封鎖廣告</string>\n    <string name=\"domain_strategy\">網域解析策略</string>\n    <string name=\"traffic_sniffing\">啟用流量嗅探</string>\n    <string name=\"enable_mux\">啟用多工器</string>\n    <string name=\"mux_concurrency\">多工器同時連線數目</string>\n    <string name=\"tcp_keep_alive_interval\">TCP 保持連線傳送間隔</string>\n    <string name=\"service_failed\">失敗：</string>\n    <string name=\"select_profile\">選取設定檔</string>\n    <string name=\"proxy_chain\">代理鏈結</string>\n    <string name=\"custom_config\">客製化設定檔</string>\n    <string name=\"balancer\">負載平衡器</string>\n    <string name=\"balancer_settings\">負載平衡器設定</string>\n    <string name=\"balancer_type\">類型</string>\n    <string name=\"balancer_strategy\">策略</string>\n    <string name=\"chain_settings\">鏈結設定</string>\n    <string name=\"config_settings\">設定檔設定</string>\n    <string name=\"circular_reference\">循環參考</string>\n    <string name=\"circular_reference_sum\">路由中不能包含自己。</string>\n    <string name=\"clear_profiles\">清除</string>\n    <string name=\"action_export\">匯出</string>\n    <string name=\"insecure\">不安全</string>\n    <string name=\"deprecated\">已被取代</string>\n    <string name=\"no_proxies_found_in_subscription\">未在此訂閱中找到代理</string>\n    <string name=\"no_proxies_found_in_file\">未在此檔案中找到代理</string>\n    <string name=\"no_proxies_found_in_clipboard\">未在剪貼簿中找到代理</string>\n    <string name=\"deduplication\">重複資料刪除</string>\n    <string name=\"donate\">捐贈</string>\n    <string name=\"ignore_battery_optimizations\">忽略電池效能最佳化</string>\n    <string name=\"ignore_battery_optimizations_sum\">移除一些限制</string>\n    <string name=\"ss_cat\">Shadowsocks 設定</string>\n    <string name=\"need_reload\">重新載入代理服務以套用修改</string>\n    <string name=\"route_warn\">請在新增客製化規則前閱讀文件，否則您可能會無法連線至網際網路。</string>\n    <string name=\"lines\">%d 行</string>\n    <string name=\"night_mode\">夜間模式</string>\n    <string name=\"follow_system\">跟隨系統</string>\n    <string name=\"enable\">啟用</string>\n    <string name=\"disable\">停用</string>\n    <string name=\"auto\">自動</string>\n    <string name=\"enable_log\">啟用記錄檔</string>\n    <string name=\"enable_log_sum\">用於除錯用途</string>\n    <string name=\"list\">清單</string>\n    <string name=\"random\">隨機</string>\n    <string name=\"api_port\">API 連接埠</string>\n    <string name=\"probe_interval\">負載平衡器觀測間隔</string>\n    <string name=\"standard\">標準</string>\n    <string name=\"unavailable\">無法使用</string>\n    <string name=\"always_show_address\">始終顯示位址</string>\n    <string name=\"always_show_address_sum\">使用在設定檔卡片中顯示伺服器位址</string>\n    <string name=\"clear_traffic_statistics\">清除流量統計資料</string>\n    <string name=\"connection_test\">連線測試</string>\n    <string name=\"connection_test_tcp_ping_unavailable\">TCPing 無法使用</string>\n    <string name=\"connection_test_clear_results\">清除測試結果</string>\n    <string name=\"connection_test_icmp_ping_unavailable\">ICMPing 無法使用</string>\n    <string name=\"connection_test_domain_not_found\">網域不存在</string>\n    <string name=\"connection_test_refused\">連線被拒</string>\n    <string name=\"donate_info\">貓貓很可愛 請給貓貓錢</string>\n    <string name=\"connection_test_unreachable\">無法連線</string>\n    <string name=\"connection_test_timeout\">逾時</string>\n    <string name=\"append_http_proxy\">為 VPN 附加 HTTP 代理</string>\n    <string name=\"append_http_proxy_sum\">HTTP 代理將直接被支援的瀏覽器/應用程式使用，而無需經過虛擬網路卡（Android 10+）</string>\n    <string name=\"trojan_provider\">Trojan 提供程式</string>\n    <string name=\"protocol_settings\">協議設定</string>\n    <string name=\"group_basic\">基本</string>\n    <string name=\"group_settings\">群組設定</string>\n    <string name=\"subscription\">訂閱</string>\n    <string name=\"subscription_settings\">訂閱設定</string>\n    <string name=\"group_type\">群組類型</string>\n    <string name=\"subscription_type\">訂閱類型</string>\n    <string name=\"delete_group_prompt\">您真的希望移除此群組嗎？</string>\n    <string name=\"force_resolve\">強制解析</string>\n    <string name=\"force_resolve_sum\">在更新時將所有網域解析為 IP。主機名和伺服器名稱指示將在可能時被自動附加</string>\n    <string name=\"deduplication_sum\">在更新時移除重複的設定檔</string>\n    <string name=\"raw\">原始</string>\n    <string name=\"update_settings\">更新設定</string>\n    <string name=\"auto_update\">自動更新</string>\n    <string name=\"auto_update_delay\">自動更新間隔（分鐘）</string>\n    <string name=\"update_when_connected_only\">僅在已連線時更新</string>\n    <string name=\"update_when_connected_only_sum\">防止 IP 位址洩漏</string>\n    <string name=\"subscription_user_agent\">使用者代理</string>\n    <string name=\"confirm\">確認</string>\n    <string name=\"missing_plugin\">遺失外掛程式</string>\n    <string name=\"profile_requiring_plugin\">設定檔 %s 需要外掛程式 %s，但其未被安裝。</string>\n    <string name=\"action_learn_more\">深入瞭解</string>\n    <string name=\"action_download\">下載</string>\n    <string name=\"install_from_play_store\">從 Play 商店安裝</string>\n    <string name=\"install_from_fdroid\">從 F-Droid 安裝</string>\n    <string name=\"download\">下載</string>\n    <string name=\"ooc_subscription_token_invalid\">無效的 OOCv1 權杖</string>\n    <string name=\"update_subscription_warning\">代理尚未連線，您真的希望繼續更新嗎？</string>\n    <string name=\"ooc_warning\">警告</string>\n    <string name=\"ooc_missing_protocol\">此訂閱需支援 %s 通訊協定，但其未被安裝，不支援的設定檔將被忽略。</string>\n    <string name=\"service_subscription\">訂閱更新服務</string>\n    <string name=\"subscription_update\">訂閱更新</string>\n    <string name=\"subscription_update_message\">正在更新 %s …</string>\n    <string name=\"group_filter\">篩選</string>\n    <string name=\"subscription_used\">已用 %s</string>\n    <string name=\"subscription_traffic\">已用 %s / 剩餘 %s</string>\n    <string name=\"subscription_import\">匯入訂閱</string>\n    <string name=\"subscription_import_message\">您確定要匯入訂閱 %s 嗎？如果訂閱的來源不可信，您的 IP 和行為將被洩漏。</string>\n    <string name=\"profile_import\">匯入設定檔</string>\n    <string name=\"profile_import_message\">您確定要匯入設定檔 %s 嗎？</string>\n    <string name=\"clear_profiles_message\">您確定要清除此群組嗎？</string>\n    <string name=\"apps\">應用程式</string>\n    <string name=\"select_apps\">選擇應用程式</string>\n    <string name=\"apps_message\">%d 個應用程式</string>\n    <string name=\"file_manager_missing\">您的裝置缺少 Android 標準檔案選擇器, 請安裝一個, 如 Material Files.</string>\n    <string name=\"menu_traffic\">流量</string>\n    <string name=\"shadowsocks_plugin_v2ray\">V2Ray (Shadowsocks Android 外掛程式)</string>\n    <string name=\"shadowsocks_plugin_simple_obfs\">Simple Obfs (Shadowsocks Android 外掛程式)</string>\n    <string name=\"plugin_exists_but_on_shit_system\">設定檔 %s 需要外掛程式 %s，但是您的專有設備提供商（通常也是監視資本主義巨頭和惡意軟體製造商）篡改了你的 Android 系統，使此外掛程式不可用。</string>\n    <string name=\"ssh_private_key_passphrase\">私密金鑰複雜密碼</string>\n    <string name=\"ssh_private_key\">私密金鑰</string>\n    <string name=\"ssh_public_key\">公開金鑰</string>\n    <string name=\"ssh_auth_type_none\">無</string>\n    <string name=\"no_statistics\">暫無統計資料</string>\n    <string name=\"route_need_vpn\">路由規則`%s` 需要 VPN 模式以生效，因此被忽略。</string>\n    <string name=\"route_for\">%s 的規則</string>\n    <string name=\"app_no_launcher\">此應用程式沒有介面。</string>\n    <string name=\"copy_failed\">複製失敗。</string>\n    <string name=\"copy_success\">成功複製！</string>\n    <string name=\"create_rule\">建立規則</string>\n    <string name=\"open_market\">開啟應用商店</string>\n    <string name=\"open_settings\">開啟設定</string>\n    <string name=\"open_app\">開啟應用程式</string>\n    <string name=\"copy_package_name\">複製應用程式套件名稱</string>\n    <string name=\"copy_label\">複製名稱</string>\n    <string name=\"udp_connections\">%d 個 UDP 連線</string>\n    <string name=\"tcp_connections\">%d 個 TCP 連線</string>\n    <string name=\"group_order_by_delay\">以延遲</string>\n    <string name=\"group_order_by_name\">以名稱</string>\n    <string name=\"group_order_origin\">原始</string>\n    <string name=\"group_order\">順序</string>\n    <string name=\"hysteria_disable_mtu_discovery\">停用路徑 MTU 探索</string>\n    <string name=\"hysteria_connection_receive_window\">QUIC 連線接收視窗</string>\n    <string name=\"hysteria_stream_receive_window\">QUIC 串流接收視窗</string>\n    <string name=\"experimental_settings\">實驗性</string>\n    <string name=\"hysteria_download_mbps\">最大下載速度（Mbps）</string>\n    <string name=\"hysteria_upload_mbps\">最大上傳速度（Mbps）</string>\n    <string name=\"hysteria_auth_payload\">認證密碼</string>\n    <string name=\"hysteria_auth_type\">認證類型</string>\n    <string name=\"hysteria_obfs\">混淆密碼</string>\n    <string name=\"menu_tools\">工具</string>\n    <string name=\"translate_platform\">翻譯平台</string>\n    <string name=\"menu_log\">記錄檔</string>\n    <string name=\"profile_traffic_statistics_summary\">當停用時，使用的流量將不會被統計</string>\n    <string name=\"app_statistics_disabled\">應用程式流量統計資料被停用</string>\n    <string name=\"tun_implementation\">TUN 實作</string>\n    <string name=\"destination_override\">覆寫目的地位址</string>\n    <string name=\"resolve_destination\">解析目的地位址</string>\n    <string name=\"clear_logcat\">清除記錄檔</string>\n    <string name=\"wireguard_local_address\">本機位址</string>\n    <string name=\"wireguard_public_key\">節點公開金鑰</string>\n    <string name=\"wireguard_psk\">節點預先共用金鑰</string>\n    <string name=\"warp_license\">CloudFlare Warp 是一個免費的 WireGuard VPN 提供者。使用此服務表示您同意其服務條款。</string>\n    <string name=\"warp_generate\">產生設定檔</string>\n    <string name=\"generating\">正在產生…</string>\n    <string name=\"pcap_notice\">Pcap 檔案將被儲存至 %s</string>\n    <string name=\"profile_traffic_statistics\">設定檔流量統計資料</string>\n    <string name=\"resolve_destination_summary\">如果目的地位址是一個網域，則基於 IPv6 策略傳出。</string>\n    <string name=\"destination_override_summary\">使用被偵測到的網域複寫目的地位址，而不是僅用於路由</string>\n    <string name=\"route_opt_block_analysis\">封鎖分析</string>\n    <string name=\"route_play_store\">%s Google Play商店規則</string>\n    <string name=\"naive_insecure_concurrency\">不安全並發</string>\n    <string name=\"stun_test\">NAT 行為尋找</string>\n    <string name=\"naive_insecure_concurrency_summary\">使用N個並發的隧道能使網路條件較差时更加可用。但數量越多，越易被偵測，安全性越低。由於本項目致力於面對流量分析的安全性，以不安全的方式使用違背了其初忠。\n\\n\n\\n如一定要使用，請先嘗試 N=2，觀察其是否解決了您的問題。強烈反對於此使用多於4個隧道。</string>\n    <string name=\"stun_attest_loading\">這可能需要數分鐘…</string>\n    <string name=\"stun_test_summary\">使用 STUN 確定用戶端的 NAT 映射行為和 RFC 5780 中定義的 NAT 篩選行為。</string>\n    <string name=\"start\">開始</string>\n    <string name=\"backup\">備份</string>\n    <string name=\"backup_rules\">路由規則</string>\n    <string name=\"invalid_backup_file\">無效的備份檔</string>\n    <string name=\"tools_network\">網路</string>\n    <string name=\"backup_settings\">設定</string>\n    <string name=\"backup_import\">匯入</string>\n    <string name=\"backup_import_summary\">匯入將覆蓋現有資料。</string>\n    <string name=\"backup_importing\">正在匯入…</string>\n    <string name=\"backup_groups_and_configurations\">分組和設定檔</string>\n    <string name=\"backup_summary\">如果路由規則不與設定檔一起備份，則自訂出站將丟失。</string>\n    <string name=\"backup_not_file\">不是一個備份檔案，預計 .json，但 %s</string>\n    <string name=\"packet_encoding\">封包編碼</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/anytls_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"password\"\n            app:title=\"@string/password\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/security_settings\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"sni\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"allowInsecure\"\n            app:title=\"@string/allow_insecure\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"alpn\"\n            app:title=\"@string/alpn\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"certificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"\"\n            app:entries=\"@array/utls_fingerprint_entry\"\n            app:entryValues=\"@array/utls_fingerprint_entry\"\n            app:icon=\"@drawable/ic_baseline_fingerprint_24\"\n            app:key=\"utlsFingerprint\"\n            app:title=\"@string/utls_fingerprint\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/backup_descriptor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<full-backup-content xmlns:tools=\"http://schemas.android.com/tools\"\n                     tools:ignore=\"FullBackupContent\">\n    <include domain=\"database\" path=\"sager_net.db\"/>\n    <!-- No device storage yet in Android 6.0 -->\n    <include domain=\"database\" path=\"configuration.db\"/>\n    <include domain=\"device_database\" path=\"configuration.db\"/>\n</full-backup-content>\n"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<data-extraction-rules xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"FullBackupContent\">\n    <cloud-backup>\n        <include\n            domain=\"database\"\n            path=\"sager_net.db\" />\n        <include\n            domain=\"device_database\"\n            path=\"configuration.db\" />\n    </cloud-backup>\n    <device-transfer>\n        <include\n            domain=\"database\"\n            path=\"sager_net.db\" />\n        <include\n            domain=\"device_database\"\n            path=\"configuration.db\" />\n    </device-transfer>\n</data-extraction-rules>\n"
  },
  {
    "path": "app/src/main/res/xml/balancer_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <moe.matsuri.nb4a.ui.SimpleMenuPreference\n        app:defaultValue=\"0\"\n        app:entries=\"@array/balancer_type\"\n        app:entryValues=\"@array/int_array_2\"\n        app:icon=\"@drawable/ic_baseline_nfc_24\"\n        app:key=\"balancerType\"\n        app:title=\"@string/balancer_type\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <moe.matsuri.nb4a.ui.SimpleMenuPreference\n        app:entries=\"@array/balancer_strategy_entry\"\n        app:entryValues=\"@array/balancer_strategy_value\"\n        app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n        app:key=\"balancerStrategy\"\n        app:title=\"@string/balancer_strategy\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <io.nekohasekai.sagernet.widget.GroupPreference\n        app:icon=\"@drawable/ic_baseline_view_list_24\"\n        app:key=\"balancerGroup\"\n        app:title=\"@string/menu_group\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/cache_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <cache-path name=\"cache\" path=\"/\"/>\n</paths>\n"
  },
  {
    "path": "app/src/main/res/xml/config_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreference\n        app:icon=\"@drawable/ic_baseline_import_contacts_24\"\n        app:key=\"isOutboundOnly\"\n        app:title=\"@string/is_outbound_only\" />\n\n    <moe.matsuri.nb4a.ui.EditConfigPreference\n        app:icon=\"@drawable/ic_baseline_layers_24\"\n        app:key=\"serverConfig\"\n        app:title=\"@string/custom_config\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/global_preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <PreferenceCategory app:title=\"@string/general_settings\">\n        <SwitchPreference\n            app:defaultValue=\"false\"\n            app:icon=\"@drawable/ic_communication_phonelink_ring\"\n            app:key=\"isAutoConnect\"\n            app:summary=\"@string/auto_connect_summary\"\n            app:title=\"@string/auto_connect\" />\n        <moe.matsuri.nb4a.ui.ColorPickerPreference\n            android:title=\"@string/theme\"\n            app:icon=\"@drawable/ic_baseline_color_lens_24\"\n            app:key=\"appTheme\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/night_mode\"\n            app:entryValues=\"@array/int_array_4\"\n            app:icon=\"@drawable/ic_baseline_wb_sunny_24\"\n            app:key=\"nightTheme\"\n            app:title=\"@string/night_mode\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"vpn\"\n            app:entries=\"@array/service_modes\"\n            app:entryValues=\"@array/service_mode_values\"\n            app:icon=\"@drawable/ic_device_developer_mode\"\n            app:key=\"serviceMode\"\n            app:title=\"@string/service_mode\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/tun_implementation\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_flip_camera_android_24\"\n            app:key=\"tunImplementation\"\n            app:title=\"@string/tun_implementation\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.MTUPreference\n            app:defaultValue=\"9000\"\n            app:entries=\"@array/mtu_select\"\n            app:entryValues=\"@array/mtu_select\"\n            app:icon=\"@drawable/baseline_public_24\"\n            app:key=\"mtu\"\n            app:title=\"@string/mtu\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"1000\"\n            app:entries=\"@array/notification_entry\"\n            app:entryValues=\"@array/notification_value\"\n            app:icon=\"@drawable/ic_baseline_shutter_speed_24\"\n            app:key=\"speedInterval\"\n            app:title=\"@string/speed_interval\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:defaultValue=\"true\"\n            app:key=\"profileTrafficStatistics\"\n            app:summary=\"@string/profile_traffic_statistics_summary\"\n            app:title=\"@string/profile_traffic_statistics\" />\n        <SwitchPreference\n            app:defaultValue=\"true\"\n            app:icon=\"@drawable/ic_baseline_speed_24\"\n            app:key=\"showDirectSpeed\"\n            app:summary=\"@string/show_direct_speed_sum\"\n            app:title=\"@string/show_direct_speed\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:key=\"showGroupInNotification\"\n            app:title=\"@string/show_group_in_notification\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_center_focus_weak_24\"\n            app:key=\"alwaysShowAddress\"\n            app:summary=\"@string/always_show_address_sum\"\n            app:title=\"@string/always_show_address\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_device_data_usage\"\n            app:key=\"meteredNetwork\"\n            app:summary=\"@string/metered_summary\"\n            app:title=\"@string/metered\" />\n        <SwitchPreference\n            app:icon=\"@drawable/baseline_developer_board_24\"\n            app:key=\"acquireWakeLock\"\n            app:summary=\"@string/acquire_wake_lock_summary\"\n            app:title=\"@string/acquire_wake_lock\" />\n        <moe.matsuri.nb4a.ui.LongClickListPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/log_level\"\n            app:entryValues=\"@array/int_array_5\"\n            app:icon=\"@drawable/ic_baseline_bug_report_24\"\n            app:key=\"logLevel\"\n            app:title=\"@string/log_level\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.EditConfigPreference\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"globalCustomConfig\"\n            app:title=\"@string/custom_config\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/cag_route\">\n        <SwitchPreference\n            app:icon=\"@drawable/ic_navigation_apps\"\n            app:key=\"proxyApps\"\n            app:summary=\"@string/proxied_apps_summary\"\n            app:title=\"@string/proxied_apps\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"bypassLan\"\n            app:title=\"@string/route_opt_bypass_lan\" />\n        <SwitchPreference\n            app:key=\"bypassLanInCore\"\n            app:title=\"@string/bypass_lan_in_core\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"1\"\n            app:entries=\"@array/traffic_sniffing_values\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_manage_search_24\"\n            app:key=\"trafficSniffing\"\n            app:title=\"@string/traffic_sniffing\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/baseline_wrap_text_24\"\n            app:key=\"resolveDestination\"\n            app:summary=\"@string/resolve_destination_summary\"\n            app:title=\"@string/resolve_destination\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/ipv6_mode\"\n            app:entryValues=\"@array/int_array_4\"\n            app:icon=\"@drawable/ic_image_looks_6\"\n            app:key=\"ipv6Mode\"\n            app:title=\"@string/ipv6\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/rules_dat_provider\"\n            app:entryValues=\"@array/int_array_4\"\n            app:icon=\"@drawable/ic_baseline_rule_folder_24\"\n            app:key=\"rulesProvider\"\n            app:title=\"@string/route_rules_provider\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/cag_dns\">\n        <EditTextPreference\n            app:defaultValue=\"https://dns.google/dns-query\"\n            app:icon=\"@drawable/ic_action_dns\"\n            app:key=\"remoteDns\"\n            app:title=\"@string/remote_dns\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"auto\"\n            app:entries=\"@array/dns_network_select\"\n            app:entryValues=\"@array/dns_network_select\"\n            app:key=\"domain_strategy_for_remote\"\n            app:title=\"@string/domain_strategy_for_remote\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:defaultValue=\"https://223.5.5.5/dns-query\"\n            app:icon=\"@drawable/ic_action_dns\"\n            app:key=\"directDns\"\n            app:title=\"@string/direct_dns\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"auto\"\n            app:entries=\"@array/dns_network_select\"\n            app:entryValues=\"@array/dns_network_select\"\n            app:key=\"domain_strategy_for_direct\"\n            app:title=\"@string/domain_strategy_for_direct\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"auto\"\n            app:entries=\"@array/dns_network_select\"\n            app:entryValues=\"@array/dns_network_select\"\n            app:key=\"domain_strategy_for_server\"\n            app:title=\"@string/domain_strategy_for_server\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:defaultValue=\"true\"\n            app:icon=\"@drawable/ic_baseline_camera_24\"\n            app:key=\"enableDnsRouting\"\n            app:summary=\"@string/dns_routing_message\"\n            app:title=\"@string/enable_dns_routing\" />\n        <SwitchPreference\n            app:defaultValue=\"true\"\n            app:icon=\"@drawable/ic_action_lock\"\n            app:key=\"enableFakeDns\"\n            app:summary=\"@string/fakedns_message\"\n            app:title=\"@string/enable_fakedns\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/inbound_settings\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"mixedPort\"\n            app:title=\"@string/port_proxy\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:defaultValue=\"false\"\n            app:key=\"appendHttpProxy\"\n            app:summary=\"@string/append_http_proxy_sum\"\n            app:title=\"@string/append_http_proxy\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_nat_24\"\n            app:key=\"allowAccess\"\n            app:summary=\"@string/allow_access_sum\"\n            app:title=\"@string/allow_access\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/cag_misc\">\n        <moe.matsuri.nb4a.ui.UrlTestPreference\n            app:defaultValue=\"http://cp.cloudflare.com/\"\n            app:icon=\"@drawable/ic_baseline_cast_connected_24\"\n            app:key=\"connectionTestURL\"\n            app:title=\"@string/connection_test_url\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/baseline_construction_24\"\n            app:key=\"enableClashAPI\"\n            app:summary=\"@string/enable_clash_api_summary\"\n            app:title=\"@string/enable_clash_api\" />\n        <SwitchPreference\n            app:defaultValue=\"true\"\n            app:icon=\"@drawable/ic_baseline_flip_camera_android_24\"\n            app:key=\"networkChangeResetConnections\"\n            app:title=\"@string/network_change_reset_connections\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:key=\"wakeResetConnections\"\n            app:title=\"@string/wake_reset_connections\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_action_lock_open\"\n            app:key=\"globalAllowInsecure\"\n            app:title=\"@string/global_allow_insecure\" />\n        <SwitchPreference\n            app:key=\"allowInsecureOnRequest\"\n            app:title=\"@string/allow_insecure_on_request_sum\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"1.2\"\n            app:entries=\"@array/app_tls_version\"\n            app:entryValues=\"@array/app_tls_version\"\n            app:key=\"appTLSVersion\"\n            app:title=\"@string/app_tls_version\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:key=\"showBottomBar\"\n            app:title=\"@string/show_bottom_bar\" />\n    </PreferenceCategory>\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/group_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"groupName\"\n        app:title=\"@string/group_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <moe.matsuri.nb4a.ui.SimpleMenuPreference\n        app:defaultValue=\"0\"\n        app:entries=\"@array/group_types\"\n        app:entryValues=\"@array/int_array_2\"\n        app:icon=\"@drawable/ic_baseline_layers_24\"\n        app:key=\"groupType\"\n        app:title=\"@string/group_type\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <moe.matsuri.nb4a.ui.SimpleMenuPreference\n        app:defaultValue=\"0\"\n        app:entries=\"@array/group_orders\"\n        app:entryValues=\"@array/int_array_3\"\n        app:icon=\"@drawable/ic_baseline_low_priority_24\"\n        app:key=\"groupOrder\"\n        app:title=\"@string/group_order\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <SwitchPreference\n        app:icon=\"@drawable/ic_baseline_manage_search_24\"\n        app:key=\"groupIsSelector\"\n        app:title=\"@string/use_selector\" />\n\n    <io.nekohasekai.sagernet.widget.OutboundPreference\n        app:icon=\"@drawable/ic_hardware_router\"\n        app:key=\"groupFrontProxy\"\n        app:title=\"@string/front_proxy\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <io.nekohasekai.sagernet.widget.OutboundPreference\n        app:icon=\"@drawable/baseline_public_24\"\n        app:key=\"groupLandingProxy\"\n        app:title=\"@string/landing_proxy\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory\n        app:key=\"groupSubscription\"\n        app:title=\"@string/subscription_settings\">\n\n        <io.nekohasekai.sagernet.widget.LinkOrContentPreference\n            app:icon=\"@drawable/ic_baseline_link_24\"\n            app:key=\"subscriptionLink\"\n            app:title=\"@string/group_subscription_link\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_manage_search_24\"\n            app:key=\"subscriptionForceResolve\"\n            app:summary=\"@string/force_resolve_sum\"\n            app:title=\"@string/force_resolve\" />\n\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_import_contacts_24\"\n            app:key=\"subscriptionDeduplication\"\n            app:summary=\"@string/deduplication_sum\"\n            app:title=\"@string/deduplication\" />\n\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"subscriptionUpdate\"\n        app:title=\"@string/update_settings\">\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_security_24\"\n            app:key=\"subscriptionUpdateWhenConnectedOnly\"\n            app:summary=\"@string/update_when_connected_only_sum\"\n            app:title=\"@string/update_when_connected_only\" />\n        <io.nekohasekai.sagernet.widget.UserAgentPreference\n            app:icon=\"@drawable/ic_baseline_grid_3x3_24\"\n            app:key=\"subscriptionUserAgent\"\n            app:title=\"@string/subscription_user_agent\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_flip_camera_android_24\"\n            app:key=\"subscriptionAutoUpdate\"\n            app:title=\"@string/auto_update\" />\n        <EditTextPreference\n            app:defaultValue=\"1440\"\n            app:icon=\"@drawable/ic_baseline_timelapse_24\"\n            app:key=\"subscriptionAutoUpdateDelay\"\n            app:title=\"@string/auto_update_delay\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/hysteria_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <moe.matsuri.nb4a.ui.SimpleMenuPreference\n        app:defaultValue=\"2\"\n        app:entries=\"@array/hysteria_version\"\n        app:entryValues=\"@array/hysteria_version\"\n        app:icon=\"@drawable/ic_baseline_update_24\"\n        app:key=\"protocolVersion\"\n        app:title=\"@string/protocol_version\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPorts\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_baseline_texture_24\"\n            app:key=\"serverObfs\"\n            app:title=\"@string/hysteria_obfs\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/hysteria_auth_type\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"serverAuthType\"\n            app:title=\"@string/hysteria_auth_type\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/hysteria_auth_payload\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"https\"\n            app:entries=\"@array/hysteria_protocol\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"serverProtocol\"\n            app:title=\"@string/protocol\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"serverSNI\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"serverALPN\"\n            app:title=\"@string/alpn\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"serverCertificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"serverAllowInsecure\"\n            app:summary=\"@string/allow_insecure_sum\"\n            app:title=\"@string/allow_insecure\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_file_file_upload\"\n            app:key=\"serverUploadSpeed\"\n            app:title=\"@string/hysteria_upload_mbps\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_download_24\"\n            app:key=\"serverDownloadSpeed\"\n            app:title=\"@string/hysteria_download_mbps\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_texture_24\"\n            app:key=\"serverStreamReceiveWindow\"\n            app:title=\"@string/hysteria_stream_receive_window\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_transform_24\"\n            app:key=\"serverConnectionReceiveWindow\"\n            app:title=\"@string/hysteria_connection_receive_window\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_multiple_stop_24\"\n            app:key=\"serverDisableMtuDiscovery\"\n            app:title=\"@string/hysteria_disable_mtu_discovery\" />\n        <EditTextPreference\n            app:key=\"hopInterval\"\n            app:title=\"@string/hop_interval\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen> \n"
  },
  {
    "path": "app/src/main/res/xml/mieru_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"TCP\"\n            app:entries=\"@array/mieru_protocol\"\n            app:entryValues=\"@array/mieru_protocol\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"serverProtocol\"\n            app:title=\"@string/protocol\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"serverUsername\"\n            app:title=\"@string/username\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password\" />\n        <EditTextPreference\n            app:icon=\"@drawable/baseline_public_24\"\n            app:key=\"serverMTU\"\n            app:title=\"@string/mtu\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/naive_preferences.xml",
    "content": "<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"serverUsername\"\n            app:title=\"@string/username_opt\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password_opt\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            android:layout_height=\"match_parent\"\n            app:defaultValue=\"https\"\n            app:entries=\"@array/naive_proto_entry\"\n            app:entryValues=\"@array/naive_proto_value\"\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"serverProtocol\"\n            app:title=\"@string/protocol\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_texture_24\"\n            app:key=\"serverHeaders\"\n            app:title=\"@string/extra_headers\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"serverSNI\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"serverCertificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogMessage=\"@string/naive_insecure_concurrency_summary\"\n            app:icon=\"@drawable/ic_baseline_warning_24\"\n            app:key=\"serverInsecureConcurrency\"\n            app:title=\"@string/naive_insecure_concurrency\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"sing-box server\">\n        <SwitchPreference\n            app:key=\"sUoT\"\n            app:title=\"UDP over TCP\" />\n    </PreferenceCategory>\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/name_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/neko_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\"/>\n</network-security-config>\n"
  },
  {
    "path": "app/src/main/res/xml/route_preferences.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"routeName\"\n        app:title=\"@string/route_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n    <moe.matsuri.nb4a.ui.EditConfigPreference\n        app:icon=\"@drawable/ic_baseline_layers_24\"\n        app:key=\"serverConfig\"\n        app:title=\"@string/custom_config\"\n        app:useSimpleSummaryProvider=\"true\" />\n    <PreferenceCategory app:title=\"@string/cag_route\">\n        <io.nekohasekai.sagernet.widget.AppListPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"routePackages\"\n            app:title=\"@string/apps\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_domain_24\"\n            app:key=\"routeDomain\"\n            app:title=\"domain\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_add_road_24\"\n            app:key=\"routeIP\"\n            app:title=\"dst ip\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"routePort\"\n            app:title=\"dst port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_local_bar_24\"\n            app:key=\"routeSource\"\n            app:title=\"src ip\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_home_24\"\n            app:key=\"routeSourcePort\"\n            app:title=\"src port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/route_protocol_entry\"\n            app:entryValues=\"@array/route_protocol_value\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"routeNetwork\"\n            app:title=\"network\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"routeProtocol\"\n            app:title=\"protocol\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <io.nekohasekai.sagernet.widget.OutboundPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"routeOutbound\"\n            app:title=\"outbound\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/shadowsocks_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/ss_enc_method_value\"\n            app:entryValues=\"@array/ss_enc_method_value\"\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"method\"\n            app:title=\"@string/enc_method\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"password\"\n            app:title=\"@string/password\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"@string/plugin\">\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"\"\n            app:entries=\"@array/box_shadowsocks_plugins\"\n            app:entryValues=\"@array/box_shadowsocks_plugins\"\n            app:icon=\"@drawable/baseline_construction_24\"\n            app:key=\"pluginName\"\n            app:title=\"@string/plugin\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_settings\"\n            app:key=\"pluginConfig\"\n            app:title=\"@string/plugin_configure\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"sing-box server\">\n        <SwitchPreference\n            app:key=\"sUoT\"\n            app:title=\"UDP over TCP\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/shadowtls_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/shadowtls_version_value\"\n            app:entryValues=\"@array/shadowtls_version_value\"\n            app:icon=\"@drawable/ic_baseline_update_24\"\n            app:key=\"version\"\n            app:title=\"@string/protocol_version\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"password\"\n            app:title=\"@string/password\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverSecurityCategory\"\n        app:title=\"@string/security_settings\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"sni\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"alpn\"\n            app:title=\"@string/alpn\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"certificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"allowInsecure\"\n            app:summary=\"@string/allow_insecure_sum\"\n            app:title=\"@string/allow_insecure\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"\"\n            app:entries=\"@array/utls_fingerprint_entry\"\n            app:entryValues=\"@array/utls_fingerprint_entry\"\n            app:icon=\"@drawable/ic_baseline_fingerprint_24\"\n            app:key=\"utlsFingerprint\"\n            app:title=\"@string/utls_fingerprint\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n</PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/shortcuts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shortcuts xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <shortcut\n        android:icon=\"@drawable/ic_qu_shadowsocks_launcher\"\n        android:shortcutId=\"toggle\"\n        android:shortcutLongLabel=\"@string/quick_toggle\"\n        android:shortcutShortLabel=\"@string/quick_toggle\">\n        <intent\n            android:action=\"android.intent.action.MAIN\"\n            android:targetClass=\"io.nekohasekai.sagernet.QuickToggleShortcut\"\n            android:targetPackage=\"moe.nb4a\" />\n    </shortcut>\n    <shortcut\n        android:icon=\"@drawable/ic_qu_shadowsocks_launcher\"\n        android:shortcutId=\"enable\"\n        android:shortcutLongLabel=\"@string/quick_enable\"\n        android:shortcutShortLabel=\"@string/quick_enable\">\n        <intent\n            android:action=\"android.intent.action.MAIN\"\n            android:targetClass=\"io.nekohasekai.sagernet.ui.QuickEnableShortcut\"\n            android:targetPackage=\"moe.nb4a\" />\n    </shortcut>\n    <shortcut\n        android:icon=\"@drawable/ic_qu_shadowsocks_launcher\"\n        android:shortcutId=\"disable\"\n        android:shortcutLongLabel=\"@string/quick_disable\"\n        android:shortcutShortLabel=\"@string/quick_disable\">\n        <intent\n            android:action=\"android.intent.action.MAIN\"\n            android:targetClass=\"io.nekohasekai.sagernet.ui.QuickDisableShortcut\"\n            android:targetPackage=\"moe.nb4a\" />\n    </shortcut>\n    <shortcut\n        android:icon=\"@drawable/ic_qu_camera_launcher\"\n        android:shortcutId=\"scan\"\n        android:shortcutLongLabel=\"@string/add_profile_methods_scan_qr_code\"\n        android:shortcutShortLabel=\"@string/add_profile_methods_scan_qr_code\">\n        <intent\n            android:action=\"android.intent.action.MAIN\"\n            android:targetClass=\"io.nekohasekai.sagernet.ui.ScannerActivity\"\n            android:targetPackage=\"moe.nb4a\" />\n    </shortcut>\n</shortcuts>\n"
  },
  {
    "path": "app/src/main/res/xml/socks_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"2\"\n            app:entries=\"@array/socks_versions\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_nfc_24\"\n            app:key=\"serverProtocol\"\n            app:title=\"@string/app_version\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"serverUsername\"\n            app:title=\"@string/username_opt\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password_opt\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory app:title=\"sing-box server\">\n        <SwitchPreference\n            app:key=\"sUoT\"\n            app:title=\"UDP over TCP\" />\n    </PreferenceCategory>\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/ssh_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"serverUsername\"\n            app:title=\"@string/username\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/ssh_auth_type\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"serverAuthType\"\n            app:title=\"@string/hysteria_auth_type\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password\" />\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"serverPrivateKey\"\n            app:title=\"@string/ssh_private_key\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword1\"\n            app:title=\"@string/ssh_private_key_passphrase\" />\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"serverCertificates\"\n            app:title=\"@string/ssh_public_key\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/standard_v2ray_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"username\"\n            app:title=\"@string/username_opt\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"password\"\n            app:title=\"@string/password_opt\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"uuid\"\n            app:title=\"@string/uuid\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_360\"\n            app:key=\"alterId\"\n            app:title=\"@string/alter_id\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/vmess_encryption_value\"\n            app:entryValues=\"@array/vmess_encryption_value\"\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"encryption\"\n            app:title=\"@string/encryption\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/packet_encoding_entry\"\n            app:entryValues=\"@array/int_array_3\"\n            app:icon=\"@drawable/baseline_widgets_24\"\n            app:key=\"packetEncoding\"\n            app:title=\"@string/packet_encoding\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/networks_value\"\n            app:entryValues=\"@array/networks_value\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"type\"\n            app:title=\"@string/network\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_airplanemode_active_24\"\n            app:key=\"host\"\n            app:title=\"@string/http_host\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_format_align_left_24\"\n            app:key=\"path\"\n            app:title=\"@string/http_path\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/transport_layer_encryption_value\"\n            app:entryValues=\"@array/transport_layer_encryption_value\"\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"security\"\n            app:title=\"@string/security\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverWsCategory\"\n        app:title=\"@string/cag_ws\">\n        <EditTextPreference\n            app:defaultValue=\"0\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"wsMaxEarlyData\"\n            app:title=\"@string/ws_max_early_data\"\n            app:useSimpleSummaryProvider=\"true\" />\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_stream_24\"\n            app:key=\"earlyDataHeaderName\"\n            app:title=\"@string/early_data_header_name\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverSecurityCategory\"\n        app:title=\"@string/security_settings\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"sni\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"alpn\"\n            app:title=\"@string/alpn\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"certificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"allowInsecure\"\n            app:summary=\"@string/allow_insecure_sum\"\n            app:title=\"@string/allow_insecure\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverTlsCamouflageCategory\"\n        app:title=\"@string/tls_camouflage_settings\">\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"\"\n            app:entries=\"@array/utls_fingerprint_entry\"\n            app:entryValues=\"@array/utls_fingerprint_entry\"\n            app:icon=\"@drawable/ic_baseline_fingerprint_24\"\n            app:key=\"utlsFingerprint\"\n            app:title=\"@string/utls_fingerprint\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"realityPubKey\"\n            app:title=\"Reality Public Key\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_texture_24\"\n            app:key=\"realityShortId\"\n            app:title=\"Reality ShortId\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverMuxCategory\"\n        app:title=\"@string/mux_preference\">\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"enableMux\"\n            app:summary=\"@string/mux_sum\"\n            app:title=\"@string/enable_mux\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"0\"\n            app:entries=\"@array/mux_type\"\n            app:entryValues=\"@array/int_array_3\"\n            app:key=\"muxType\"\n            app:title=\"@string/mux_type\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:defaultValue=\"1\"\n            app:icon=\"@drawable/ic_baseline_low_priority_24\"\n            app:key=\"muxConcurrency\"\n            app:title=\"@string/mux_concurrency\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/baseline_developer_board_24\"\n            app:key=\"muxPadding\"\n            app:title=\"@string/padding\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverECHCategory\"\n        app:title=\"ECH\">\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_security_24\"\n            app:key=\"enableECH\"\n            app:title=\"@string/enable\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_nfc_24\"\n            app:key=\"echConfig\"\n            app:title=\"@string/ech_config\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/trojan_go_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"serverSNI\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"serverAllowInsecure\"\n            app:summary=\"@string/allow_insecure_sum\"\n            app:title=\"@string/allow_insecure\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"none\"\n            app:entries=\"@array/trojan_go_networks_entry\"\n            app:entryValues=\"@array/trojan_go_networks_value\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"serverNetwork\"\n            app:title=\"@string/network\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:entries=\"@array/trojan_go_security_entry\"\n            app:entryValues=\"@array/trojan_go_security_value\"\n            app:icon=\"@drawable/ic_baseline_layers_24\"\n            app:key=\"serverEncryption\"\n            app:title=\"@string/encryption\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverWsCategory\"\n        app:title=\"@string/cag_ws\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_airplanemode_active_24\"\n            app:key=\"serverHost\"\n            app:title=\"@string/ws_host\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_format_align_left_24\"\n            app:key=\"serverPath\"\n            app:title=\"@string/ws_path\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n    <PreferenceCategory\n        app:key=\"serverSsCategory\"\n        app:title=\"@string/ss_cat\">\n\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"AES-128-GCM\"\n            app:entries=\"@array/trojan_go_methods\"\n            app:entryValues=\"@array/trojan_go_methods\"\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"serverMethod\"\n            app:title=\"@string/enc_method\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword1\"\n            app:title=\"@string/password\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/tuic_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"profileName\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_person_24\"\n            app:key=\"serverUsername\"\n            app:title=\"@string/uuid\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"serverPassword\"\n            app:title=\"@string/password\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_legend_toggle_24\"\n            app:key=\"serverALPN\"\n            app:title=\"@string/alpn\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"serverCertificates\"\n            app:title=\"@string/certificates\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"https\"\n            app:entries=\"@array/tuic_udp_relay_mode_value\"\n            app:entryValues=\"@array/tuic_udp_relay_mode_value\"\n            app:icon=\"@drawable/ic_baseline_add_road_24\"\n            app:key=\"serverUDPRelayMode\"\n            app:title=\"@string/tuic_udp_relay_mode\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <moe.matsuri.nb4a.ui.SimpleMenuPreference\n            app:defaultValue=\"https\"\n            app:entries=\"@array/tuic_congestion_controller_value\"\n            app:entryValues=\"@array/tuic_congestion_controller_value\"\n            app:icon=\"@drawable/ic_baseline_compare_arrows_24\"\n            app:key=\"serverCongestionController\"\n            app:title=\"@string/tuic_congestion_controller\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_baseline_fingerprint_24\"\n            app:key=\"serverDisableSNI\"\n            app:title=\"@string/tuic_disable_sni\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"serverSNI\"\n            app:title=\"@string/sni\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <SwitchPreference\n            app:icon=\"@drawable/baseline_flight_takeoff_24\"\n            app:key=\"serverReduceRTT\"\n            app:title=\"@string/tuic_reduce_rtt\" />\n        <SwitchPreference\n            app:icon=\"@drawable/ic_notification_enhanced_encryption\"\n            app:key=\"serverAllowInsecure\"\n            app:title=\"@string/allow_insecure\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/wireguard_preferences.xml",
    "content": "<PreferenceScreen xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <EditTextPreference\n        app:icon=\"@drawable/ic_social_emoji_symbols\"\n        app:key=\"name\"\n        app:title=\"@string/profile_name\"\n        app:useSimpleSummaryProvider=\"true\" />\n\n    <PreferenceCategory app:title=\"@string/proxy_cat\">\n        <EditTextPreference\n            app:icon=\"@drawable/ic_hardware_router\"\n            app:key=\"serverAddress\"\n            app:title=\"@string/server_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_maps_directions_boat\"\n            app:key=\"serverPort\"\n            app:title=\"@string/server_port\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_domain_24\"\n            app:key=\"localAddress\"\n            app:title=\"@string/wireguard_local_address\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_vpn_key_24\"\n            app:key=\"privateKey\"\n            app:title=\"@string/ssh_private_key\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_action_copyright\"\n            app:key=\"peerPublicKey\"\n            app:title=\"@string/wireguard_public_key\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:dialogLayout=\"@layout/layout_password_dialog\"\n            app:icon=\"@drawable/ic_settings_password\"\n            app:key=\"peerPreSharedKey\"\n            app:title=\"@string/wireguard_psk\" />\n        <EditTextPreference\n            app:defaultValue=\"1420\"\n            app:icon=\"@drawable/baseline_public_24\"\n            app:key=\"mtu\"\n            app:title=\"@string/mtu\"\n            app:useSimpleSummaryProvider=\"true\" />\n        <EditTextPreference\n            app:icon=\"@drawable/ic_baseline_fingerprint_24\"\n            app:key=\"reserved\"\n            app:title=\"Reserved\"\n            app:useSimpleSummaryProvider=\"true\" />\n    </PreferenceCategory>\n\n</PreferenceScreen>"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nallprojects {\n    apply(from = \"${rootProject.projectDir}/repositories.gradle.kts\")\n}\n\ntasks.register<Delete>(\"clean\") {\n    delete(rootProject.buildDir)\n}\n\nplugins {\n    id(\"com.google.devtools.ksp\") version \"2.0.21-1.0.27\" apply false\n}\n"
  },
  {
    "path": "buildScript/copyLocal.sh",
    "content": "#!/bin/sh\n# cp local.properties external/editorkit/\n# cp local.properties external/termux-view/\n"
  },
  {
    "path": "buildScript/fdroid/prebuild.sh",
    "content": "#!/bin/bash\n\nbuildScript/init/action/gradle.sh\n\n# Build libcore\nbuildScript/lib/core.sh\n"
  },
  {
    "path": "buildScript/init/action/gradle.sh",
    "content": "#!/bin/bash\n# Prepare script for Actions Gradle build\nset -e\n\n#### Download assets\nbash buildScript/lib/assets.sh\n\nexit\n\n#### Download \"external\" from Internet\n#rm -rf external\n#mkdir -p external\n#cd external\n#\n#echo \"Downloading preferencex-android\"\n#wget -q -O tmp.zip https://github.com/SagerNet/preferencex-android/archive/8bdb0c6ae44f378b073c6a1c850d03d729b70ff8.zip\n#unzip tmp.zip > /dev/null 2>&1\n#mv preferencex-android-* preferencex\n#\n#rm tmp.zip\n"
  },
  {
    "path": "buildScript/init/env.sh",
    "content": "#!/bin/bash\n\nsource buildScript/init/env_ndk.sh\n\nif [[ \"$OSTYPE\" =~ ^darwin ]]; then\n  export SRC_ROOT=$PWD\nelse\n  export SRC_ROOT=$(realpath .)\nfi\n\nDEPS=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin\n\nexport ANDROID_ARM_CC=$DEPS/armv7a-linux-androideabi21-clang\nexport ANDROID_ARM_CXX=$DEPS/armv7a-linux-androideabi21-clang++\nexport ANDROID_ARM_CC_21=$DEPS/armv7a-linux-androideabi21-clang\nexport ANDROID_ARM_CXX_21=$DEPS/armv7a-linux-androideabi21-clang++\nexport ANDROID_ARM_STRIP=$DEPS/arm-linux-androideabi-strip\n\nexport ANDROID_ARM64_CC=$DEPS/aarch64-linux-android21-clang\nexport ANDROID_ARM64_CXX=$DEPS/aarch64-linux-android21-clang++\nexport ANDROID_ARM64_STRIP=$DEPS/aarch64-linux-android-strip\n\nexport ANDROID_X86_CC=$DEPS/i686-linux-android21-clang\nexport ANDROID_X86_CXX=$DEPS/i686-linux-android21-clang++\nexport ANDROID_X86_CC_21=$DEPS/i686-linux-android21-clang\nexport ANDROID_X86_CXX_21=$DEPS/i686-linux-android21-clang++\nexport ANDROID_X86_STRIP=$DEPS/i686-linux-android-strip\n\nexport ANDROID_X86_64_CC=$DEPS/x86_64-linux-android21-clang\nexport ANDROID_X86_64_CXX=$DEPS/x86_64-linux-android21-clang++\nexport ANDROID_X86_64_STRIP=$DEPS/x86_64-linux-android-strip\n"
  },
  {
    "path": "buildScript/init/env_ndk.sh",
    "content": "#!/bin/bash\n\nif [ -z \"$ANDROID_HOME\" ]; then\n  if [ -d \"$HOME/Android/Sdk\" ]; then\n    export ANDROID_HOME=\"$HOME/Android/Sdk\"\n  elif [ -d \"$HOME/.local/lib/android/sdk\" ]; then\n    export ANDROID_HOME=\"$HOME/.local/lib/android/sdk\"\n  elif [ -d \"$HOME/Library/Android/sdk\" ]; then\n    export ANDROID_HOME=\"$HOME/Library/Android/sdk\"\n  fi\nfi\n\n_NDK=\"$ANDROID_HOME/ndk/25.0.8775105\"\n[ -f \"$_NDK/source.properties\" ] || _NDK=\"$ANDROID_NDK_HOME\"\n[ -f \"$_NDK/source.properties\" ] || _NDK=\"$NDK\"\n[ -f \"$_NDK/source.properties\" ] || _NDK=\"$ANDROID_HOME/ndk-bundle\"\n\nif [ ! -f \"$_NDK/source.properties\" ]; then\n  echo \"Error: NDK not found.\"\n  exit 1\nfi\n\nexport ANDROID_NDK_HOME=$_NDK\nexport NDK=$_NDK\n"
  },
  {
    "path": "buildScript/lib/assets.sh",
    "content": "#!/bin/bash\n\nset -e\n\nDIR=app/src/main/assets/sing-box\nrm -rf $DIR\nmkdir -p $DIR\ncd $DIR\n\nget_latest_release() {\n  curl --silent \"https://api.github.com/repos/$1/releases/latest\" | # Get latest release from GitHub api\n    grep '\"tag_name\":' |                                            # Get tag line\n    sed -E 's/.*\"([^\"]+)\".*/\\1/'                                    # Pluck JSON value\n}\n\n####\nVERSION_GEOIP=`get_latest_release \"SagerNet/sing-geoip\"`\necho VERSION_GEOIP=$VERSION_GEOIP\necho -n $VERSION_GEOIP > geoip.version.txt\ncurl -fLSsO https://github.com/SagerNet/sing-geoip/releases/download/$VERSION_GEOIP/geoip.db\nxz -9 geoip.db\n\n####\nVERSION_GEOSITE=`get_latest_release \"SagerNet/sing-geosite\"`\necho VERSION_GEOSITE=$VERSION_GEOSITE\necho -n $VERSION_GEOSITE > geosite.version.txt\ncurl -fLSsO https://github.com/SagerNet/sing-geosite/releases/download/$VERSION_GEOSITE/geosite.db\nxz -9 geosite.db\n"
  },
  {
    "path": "buildScript/lib/core/build.sh",
    "content": "#!/usr/bin/env bash\n\nsource \"buildScript/init/env.sh\"\nexport CGO_ENABLED=1\nexport GO386=softfloat\n\ncd libcore\nrel=1 ./build.sh || exit 1\n"
  },
  {
    "path": "buildScript/lib/core/get_source.sh",
    "content": "#!/bin/bash\nset -e\n\nsource \"buildScript/init/env.sh\"\nENV_NB4A=1\nsource \"buildScript/lib/core/get_source_env.sh\"\npushd ..\n\n####\n\nif [ ! -d \"sing-box\" ]; then\n  git clone --no-checkout https://github.com/MatsuriDayo/sing-box.git\nfi\npushd sing-box\ngit checkout \"$COMMIT_SING_BOX\"\npopd\n\n####\n\nif [ ! -d \"libneko\" ]; then\n  git clone --no-checkout https://github.com/MatsuriDayo/libneko.git\nfi\npushd libneko\ngit checkout \"$COMMIT_LIBNEKO\"\npopd\n\n####\n\npopd\n"
  },
  {
    "path": "buildScript/lib/core/get_source_env.sh",
    "content": "export COMMIT_SING_BOX=\"aed32ee3066cdbc7d471e3e0415c5134088962df\"\nexport COMMIT_LIBNEKO=\"1c47a3af71990a7b2192e03292b4d246c308ef0b\"\n"
  },
  {
    "path": "buildScript/lib/core/init.sh",
    "content": "#!/usr/bin/env bash\n\nsource \"buildScript/init/env.sh\"\n\n# fetch soucre\nbash buildScript/lib/core/get_source.sh\n\n[ -f libcore/go.mod ] || exit 1\ncd libcore\n\n./init.sh || exit 1\n"
  },
  {
    "path": "buildScript/lib/core.sh",
    "content": "#!/bin/bash\n\nbuildScript/lib/core/init.sh\nbuildScript/lib/core/build.sh"
  },
  {
    "path": "buildSrc/build.gradle.kts",
    "content": "plugins {\n    `java-gradle-plugin`\n    `kotlin-dsl`\n}\n\napply(from = \"../repositories.gradle.kts\")\n\ndependencies {\n    // Gradle Plugins\n    implementation(\"com.android.tools.build:gradle:8.8.1\")\n    implementation(\"org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21\")\n}\n"
  },
  {
    "path": "buildSrc/src/main/kotlin/Helpers.kt",
    "content": "import com.android.build.api.dsl.ApplicationExtension\nimport com.android.build.gradle.AbstractAppExtension\nimport com.android.build.gradle.internal.api.BaseVariantOutputImpl\nimport org.gradle.api.JavaVersion\nimport org.gradle.api.Project\nimport org.gradle.api.plugins.ExtensionAware\nimport org.gradle.kotlin.dsl.getByName\nimport org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions\nimport java.util.Base64\nimport java.util.Properties\nimport kotlin.system.exitProcess\n\nprivate val Project.android get() = extensions.getByName<ApplicationExtension>(\"android\")\n\nprivate lateinit var metadata: Properties\nprivate lateinit var localProperties: Properties\n\nfun Project.requireMetadata(): Properties {\n    if (!::metadata.isInitialized) {\n        metadata = Properties().apply {\n            load(rootProject.file(\"nb4a.properties\").inputStream())\n        }\n    }\n    return metadata\n}\n\nfun Project.requireLocalProperties(): Properties {\n    if (!::localProperties.isInitialized) {\n        localProperties = Properties()\n\n        val base64 = System.getenv(\"LOCAL_PROPERTIES\")\n        if (!base64.isNullOrBlank()) {\n\n            localProperties.load(Base64.getDecoder().decode(base64).inputStream())\n        } else if (project.rootProject.file(\"local.properties\").exists()) {\n            localProperties.load(rootProject.file(\"local.properties\").inputStream())\n        }\n    }\n    return localProperties\n}\n\nfun Project.setupCommon() {\n    android.apply {\n        buildToolsVersion = \"35.0.1\"\n        compileSdk = 35\n        defaultConfig {\n            minSdk = 21\n            targetSdk = 35\n        }\n        buildTypes {\n            getByName(\"release\") {\n                isMinifyEnabled = true\n            }\n        }\n        compileOptions {\n            sourceCompatibility = JavaVersion.VERSION_1_8\n            targetCompatibility = JavaVersion.VERSION_1_8\n        }\n        (android as ExtensionAware).extensions.getByName<KotlinJvmOptions>(\"kotlinOptions\").apply {\n            jvmTarget = JavaVersion.VERSION_1_8.toString()\n        }\n        lint {\n            showAll = true\n            checkAllWarnings = true\n            checkReleaseBuilds = true\n            warningsAsErrors = true\n            textOutput = project.file(\"build/lint.txt\")\n            htmlOutput = project.file(\"build/lint.html\")\n        }\n        packaging {\n            resources.excludes.addAll(\n                listOf(\n                    \"**/*.kotlin_*\",\n                    \"/META-INF/*.version\",\n                    \"/META-INF/native/**\",\n                    \"/META-INF/native-image/**\",\n                    \"/META-INF/INDEX.LIST\",\n                    \"DebugProbesKt.bin\",\n                    \"com/**\",\n                    \"org/**\",\n                    \"**/*.java\",\n                    \"**/*.proto\",\n                    \"okhttp3/**\"\n                )\n            )\n        }\n        (this as? AbstractAppExtension)?.apply {\n            buildTypes {\n                getByName(\"release\") {\n                    isShrinkResources = true\n                    if (System.getenv(\"nkmr_minify\") == \"0\") {\n                        isShrinkResources = false\n                        isMinifyEnabled = false\n                    }\n                }\n                getByName(\"debug\") {\n                    applicationIdSuffix = \"debug\"\n                    debuggable(true)\n                    jniDebuggable(true)\n                }\n            }\n            applicationVariants.forEach { variant ->\n                variant.outputs.forEach {\n                    it as BaseVariantOutputImpl\n                    it.outputFileName = it.outputFileName.replace(\n                        \"app\", \"${project.name}-\" + variant.versionName\n                    ).replace(\"-release\", \"\").replace(\"-oss\", \"\")\n                }\n            }\n        }\n    }\n}\n\nfun Project.setupAppCommon() {\n    setupCommon()\n\n    val lp = requireLocalProperties()\n    val keystorePwd = lp.getProperty(\"KEYSTORE_PASS\") ?: System.getenv(\"KEYSTORE_PASS\")\n    val alias = lp.getProperty(\"ALIAS_NAME\") ?: System.getenv(\"ALIAS_NAME\")\n    val pwd = lp.getProperty(\"ALIAS_PASS\") ?: System.getenv(\"ALIAS_PASS\")\n\n    android.apply {\n        if (keystorePwd != null) {\n            signingConfigs {\n                create(\"release\") {\n                    storeFile = rootProject.file(\"release.keystore\")\n                    storePassword = keystorePwd\n                    keyAlias = alias\n                    keyPassword = pwd\n                }\n            }\n        }\n        buildTypes {\n            val key = signingConfigs.findByName(\"release\")\n            if (key != null) {\n                getByName(\"release\").signingConfig = key\n                getByName(\"debug\").signingConfig = key\n            }\n        }\n    }\n}\n\nfun Project.setupApp() {\n    val pkgName = requireMetadata().getProperty(\"PACKAGE_NAME\")\n    val verName = requireMetadata().getProperty(\"VERSION_NAME\")\n    val verCode = (requireMetadata().getProperty(\"VERSION_CODE\").toInt()) * 5\n    android.apply {\n        defaultConfig {\n            applicationId = pkgName\n            versionCode = verCode\n            versionName = verName\n            buildConfigField(\"String\", \"PRE_VERSION_NAME\", \"\\\"\\\"\")\n        }\n    }\n    setupAppCommon()\n\n    android.apply {\n        this as AbstractAppExtension\n\n        buildTypes {\n            getByName(\"release\") {\n                proguardFiles(\n                    getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                    file(\"proguard-rules.pro\")\n                )\n            }\n        }\n\n        splits.abi {\n            reset()\n            isEnable = true\n            isUniversalApk = false\n            include(\"armeabi-v7a\")\n            include(\"arm64-v8a\")\n            include(\"x86\")\n            include(\"x86_64\")\n        }\n\n        flavorDimensions += \"vendor\"\n        productFlavors {\n            create(\"oss\")\n            create(\"fdroid\")\n            create(\"play\")\n            create(\"preview\") {\n                buildConfigField(\n                    \"String\",\n                    \"PRE_VERSION_NAME\",\n                    \"\\\"${requireMetadata().getProperty(\"PRE_VERSION_NAME\")}\\\"\"\n                )\n            }\n        }\n\n        applicationVariants.all {\n            outputs.all {\n                this as BaseVariantOutputImpl\n                val isPreview = outputFileName.contains(\"-preview\")\n                outputFileName = if (isPreview) {\n                    outputFileName.replace(\n                        project.name,\n                        \"NekoBox-\" + requireMetadata().getProperty(\"PRE_VERSION_NAME\")\n                    ).replace(\"-preview\", \"\")\n                } else {\n                    outputFileName.replace(project.name, \"NekoBox-$versionName\")\n                        .replace(\"-release\", \"\")\n                        .replace(\"-oss\", \"\")\n                }\n            }\n        }\n\n        for (abi in listOf(\"Arm64\", \"Arm\", \"X64\", \"X86\")) {\n            tasks.create(\"assemble\" + abi + \"FdroidRelease\") {\n                dependsOn(\"assembleFdroidRelease\")\n            }\n        }\n\n        sourceSets.getByName(\"main\").apply {\n            jniLibs.srcDir(\"executableSo\")\n        }\n    }\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10.2-bin.zip\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -Dfile.encoding=UTF-8\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\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Gradle parallel build\norg.gradle.parallel=true\nandroid.nonTransitiveRClass=false\nandroid.nonFinalResIds=false\n"
  },
  {
    "path": "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\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/master/subprojects/plugins/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\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=${0##*/}\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# 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=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\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    which java >/dev/null 2>&1 || 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.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\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        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# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\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": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "libcore/.gitignore",
    "content": "binary*.go\n*.[a|j]ar\n.idea\n.vscode\n/debug.go\nbuild\n.build\nenv_*.sh\n"
  },
  {
    "path": "libcore/LICENSE",
    "content": "Copyright (C) 2021 by nekohasekai <contact-sagernet@sekai.icu>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>."
  },
  {
    "path": "libcore/assets.go",
    "content": "package libcore\n\nconst (\n\tgeoipDat       = \"geoip.db\"\n\tgeositeDat     = \"geosite.db\"\n\tgeoipVersion   = \"geoip.version.txt\"\n\tgeositeVersion = \"geosite.version.txt\"\n\n\tyacdDstFolder = \"yacd\"\n\tyacdVersion   = \"yacd.version.txt\"\n)\n\nvar apkAssetPrefixSingBox = \"sing-box/\"\nvar internalAssetsPath string\nvar externalAssetsPath string\n"
  },
  {
    "path": "libcore/assets_android.go",
    "content": "//go:build android\n\npackage libcore\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strconv\"\n\n\t\"golang.org/x/mobile/asset\"\n)\n\nfunc extractAssets() {\n\tuseOfficialAssets := intfNB4A.UseOfficialAssets()\n\n\textract := func(name string) {\n\t\terr := extractAssetName(name, useOfficialAssets)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Extract\", geoipDat, \"failed:\", err)\n\t\t}\n\t}\n\n\textract(geoipDat)\n\textract(geositeDat)\n\textract(yacdDstFolder)\n}\n\n// 这里解压的是 apk 里面的\nfunc extractAssetName(name string, useOfficialAssets bool) error {\n\t// 支持非官方源的，就是 replaceable，放 Android 目录\n\t// 不支持非官方源的，就放 file 目录\n\treplaceable := true\n\n\tvar version string\n\tvar apkPrefix string\n\tswitch name {\n\tcase geoipDat:\n\t\tversion = geoipVersion\n\t\tapkPrefix = apkAssetPrefixSingBox\n\tcase geositeDat:\n\t\tversion = geositeVersion\n\t\tapkPrefix = apkAssetPrefixSingBox\n\tcase yacdDstFolder:\n\t\tversion = yacdVersion\n\t\treplaceable = false\n\t}\n\n\tvar dir string\n\tif !replaceable {\n\t\tdir = internalAssetsPath\n\t} else {\n\t\tdir = externalAssetsPath\n\t}\n\tdstName := dir + name\n\n\tvar localVersion string\n\tvar assetVersion string\n\n\t// loadAssetVersion from APK\n\tloadAssetVersion := func() error {\n\t\tav, err := asset.Open(apkPrefix + version)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"open version in assets: %v\", err)\n\t\t}\n\t\tb, err := io.ReadAll(av)\n\t\tav.Close()\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"read internal version: %v\", err)\n\t\t}\n\t\tassetVersion = string(b)\n\t\treturn nil\n\t}\n\tif err := loadAssetVersion(); err != nil {\n\t\treturn err\n\t}\n\n\tvar doExtract bool\n\n\tif _, err := os.Stat(dstName); err != nil {\n\t\t// assetFileMissing\n\t\tdoExtract = true\n\t} else if useOfficialAssets || !replaceable {\n\t\t// 官方源升级\n\t\tb, err := os.ReadFile(dir + version)\n\t\tif err != nil {\n\t\t\t// versionFileMissing\n\t\t\tdoExtract = true\n\t\t\t_ = os.RemoveAll(version)\n\t\t} else {\n\t\t\tlocalVersion = string(b)\n\t\t\tif localVersion == \"Custom\" {\n\t\t\t\tdoExtract = false\n\t\t\t} else {\n\t\t\t\tav, err := strconv.ParseUint(assetVersion, 10, 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\tdoExtract = assetVersion != localVersion\n\t\t\t\t} else {\n\t\t\t\t\tlv, err := strconv.ParseUint(localVersion, 10, 64)\n\t\t\t\t\tdoExtract = err != nil || av > lv\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} else {\n\t\t//非官方源不升级\n\t}\n\n\tif !doExtract {\n\t\treturn nil\n\t}\n\n\textractXz := func(f asset.File) error {\n\t\ttmpXzName := dstName + \".xz\"\n\t\terr := extractAsset(f, tmpXzName)\n\t\tif err == nil {\n\t\t\terr = Unxz(tmpXzName, dstName)\n\t\t\tos.Remove(tmpXzName)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"extract xz: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\textracZip := func(f asset.File, outDir string) error {\n\t\ttmpZipName := dstName + \".zip\"\n\t\terr := extractAsset(f, tmpZipName)\n\t\tif err == nil {\n\t\t\terr = Unzip(tmpZipName, outDir)\n\t\t\tos.Remove(tmpZipName)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"extract zip: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\tif f, err := asset.Open(apkPrefix + name + \".xz\"); err == nil {\n\t\textractXz(f)\n\t} else if f, err := asset.Open(\"yacd.zip\"); err == nil {\n\t\tos.RemoveAll(dstName)\n\t\textracZip(f, internalAssetsPath)\n\t\tm, err := filepath.Glob(internalAssetsPath + \"/Yacd-*\")\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"glob Yacd: %v\", err)\n\t\t}\n\t\tif len(m) != 1 {\n\t\t\treturn fmt.Errorf(\"glob Yacd found %d result, expect 1\", len(m))\n\t\t}\n\t\terr = os.Rename(m[0], dstName)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"rename Yacd: %v\", err)\n\t\t}\n\n\t} // TODO normal file\n\n\to, err := os.Create(dir + version)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create version: %v\", err)\n\t}\n\t_, err = io.WriteString(o, assetVersion)\n\to.Close()\n\treturn err\n}\n\nfunc extractAsset(i asset.File, path string) error {\n\tdefer i.Close()\n\to, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer o.Close()\n\t_, err = io.Copy(o, i)\n\tif err == nil {\n\t\tlog.Println(\"Extract >>\", path)\n\t}\n\treturn err\n}\n"
  },
  {
    "path": "libcore/assets_other.go",
    "content": "//go:build !android\n\npackage libcore\n\nfunc extractAssets() {}\n"
  },
  {
    "path": "libcore/box.go",
    "content": "package libcore\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"libcore/device\"\n\t\"log\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/matsuridayo/libneko/protect_server\"\n\t\"github.com/matsuridayo/libneko/speedtest\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/boxapi\"\n\t\"github.com/sagernet/sing-box/experimental/libbox/platform\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\n\tbox \"github.com/sagernet/sing-box\"\n\t\"github.com/sagernet/sing-box/common/conntrack\"\n\t\"github.com/sagernet/sing-box/common/dialer\"\n\t\"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/service\"\n\t\"github.com/sagernet/sing/service/pause\"\n)\n\nfunc init() {\n\tdialer.DoNotSelectInterface = true\n}\n\nvar mainInstance *BoxInstance\n\nfunc VersionBox() string {\n\tversion := []string{\n\t\t\"sing-box: \" + constant.Version,\n\t\truntime.Version() + \"@\" + runtime.GOOS + \"/\" + runtime.GOARCH,\n\t}\n\n\tvar tags string\n\tdebugInfo, loaded := debug.ReadBuildInfo()\n\tif loaded {\n\t\tfor _, setting := range debugInfo.Settings {\n\t\t\tswitch setting.Key {\n\t\t\tcase \"-tags\":\n\t\t\t\ttags = setting.Value\n\t\t\t}\n\t\t}\n\t}\n\n\tif tags != \"\" {\n\t\tversion = append(version, tags)\n\t}\n\n\treturn strings.Join(version, \"\\n\")\n}\n\nfunc ResetAllConnections(system bool) {\n\tif system {\n\t\tconntrack.Close()\n\t\tlog.Println(\"Reset system connections done\")\n\t} else {\n\t\tlog.Println(\"TODO: Reset user connections\")\n\t}\n}\n\ntype BoxInstance struct {\n\taccess sync.Mutex\n\n\t*box.Box\n\tcancel context.CancelFunc\n\tstate  int\n\n\tv2api        *boxapi.SbV2rayServer\n\tselector     *group.Selector\n\tpauseManager pause.Manager\n}\n\nfunc NewSingBoxInstance(config string, localTransport LocalDNSTransport) (b *BoxInstance, err error) {\n\tdefer device.DeferPanicToError(\"NewSingBoxInstance\", func(err_ error) { err = err_ })\n\n\t// create box context\n\tctx, cancel := context.WithCancel(context.Background())\n\tctx = box.Context(ctx,\n\t\tnekoboxAndroidInboundRegistry(), nekoboxAndroidOutboundRegistry(), nekoboxAndroidEndpointRegistry(),\n\t\tnekoboxAndroidDNSTransportRegistry(localTransport), nekoboxAndroidServiceRegistry(),\n\t)\n\tctx = service.ContextWithDefaultRegistry(ctx)\n\tservice.MustRegister[platform.Interface](ctx, boxPlatformInterfaceInstance)\n\n\t// parse options\n\tvar options option.Options\n\terr = options.UnmarshalJSONContext(ctx, []byte(config))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"decode config: %v\", err)\n\t}\n\n\t// create box\n\tinstance, err := box.New(box.Options{\n\t\tOptions:           options,\n\t\tContext:           ctx,\n\t\tPlatformLogWriter: boxPlatformLogWriter,\n\t})\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"create service: %v\", err)\n\t}\n\n\tb = &BoxInstance{\n\t\tBox:          instance,\n\t\tcancel:       cancel,\n\t\tpauseManager: service.FromContext[pause.Manager](ctx),\n\t}\n\n\t// selector\n\tif proxy, ok := b.Outbound().Outbound(\"proxy\"); ok {\n\t\tif selector, ok := proxy.(*group.Selector); ok {\n\t\t\tb.selector = selector\n\t\t}\n\t}\n\n\treturn b, nil\n}\n\nfunc (b *BoxInstance) Start() (err error) {\n\tb.access.Lock()\n\tdefer b.access.Unlock()\n\n\tdefer device.DeferPanicToError(\"box.Start\", func(err_ error) { err = err_ })\n\n\tif b.state == 0 {\n\t\tb.state = 1\n\t\treturn b.Box.Start()\n\t}\n\treturn errors.New(\"already started\")\n}\n\nfunc (b *BoxInstance) Close() (err error) {\n\tb.access.Lock()\n\tdefer b.access.Unlock()\n\n\tdefer device.DeferPanicToError(\"box.Close\", func(err_ error) { err = err_ })\n\n\t// no double close\n\tif b.state == 2 {\n\t\treturn nil\n\t}\n\tb.state = 2\n\n\t// clear main instance\n\tif mainInstance == b {\n\t\tmainInstance = nil\n\t\tgoServeProtect(false)\n\t}\n\n\t// close box\n\tif b.cancel != nil {\n\t\tb.cancel()\n\t}\n\tif b.Box != nil {\n\t\tb.Box.Close()\n\t}\n\n\treturn nil\n}\n\nfunc (b *BoxInstance) Sleep() {\n\tif b.pauseManager != nil {\n\t\tb.pauseManager.DevicePause()\n\t}\n\t// _ = b.Box.Router().ResetNetwork()\n}\n\nfunc (b *BoxInstance) Wake() {\n\tif b.pauseManager != nil {\n\t\tb.pauseManager.DeviceWake()\n\t}\n}\n\nfunc (b *BoxInstance) SetAsMain() {\n\tmainInstance = b\n\tgoServeProtect(true)\n}\n\nfunc (b *BoxInstance) SetV2rayStats(outbounds string) {\n\tb.access.Lock()\n\tdefer b.access.Unlock()\n\tif b.v2api != nil {\n\t\tlog.Println(\"duplicate call of SetV2rayStats\")\n\t\treturn\n\t}\n\tb.v2api = boxapi.NewSbV2rayServer(option.V2RayStatsServiceOptions{\n\t\tEnabled:   true,\n\t\tOutbounds: strings.Split(outbounds, \"\\n\"),\n\t})\n\tb.Box.Router().AppendTracker(b.v2api.StatsService())\n}\n\nfunc (b *BoxInstance) QueryStats(tag, direct string) int64 {\n\tif b.v2api == nil {\n\t\treturn 0\n\t}\n\treturn b.v2api.QueryStats(fmt.Sprintf(\"outbound>>>%s>>>traffic>>>%s\", tag, direct))\n}\n\nfunc (b *BoxInstance) SelectOutbound(tag string) bool {\n\tif b.selector != nil {\n\t\treturn b.selector.SelectOutbound(tag)\n\t}\n\treturn false\n}\n\nfunc UrlTest(i *BoxInstance, link string, timeout int32) (latency int32, err error) {\n\tdefer device.DeferPanicToError(\"box.UrlTest\", func(err_ error) { err = err_ })\n\tvar connectionTracker adapter.ConnectionTracker\n\t// test i\n\tif i != nil {\n\t\tif i.v2api != nil {\n\t\t\tconnectionTracker = i.v2api.StatsService()\n\t\t}\n\t\treturn speedtest.UrlTest(boxapi.CreateProxyHttpClient(i.Box, connectionTracker), link, timeout, speedtest.UrlTestStandard_RTT)\n\t}\n\t// test direct\n\tif mainInstance == nil {\n\t\treturn speedtest.UrlTest(boxapi.CreateProxyHttpClient(nil, nil), link, timeout, speedtest.UrlTestStandard_RTT)\n\t}\n\t// test mainInstance\n\tif mainInstance.v2api != nil {\n\t\tconnectionTracker = mainInstance.v2api.StatsService()\n\t}\n\treturn speedtest.UrlTest(boxapi.CreateProxyHttpClient(mainInstance.Box, connectionTracker), link, timeout, speedtest.UrlTestStandard_RTT)\n}\n\nvar protectCloser io.Closer\n\nfunc goServeProtect(start bool) {\n\tif protectCloser != nil {\n\t\tprotectCloser.Close()\n\t\tprotectCloser = nil\n\t}\n\tif start {\n\t\tprotectCloser = protect_server.ServeProtect(\"protect_path\", false, 0, func(fd int) {\n\t\t\tintfBox.AutoDetectInterfaceControl(int32(fd))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "libcore/box_include.go",
    "content": "package libcore\n\nimport (\n\t\"context\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/adapter/endpoint\"\n\t\"github.com/sagernet/sing-box/adapter/inbound\"\n\t\"github.com/sagernet/sing-box/adapter/outbound\"\n\t\"github.com/sagernet/sing-box/adapter/service\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/dns/transport\"\n\t\"github.com/sagernet/sing-box/dns/transport/fakeip\"\n\t\"github.com/sagernet/sing-box/dns/transport/hosts\"\n\t\"github.com/sagernet/sing-box/dns/transport/local\"\n\t\"github.com/sagernet/sing-box/dns/transport/quic\"\n\t\"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing-box/protocol/anytls\"\n\t\"github.com/sagernet/sing-box/protocol/block\"\n\t\"github.com/sagernet/sing-box/protocol/direct\"\n\tprotocolDns \"github.com/sagernet/sing-box/protocol/dns\"\n\t\"github.com/sagernet/sing-box/protocol/group\"\n\t\"github.com/sagernet/sing-box/protocol/http\"\n\t\"github.com/sagernet/sing-box/protocol/hysteria\"\n\t\"github.com/sagernet/sing-box/protocol/hysteria2\"\n\t\"github.com/sagernet/sing-box/protocol/mixed\"\n\t\"github.com/sagernet/sing-box/protocol/redirect\"\n\t\"github.com/sagernet/sing-box/protocol/shadowsocks\"\n\t\"github.com/sagernet/sing-box/protocol/shadowtls\"\n\t\"github.com/sagernet/sing-box/protocol/socks\"\n\t\"github.com/sagernet/sing-box/protocol/ssh\"\n\t\"github.com/sagernet/sing-box/protocol/tor\"\n\t\"github.com/sagernet/sing-box/protocol/trojan\"\n\t\"github.com/sagernet/sing-box/protocol/tuic\"\n\t\"github.com/sagernet/sing-box/protocol/tun\"\n\t\"github.com/sagernet/sing-box/protocol/vless\"\n\t\"github.com/sagernet/sing-box/protocol/vmess\"\n\t\"github.com/sagernet/sing-box/protocol/wireguard\"\n\n\t_ \"github.com/sagernet/sing-box/experimental/clashapi\"\n\t_ \"github.com/sagernet/sing-box/transport/v2rayquic\"\n)\n\nfunc nekoboxAndroidInboundRegistry() *inbound.Registry {\n\tregistry := inbound.NewRegistry()\n\n\ttun.RegisterInbound(registry)\n\tredirect.RegisterRedirect(registry)\n\tredirect.RegisterTProxy(registry)\n\tdirect.RegisterInbound(registry)\n\n\tsocks.RegisterInbound(registry)\n\thttp.RegisterInbound(registry)\n\tmixed.RegisterInbound(registry)\n\n\treturn registry\n}\n\nfunc nekoboxAndroidOutboundRegistry() *outbound.Registry {\n\tregistry := outbound.NewRegistry()\n\n\tdirect.RegisterOutbound(registry)\n\n\tblock.RegisterOutbound(registry)\n\tprotocolDns.RegisterOutbound(registry)\n\n\tgroup.RegisterSelector(registry)\n\tgroup.RegisterURLTest(registry)\n\n\tsocks.RegisterOutbound(registry)\n\thttp.RegisterOutbound(registry)\n\tshadowsocks.RegisterOutbound(registry)\n\tvmess.RegisterOutbound(registry)\n\ttrojan.RegisterOutbound(registry)\n\ttor.RegisterOutbound(registry)\n\tssh.RegisterOutbound(registry)\n\tshadowtls.RegisterOutbound(registry)\n\tvless.RegisterOutbound(registry)\n\tanytls.RegisterOutbound(registry)\n\n\thysteria.RegisterOutbound(registry)\n\ttuic.RegisterOutbound(registry)\n\thysteria2.RegisterOutbound(registry)\n\n\twireguard.RegisterOutbound(registry)\n\n\treturn registry\n}\n\nfunc nekoboxAndroidEndpointRegistry() *endpoint.Registry {\n\tregistry := endpoint.NewRegistry()\n\n\twireguard.RegisterEndpoint(registry)\n\n\treturn registry\n}\n\nfunc nekoboxAndroidDNSTransportRegistry(localTransport LocalDNSTransport) *dns.TransportRegistry {\n\tregistry := dns.NewTransportRegistry()\n\n\ttransport.RegisterTCP(registry)\n\ttransport.RegisterUDP(registry)\n\ttransport.RegisterTLS(registry)\n\ttransport.RegisterHTTPS(registry)\n\thosts.RegisterTransport(registry)\n\t// local.RegisterTransport(registry)\n\tfakeip.RegisterTransport(registry)\n\n\tquic.RegisterTransport(registry)\n\tquic.RegisterHTTP3Transport(registry)\n\n\tif localTransport == nil {\n\t\tlocal.RegisterTransport(registry)\n\t} else {\n\t\tdns.RegisterTransport(registry, \"local\", func(ctx context.Context, logger log.ContextLogger, tag string, options option.LocalDNSServerOptions) (adapter.DNSTransport, error) {\n\t\t\treturn newPlatformTransport(localTransport, tag, options), nil\n\t\t})\n\t}\n\n\treturn registry\n}\n\nfunc nekoboxAndroidServiceRegistry() *service.Registry {\n\tregistry := service.NewRegistry()\n\n\treturn registry\n}\n"
  },
  {
    "path": "libcore/build.sh",
    "content": "#!/bin/bash\n\nsource ./env_java.sh || true\nsource ../buildScript/init/env_ndk.sh\n\nBUILD=\".build\"\n\nrm -rf $BUILD/android \\\n  $BUILD/java \\\n  $BUILD/javac-output \\\n  $BUILD/src\n\nif [ -z \"$GOPATH\" ]; then\n  GOPATH=$(go env GOPATH)\nfi\n\nexport GOBIND=gobind-matsuri\n\"$GOPATH\"/bin/gomobile-matsuri bind -v -androidapi 21 -cache \"$(realpath $BUILD)\" -trimpath -ldflags='-s -w' -tags='with_conntrack,with_gvisor,with_quic,with_wireguard,with_utls,with_clash_api' . || exit 1\nrm -r libcore-sources.jar\n\nproj=../app/libs\nmkdir -p $proj\ncp -f libcore.aar $proj\necho \">> install $(realpath $proj)/libcore.aar\"\n"
  },
  {
    "path": "libcore/certs.go",
    "content": "package libcore\n\nimport (\n\t\"crypto/x509\"\n\t\"log\"\n\t_ \"unsafe\" // for go:linkname\n)\n\n//go:linkname systemRoots crypto/x509.systemRoots\nvar systemRoots *x509.CertPool\n\nfunc updateRootCACerts(pem []byte) {\n\tx509.SystemCertPool()\n\troots := x509.NewCertPool()\n\tif !roots.AppendCertsFromPEM(pem) {\n\t\tlog.Println(\"failed to append certificates from pem\")\n\t\treturn\n\t}\n\tsystemRoots = roots\n\tlog.Println(\"external ca.pem was loaded\")\n}\n\n//go:linkname initSystemRoots crypto/x509.initSystemRoots\nfunc initSystemRoots()\n"
  },
  {
    "path": "libcore/crypto.go",
    "content": "package libcore\n\nimport (\n\t\"crypto/sha1\"\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n)\n\nfunc Sha1(data []byte) []byte {\n\tsum := sha1.Sum(data)\n\treturn sum[:]\n}\n\nfunc Sha256Hex(data []byte) string {\n\tsum := sha256.Sum256(data)\n\treturn hex.EncodeToString(sum[:])\n}\n"
  },
  {
    "path": "libcore/device/debug.go",
    "content": "package device\n\nimport (\n\t\"fmt\"\n\t\"runtime/debug\"\n)\n\nvar DebugFunc func(interface{})\n\nfunc GoDebug(any interface{}) {\n\tif DebugFunc != nil {\n\t\tgo DebugFunc(any)\n\t}\n}\n\nfunc DeferPanicToError(name string, onError func(error)) {\n\tif r := recover(); r != nil {\n\t\tif onError != nil {\n\t\t\ts := fmt.Errorf(\"%s panic: %s\\n%s\", name, r, string(debug.Stack()))\n\t\t\tonError(s)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libcore/device/device.go",
    "content": "package device\n\nimport (\n\t\"runtime\"\n)\n\nfunc NumUDPWorkers() int {\n\tnumUDPWorkers := 4\n\tif num := runtime.GOMAXPROCS(0); num > numUDPWorkers {\n\t\tnumUDPWorkers = num\n\t}\n\treturn numUDPWorkers\n}\n"
  },
  {
    "path": "libcore/dns_android.go",
    "content": "//go:build android && cgo\n\npackage libcore\n\n/*\n#include <stdint.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <dlfcn.h>\n\ntypedef int (*android_res_nsend_t)(uint64_t network, const uint8_t* msg, size_t msglen, int flags);\ntypedef int (*android_res_nresult_t)(int fd, int* rcode, uint8_t* resp, size_t resp_len);\n\nstatic int call_android_res_nsend(void* sym, uint64_t network, const uint8_t* msg, size_t msglen, int flags) {\n    android_res_nsend_t f = (android_res_nsend_t)sym;\n    if (!f) return -1;\n    return f(network, msg, msglen, flags);\n}\n\nstatic int call_android_res_nresult(void* sym, int fd, int* rcode, uint8_t* resp, size_t resp_len) {\n    android_res_nresult_t f = (android_res_nresult_t)sym;\n    if (!f) return -1;\n    return f(fd, rcode, resp, resp_len);\n}\n*/\nimport \"C\"\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\t\"unsafe\"\n\n\t\"golang.org/x/sys/unix\"\n)\n\nfunc init() {\n\tlibname := C.CString(\"libandroid.so\")\n\tdefer C.free(unsafe.Pointer(libname))\n\n\tlibHandle := C.dlopen(libname, C.int(C.RTLD_NOW))\n\tif libHandle == nil {\n\t\treturn\n\t}\n\n\tsymNameSend := C.CString(\"android_res_nsend\")\n\tdefer C.free(unsafe.Pointer(symNameSend))\n\tandroidResNSendSym := C.dlsym(libHandle, symNameSend)\n\tif androidResNSendSym == nil {\n\t\treturn\n\t}\n\n\tsymNameResult := C.CString(\"android_res_nresult\")\n\tdefer C.free(unsafe.Pointer(symNameResult))\n\tandroidResNResultSym := C.dlsym(libHandle, symNameResult)\n\tif androidResNResultSym == nil {\n\t\treturn\n\t}\n\n\tcallAndroidResNSend := func(network uint64, msg []byte) (int, error) {\n\t\tif len(msg) == 0 {\n\t\t\treturn 0, errors.New(\"empty payload\")\n\t\t}\n\t\tmsgPtr := (*C.uint8_t)(unsafe.Pointer(&msg[0]))\n\t\tmsgLen := C.size_t(len(msg))\n\t\tret := C.call_android_res_nsend(androidResNSendSym, C.uint64_t(network), msgPtr, msgLen, C.int(0))\n\t\treturn int(ret), nil\n\t}\n\n\tcallAndroidResNResult := func(fd int, resp []byte) (int, int) {\n\t\tif len(resp) == 0 {\n\t\t\treturn 0, 0\n\t\t}\n\t\trespPtr := (*C.uint8_t)(unsafe.Pointer(&resp[0]))\n\t\trespLen := C.size_t(len(resp))\n\t\tvar rcode C.int\n\t\tn := C.call_android_res_nresult(androidResNResultSym, C.int(fd), &rcode, respPtr, respLen)\n\t\treturn int(rcode), int(n)\n\t}\n\n\t// set rawQueryFunc\n\trawQueryFunc = func(networkHandle int64, request []byte) ([]byte, error) {\n\t\tfd, err := callAndroidResNSend(uint64(networkHandle), request)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif fd < 0 {\n\t\t\treturn nil, unix.Errno(-fd)\n\t\t}\n\n\t\t// wait for response (timeout 5000 ms)\n\t\tpfds := []unix.PollFd{{Fd: int32(fd), Events: unix.POLLIN | unix.POLLERR}}\n\t\tnReady, err := unix.Poll(pfds, 5000)\n\t\tif err != nil {\n\t\t\tunix.Close(fd)\n\t\t\treturn nil, err\n\t\t}\n\t\tif nReady == 0 {\n\t\t\tunix.Close(fd)\n\t\t\treturn nil, context.DeadlineExceeded\n\t\t}\n\n\t\t// read response into buffer\n\t\tresponse := make([]byte, 8192)\n\t\t_, n := callAndroidResNResult(fd, response)\n\t\tif n < 0 {\n\t\t\treturn nil, unix.Errno(-n)\n\t\t}\n\t\tif n == 0 {\n\t\t\treturn nil, os.ErrInvalid\n\t\t}\n\t\treturn response[:n], nil\n\t}\n}\n"
  },
  {
    "path": "libcore/dns_box.go",
    "content": "// libbox/dns.go\n\npackage libcore\n\nimport (\n\t\"context\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"github.com/sagernet/sing/common\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\tM \"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/common/task\"\n\n\tmDNS \"github.com/miekg/dns\"\n)\n\nvar rawQueryFunc func(networkHandle int64, request []byte) ([]byte, error)\n\ntype LocalDNSTransport interface {\n\tRaw() bool\n\tNetworkHandle() int64\n\tLookup(ctx *ExchangeContext, network string, domain string) error\n\tExchange(ctx *ExchangeContext, message []byte) error\n}\n\nvar gLocalDNSTransport *platformLocalDNSTransport = nil\n\ntype platformLocalDNSTransport struct {\n\tdns.TransportAdapter\n\tiif LocalDNSTransport\n\traw bool\n}\n\nfunc newPlatformTransport(iif LocalDNSTransport, tag string, options option.LocalDNSServerOptions) *platformLocalDNSTransport {\n\treturn &platformLocalDNSTransport{\n\t\tTransportAdapter: dns.NewTransportAdapterWithLocalOptions(constant.DNSTypeLocal, tag, options),\n\t\tiif:              iif,\n\t\traw:              iif.Raw(),\n\t}\n}\n\nfunc (p *platformLocalDNSTransport) Start(stage adapter.StartStage) error {\n\treturn nil\n}\n\nfunc (p *platformLocalDNSTransport) Close() error {\n\treturn nil\n}\n\nfunc (p *platformLocalDNSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {\n\tif p.raw && rawQueryFunc != nil {\n\t\t// Raw - Android 10 及以上才有\n\n\t\tmessageBytes, err := message.Pack()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tmsg, err := rawQueryFunc(p.iif.NetworkHandle(), messageBytes)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresponseMessage := new(mDNS.Msg)\n\t\terr = responseMessage.Unpack(msg)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn responseMessage, nil\n\t} else {\n\t\t// Lookup - Android 10 以下\n\n\t\tquestion := message.Question[0]\n\t\tvar network string\n\t\tswitch question.Qtype {\n\t\tcase mDNS.TypeA:\n\t\t\tnetwork = \"ip4\"\n\t\tcase mDNS.TypeAAAA:\n\t\t\tnetwork = \"ip6\"\n\t\tdefault:\n\t\t\treturn nil, E.New(\"only IP queries are supported by current version of Android\")\n\t\t}\n\n\t\tdone := make(chan struct{})\n\t\tresponse := &ExchangeContext{\n\t\t\tcontext: ctx,\n\t\t\tdone: sync.OnceFunc(func() {\n\t\t\t\tclose(done)\n\t\t\t}),\n\t\t}\n\n\t\tvar responseAddrs []netip.Addr\n\t\tvar group task.Group\n\t\tgroup.Append0(func(ctx context.Context) error {\n\t\t\terr := p.iif.Lookup(response, network, question.Name)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tselect {\n\t\t\tcase <-done:\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn context.Canceled\n\t\t\t}\n\t\t\tif response.error != nil {\n\t\t\t\treturn response.error\n\t\t\t}\n\t\t\tresponseAddrs = response.addresses\n\t\t\treturn nil\n\t\t})\n\t\terr := group.Run(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn dns.FixedResponse(message.Id, question, responseAddrs, constant.DefaultDNSTTL), nil\n\t}\n}\n\ntype Func interface {\n\tInvoke() error\n}\n\ntype ExchangeContext struct {\n\tcontext   context.Context\n\tmessage   mDNS.Msg\n\taddresses []netip.Addr\n\terror     error\n\tdone      func()\n}\n\nfunc (c *ExchangeContext) OnCancel(callback Func) {\n\tgo func() {\n\t\t<-c.context.Done()\n\t\tcallback.Invoke()\n\t}()\n}\n\nfunc (c *ExchangeContext) Success(result string) {\n\tc.addresses = common.Map(common.Filter(strings.Split(result, \"\\n\"), func(it string) bool {\n\t\treturn !common.IsEmpty(it)\n\t}), func(it string) netip.Addr {\n\t\treturn M.ParseSocksaddrHostPort(it, 0).Unwrap().Addr\n\t})\n}\n\nfunc (c *ExchangeContext) RawSuccess(result []byte) {\n\terr := c.message.Unpack(result)\n\tif err != nil {\n\t\tc.error = E.Cause(err, \"parse response\")\n\t}\n\tc.done()\n}\n\nfunc (c *ExchangeContext) ErrorCode(code int32) {\n\tc.error = dns.RcodeError(code)\n\tc.done()\n}\n\nfunc (c *ExchangeContext) ErrnoCode(code int32) {\n\tc.error = syscall.Errno(code)\n\tc.done()\n}\n"
  },
  {
    "path": "libcore/ech/ech.go",
    "content": "package ech\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"log\"\n\t\"net\"\n\t\"os\"\n\n\tmDNS \"github.com/miekg/dns\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/dns\"\n\t\"github.com/sagernet/sing/common/exceptions\"\n)\n\ntype ECHClientConfig struct {\n\t*tls.Config\n\tdomain            string\n\tlocalDnsTransport adapter.DNSTransport\n}\n\nfunc NewECHClientConfig(domain string, tlsConfig *tls.Config, localDnsTransport adapter.DNSTransport) *ECHClientConfig {\n\tconfig := tlsConfig.Clone()\n\tconfig.ServerName = domain\n\treturn &ECHClientConfig{\n\t\tConfig:            config,\n\t\tdomain:            domain,\n\t\tlocalDnsTransport: localDnsTransport,\n\t}\n}\n\nfunc (s *ECHClientConfig) Client(ctx context.Context, conn net.Conn) (*tls.Conn, error) {\n\terr := s.fetchEchKeys(ctx, conn)\n\tif err != nil {\n\t\t// allow empty ech keys\n\t\tlog.Println(\"fetchEchKeys:\", err)\n\t}\n\treturn tls.Client(conn, s.Config), nil\n}\n\nfunc (s *ECHClientConfig) fetchEchKeys(ctx context.Context, conn net.Conn) error {\n\tmessage := &mDNS.Msg{\n\t\tMsgHdr: mDNS.MsgHdr{\n\t\t\tRecursionDesired: true,\n\t\t},\n\t\tQuestion: []mDNS.Question{\n\t\t\t{\n\t\t\t\tName:   mDNS.Fqdn(s.domain),\n\t\t\t\tQtype:  mDNS.TypeHTTPS,\n\t\t\t\tQclass: mDNS.ClassINET,\n\t\t\t},\n\t\t},\n\t}\n\tif s.localDnsTransport == nil {\n\t\treturn os.ErrInvalid\n\t}\n\tresponse, err := s.localDnsTransport.Exchange(ctx, message)\n\tif err != nil {\n\t\treturn exceptions.Cause(err, \"fetch ECH config list\")\n\t}\n\tif response.Rcode != mDNS.RcodeSuccess {\n\t\treturn exceptions.Cause(dns.RcodeError(response.Rcode), \"fetch ECH config list\")\n\t}\n\tfor _, rr := range response.Answer {\n\t\tswitch resource := rr.(type) {\n\t\tcase *mDNS.HTTPS:\n\t\t\tfor _, value := range resource.Value {\n\t\t\t\tif value.Key().String() == \"ech\" {\n\t\t\t\t\techConfigList, err := base64.StdEncoding.DecodeString(value.String())\n\t\t\t\t\tif err == nil {\n\t\t\t\t\t\ts.Config.EncryptedClientHelloConfigList = echConfigList\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "libcore/fix.go",
    "content": "package libcore\n\n// https://github.com/golang/go/issues/46893\n// TODO: remove after `bulkBarrierPreWrite: unaligned arguments` fixed\n\ntype StringBox struct {\n\tValue string\n}\n\nfunc wrapString(value string) *StringBox {\n\treturn &StringBox{Value: value}\n}\n"
  },
  {
    "path": "libcore/geoip.go",
    "content": "package libcore\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/oschwald/maxminddb-golang\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/nekoutils\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype geoip struct {\n\tgeoipReader *maxminddb.Reader\n}\n\nfunc (g *geoip) Open(path string) error {\n\tgeoipReader, err := maxminddb.Open(path)\n\tg.geoipReader = geoipReader\n\treturn err\n}\n\nfunc (g *geoip) Rules(countryCode string) ([]option.HeadlessRule, error) {\n\tnetworks := g.geoipReader.Networks(maxminddb.SkipAliasedNetworks)\n\tcountryMap := make(map[string][]*net.IPNet)\n\tvar (\n\t\tipNet           *net.IPNet\n\t\tnextCountryCode string\n\t\terr             error\n\t)\n\tfor networks.Next() {\n\t\tipNet, err = networks.Network(&nextCountryCode)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to get network: %w\", err)\n\t\t}\n\t\tcountryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)\n\t}\n\n\tipNets := countryMap[strings.ToLower(countryCode)]\n\n\tif len(ipNets) == 0 {\n\t\treturn nil, fmt.Errorf(\"no networks found for country code: %s\", countryCode)\n\t}\n\n\tvar headlessRule option.DefaultHeadlessRule\n\theadlessRule.IPCIDR = make([]string, 0, len(ipNets))\n\tfor _, cidr := range ipNets {\n\t\theadlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())\n\t}\n\n\treturn []option.HeadlessRule{\n\t\t{\n\t\t\tType:           C.RuleTypeDefault,\n\t\t\tDefaultOptions: headlessRule,\n\t\t},\n\t}, nil\n}\n\nfunc init() {\n\tnekoutils.GetGeoIPHeadlessRules = func(name string) ([]option.HeadlessRule, error) {\n\t\tg := new(geoip)\n\t\tif err := g.Open(filepath.Join(externalAssetsPath, \"geoip.db\")); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer g.geoipReader.Close()\n\t\treturn g.Rules(name)\n\t}\n}\n"
  },
  {
    "path": "libcore/geosite.go",
    "content": "package libcore\n\nimport (\n\t\"fmt\"\n\t\"path/filepath\"\n\n\tgeosites \"github.com/sagernet/sing-box/common/geosite\"\n\tC \"github.com/sagernet/sing-box/constant\"\n\t\"github.com/sagernet/sing-box/nekoutils\"\n\t\"github.com/sagernet/sing-box/option\"\n)\n\ntype geosite struct {\n\tgeositeReader *geosites.Reader\n}\n\nfunc (g *geosite) Open(path string) error {\n\tgeositeReader, _, err := geosites.Open(path)\n\tg.geositeReader = geositeReader\n\treturn err\n}\n\nfunc (g *geosite) Rules(code string) ([]option.HeadlessRule, error) {\n\tsourceSet, err := g.geositeReader.Read(code)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read geosite code %s :%w\", code, err)\n\t}\n\n\tvar headlessRule option.DefaultHeadlessRule\n\n\tdefaultRule := geosites.Compile(sourceSet)\n\n\theadlessRule.Domain = defaultRule.Domain\n\theadlessRule.DomainSuffix = defaultRule.DomainSuffix\n\theadlessRule.DomainKeyword = defaultRule.DomainKeyword\n\theadlessRule.DomainRegex = defaultRule.DomainRegex\n\n\treturn []option.HeadlessRule{\n\t\t{\n\t\t\tType:           C.RuleTypeDefault,\n\t\t\tDefaultOptions: headlessRule,\n\t\t},\n\t}, nil\n}\n\nfunc init() {\n\tnekoutils.GetGeoSiteHeadlessRules = func(name string) ([]option.HeadlessRule, error) {\n\t\tg := new(geosite)\n\t\tif err := g.Open(filepath.Join(externalAssetsPath, \"geosite.db\")); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn g.Rules(name)\n\t}\n}\n"
  },
  {
    "path": "libcore/go.mod",
    "content": "module libcore\n\ngo 1.23.1\n\ntoolchain go1.23.6\n\nrequire (\n\tgithub.com/matsuridayo/libneko v1.0.0 // replaced\n\tgithub.com/miekg/dns v1.1.67\n\tgithub.com/oschwald/maxminddb-golang v1.13.1\n\tgithub.com/sagernet/quic-go v0.52.0-sing-box-mod.3\n\tgithub.com/sagernet/sing v0.7.18\n\tgithub.com/sagernet/sing-box v1.0.0 // replaced\n\tgithub.com/sagernet/sing-tun v0.7.10\n\tgithub.com/ulikunitz/xz v0.5.15\n\tgolang.org/x/mobile v0.0.0-20231108233038-35478a0c49da\n\tgolang.org/x/sys v0.35.0\n)\n\nrequire (\n\tgithub.com/ajg/form v1.5.1 // indirect\n\tgithub.com/andybalholm/brotli v1.1.0 // indirect\n\tgithub.com/anytls/sing-anytls v0.0.11 // indirect\n\tgithub.com/caddyserver/certmagic v0.23.0 // indirect\n\tgithub.com/caddyserver/zerossl v0.1.3 // indirect\n\tgithub.com/cretz/bine v0.2.0 // indirect\n\tgithub.com/fsnotify/fsnotify v1.7.0 // indirect\n\tgithub.com/go-chi/chi/v5 v5.2.2 // indirect\n\tgithub.com/go-chi/render v1.0.3 // indirect\n\tgithub.com/go-ole/go-ole v1.3.0 // indirect\n\tgithub.com/gobwas/httphead v0.1.0 // indirect\n\tgithub.com/gobwas/pool v0.2.1 // indirect\n\tgithub.com/gofrs/uuid/v5 v5.3.2 // indirect\n\tgithub.com/google/btree v1.1.3 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/hashicorp/yamux v0.1.2 // indirect\n\tgithub.com/klauspost/compress v1.17.11 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.2.10 // indirect\n\tgithub.com/libdns/alidns v1.0.5-libdns.v1.beta1 // indirect\n\tgithub.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 // indirect\n\tgithub.com/libdns/libdns v1.1.0 // indirect\n\tgithub.com/logrusorgru/aurora v2.0.3+incompatible // indirect\n\tgithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 // indirect\n\tgithub.com/mdlayher/socket v0.5.1 // indirect\n\tgithub.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 // indirect\n\tgithub.com/metacubex/utls v1.8.4 // indirect\n\tgithub.com/mholt/acmez/v3 v3.1.2 // indirect\n\tgithub.com/quic-go/qpack v0.5.1 // indirect\n\tgithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect\n\tgithub.com/sagernet/cors v1.2.1 // indirect\n\tgithub.com/sagernet/fswatch v0.1.1 // indirect\n\tgithub.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb // indirect\n\tgithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect\n\tgithub.com/sagernet/nftables v0.3.0-beta.4 // indirect\n\tgithub.com/sagernet/sing-mux v0.3.4 // indirect\n\tgithub.com/sagernet/sing-quic v0.5.2 // indirect\n\tgithub.com/sagernet/sing-shadowsocks v0.2.8 // indirect\n\tgithub.com/sagernet/sing-shadowsocks2 v0.2.1 // indirect\n\tgithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 // indirect\n\tgithub.com/sagernet/sing-vmess v0.2.7 // indirect\n\tgithub.com/sagernet/smux v1.5.50-sing-box-mod.1 // indirect\n\tgithub.com/sagernet/wireguard-go v0.0.1-beta.7 // indirect\n\tgithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect\n\tgithub.com/vishvananda/netns v0.0.5 // indirect\n\tgithub.com/zeebo/blake3 v0.2.4 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.uber.org/zap/exp v0.3.0 // indirect\n\tgo4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect\n\tgolang.org/x/crypto v0.41.0 // indirect\n\tgolang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect\n\tgolang.org/x/mod v0.27.0 // indirect\n\tgolang.org/x/net v0.43.0 // indirect\n\tgolang.org/x/sync v0.16.0 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n\tgolang.org/x/time v0.9.0 // indirect\n\tgolang.org/x/tools v0.36.0 // indirect\n\tgolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect\n\tgoogle.golang.org/grpc v1.73.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tlukechampine.com/blake3 v1.3.0 // indirect\n)\n\nreplace github.com/matsuridayo/libneko => ../../libneko\n\nreplace github.com/sagernet/sing-box => ../../sing-box\n"
  },
  {
    "path": "libcore/go.sum",
    "content": "github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=\ngithub.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=\ngithub.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=\ngithub.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=\ngithub.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc=\ngithub.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8=\ngithub.com/caddyserver/certmagic v0.23.0 h1:CfpZ/50jMfG4+1J/u2LV6piJq4HOfO6ppOnOf7DkFEU=\ngithub.com/caddyserver/certmagic v0.23.0/go.mod h1:9mEZIWqqWoI+Gf+4Trh04MOVPD0tGSxtqsxg87hAIH4=\ngithub.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA=\ngithub.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4=\ngithub.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=\ngithub.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=\ngithub.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=\ngithub.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=\ngithub.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=\ngithub.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=\ngithub.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=\ngithub.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=\ngithub.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=\ngithub.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=\ngithub.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=\ngithub.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=\ngithub.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0=\ngithub.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=\ngithub.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=\ngithub.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=\ngithub.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=\ngithub.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=\ngithub.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=\ngithub.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/libdns/alidns v1.0.5-libdns.v1.beta1 h1:txHK7UxDed3WFBDjrTZPuMn8X+WmhjBTTAMW5xdy5pQ=\ngithub.com/libdns/alidns v1.0.5-libdns.v1.beta1/go.mod h1:ystHmPwcGoWjPrGpensQSMY9VoCx4cpR2hXNlwk9H/g=\ngithub.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6 h1:3MGrVWs2COjMkQR17oUw1zMIPbm2YAzxDC3oGVZvQs8=\ngithub.com/libdns/cloudflare v0.2.2-0.20250708034226-c574dccb31a6/go.mod h1:w9uTmRCDlAoafAsTPnn2nJ0XHK/eaUMh86DUk8BWi60=\ngithub.com/libdns/libdns v1.0.0-beta.1/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\ngithub.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=\ngithub.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=\ngithub.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42 h1:A1Cq6Ysb0GM0tpKMbdCXCIfBclan4oHk1Jb+Hrejirg=\ngithub.com/mdlayher/netlink v1.7.3-0.20250113171957-fbb4dce95f42/go.mod h1:BB4YCPDOzfy7FniQ/lxuYQ3dgmM2cZumHbK8RpTjN2o=\ngithub.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=\ngithub.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=\ngithub.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0 h1:Ui+/2s5Qz0lSnDUBmEL12M5Oi/PzvFxGTNohm8ZcsmE=\ngithub.com/metacubex/tfo-go v0.0.0-20250921095601-b102db4216c0/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=\ngithub.com/metacubex/utls v1.8.4 h1:HmL9nUApDdWSkgUyodfwF6hSjtiwCGGdyhaSpEejKpg=\ngithub.com/metacubex/utls v1.8.4/go.mod h1:kncGGVhFaoGn5M3pFe3SXhZCzsbCJayNOH4UEqTKTko=\ngithub.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=\ngithub.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=\ngithub.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0=\ngithub.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps=\ngithub.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=\ngithub.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=\ngithub.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0=\ngithub.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM=\ngithub.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=\ngithub.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=\ngithub.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=\ngithub.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=\ngithub.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=\ngithub.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=\ngithub.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=\ngithub.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=\ngithub.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=\ngithub.com/sagernet/quic-go v0.52.0-sing-box-mod.3 h1:ySqffGm82rPqI1TUPqmtHIYd12pfEGScygnOxjTL56w=\ngithub.com/sagernet/quic-go v0.52.0-sing-box-mod.3/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=\ngithub.com/sagernet/sing v0.7.18 h1:iZHkaru1/MoHugx3G+9S3WG4owMewKO/KvieE2Pzk4E=\ngithub.com/sagernet/sing v0.7.18/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=\ngithub.com/sagernet/sing-mux v0.3.4 h1:ZQplKl8MNXutjzbMVtWvWG31fohhgOfCuUZR4dVQ8+s=\ngithub.com/sagernet/sing-mux v0.3.4/go.mod h1:QvlKMyNBNrQoyX4x+gq028uPbLM2XeRpWtDsWBJbFSk=\ngithub.com/sagernet/sing-quic v0.5.2 h1:I3vlfRImhr0uLwRS3b3ib70RMG9FcXtOKKUDz3eKRWc=\ngithub.com/sagernet/sing-quic v0.5.2/go.mod h1:evP1e++ZG8TJHVV5HudXV4vWeYzGfCdF4HwSJZcdqkI=\ngithub.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=\ngithub.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=\ngithub.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=\ngithub.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=\ngithub.com/sagernet/sing-tun v0.7.10 h1:lLBaS9uL0mK/FCGDe3N4oKQxjMGfmv3u2/6jKtmq4Pw=\ngithub.com/sagernet/sing-tun v0.7.10/go.mod h1:pUEjh9YHQ2gJT6Lk0TYDklh3WJy7lz+848vleGM3JPM=\ngithub.com/sagernet/sing-vmess v0.2.7 h1:2ee+9kO0xW5P4mfe6TYVWf9VtY8k1JhNysBqsiYj0sk=\ngithub.com/sagernet/sing-vmess v0.2.7/go.mod h1:5aYoOtYksAyS0NXDm0qKeTYW1yoE1bJVcv+XLcVoyJs=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1 h1:XkJcivBC9V4wBjiGXIXZ229aZCU1hzcbp6kSkkyQ478=\ngithub.com/sagernet/smux v1.5.50-sing-box-mod.1/go.mod h1:NjhsCEWedJm7eFLyhuBgIEzwfhRmytrUoiLluxs5Sk8=\ngithub.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=\ngithub.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=\ngithub.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=\ngithub.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=\ngithub.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=\ngithub.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=\ngithub.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=\ngithub.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=\ngithub.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=\ngithub.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=\ngithub.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=\ngo.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=\ngo.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=\ngo.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=\ngo.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=\ngo.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=\ngo.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=\ngo.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=\ngo.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=\ngo.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U=\ngo.uber.org/zap/exp v0.3.0/go.mod h1:5I384qq7XGxYyByIhHm6jg5CHkGY0nsTfbDLgDDlgJQ=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=\ngo4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=\ngolang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=\ngolang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=\ngolang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=\ngolang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=\ngolang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=\ngolang.org/x/mobile v0.0.0-20231108233038-35478a0c49da h1:gS9sVMAeHM+gVBmM9bTM6vUi/NHv58O3QzJ3vjjN84M=\ngolang.org/x/mobile v0.0.0-20231108233038-35478a0c49da/go.mod h1:IEceR0jfVklLJXrbUe90rfdAFAYDW0SQwKl4qvO1GBQ=\ngolang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=\ngolang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=\ngolang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=\ngolang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=\ngolang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=\ngolang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngolang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=\ngolang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=\ngolang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=\ngolang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=\ngoogle.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nlukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=\nlukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=\n"
  },
  {
    "path": "libcore/http.go",
    "content": "package libcore\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"libcore/device\"\n\t\"libcore/ech\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/sagernet/quic-go\"\n\t\"github.com/sagernet/quic-go/http3\"\n\t\"github.com/sagernet/sing/common/metadata\"\n\t\"github.com/sagernet/sing/protocol/socks\"\n\t\"github.com/sagernet/sing/protocol/socks/socks5\"\n)\n\nvar errFailConnectSocks5 = errors.New(\"fail connect socks5\")\n\ntype HTTPClient interface {\n\tRestrictedTLS()\n\tModernTLS()\n\tPinnedTLS12()\n\tPinnedSHA256(sumHex string)\n\tTrySocks5(port int32)\n\tTryH3Direct()\n\tKeepAlive()\n\tNewRequest() HTTPRequest\n\tClose()\n}\n\ntype HTTPRequest interface {\n\tSetURL(link string) error\n\tSetMethod(method string)\n\tSetHeader(key string, value string)\n\tSetContent(content []byte)\n\tSetContentString(content string)\n\tSetUserAgent(userAgent string)\n\tAllowInsecure()\n\tExecute() (HTTPResponse, error)\n}\n\ntype HTTPResponse interface {\n\tGetHeader(string) *StringBox\n\tGetContent() ([]byte, error)\n\tGetContentString() (*StringBox, error)\n\tWriteTo(path string) error\n}\n\nvar (\n\t_ HTTPClient   = (*httpClient)(nil)\n\t_ HTTPRequest  = (*httpRequest)(nil)\n\t_ HTTPResponse = (*httpResponse)(nil)\n)\n\ntype httpClient struct {\n\ttls           tls.Config\n\th1h2Transport http.Transport\n\th1h2Client    http.Client\n\ttrySocks5     bool\n\ttryH3Direct   bool\n}\n\nfunc NewHttpClient() HTTPClient {\n\tclient := new(httpClient)\n\tclient.h1h2Client.Transport = &client.h1h2Transport\n\tclient.h1h2Transport.TLSClientConfig = &client.tls\n\tclient.h1h2Transport.DisableKeepAlives = true\n\treturn client\n}\n\nfunc (c *httpClient) ModernTLS() {\n\tc.tls.MinVersion = tls.VersionTLS12\n\t// c.tls.CipherSuites = nekoutils.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID })\n}\n\nfunc (c *httpClient) RestrictedTLS() {\n\tc.tls.MinVersion = tls.VersionTLS13\n\t// c.tls.CipherSuites = nekoutils.Map(nekoutils.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool {\n\t// \treturn nekoutils.Contains(it.SupportedVersions, uint16(tls.VersionTLS13))\n\t// }), func(it *tls.CipherSuite) uint16 {\n\t// \treturn it.ID\n\t// })\n}\n\nfunc (c *httpClient) PinnedTLS12() {\n\tc.tls.MinVersion = tls.VersionTLS12\n\tc.tls.MaxVersion = tls.VersionTLS12\n}\n\nfunc (c *httpClient) PinnedSHA256(sumHex string) {\n\tc.tls.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {\n\t\tfor _, rawCert := range rawCerts {\n\t\t\tcertSum := sha256.Sum256(rawCert)\n\t\t\tif sumHex == hex.EncodeToString(certSum[:]) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t}\n\t\treturn errors.New(\"pinned sha256 sum mismatch\")\n\t}\n}\n\nfunc (c *httpClient) TrySocks5(port int32) {\n\tdialer := new(net.Dialer)\n\tc.h1h2Transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\tfor {\n\t\t\tsocksConn, err := dialer.DialContext(ctx, \"tcp\", \"127.0.0.1:\"+strconv.Itoa(int(port)))\n\t\t\tif err != nil {\n\t\t\t\tif c.tryH3Direct {\n\t\t\t\t\treturn nil, errFailConnectSocks5\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\t_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, metadata.ParseSocksaddr(addr), \"\", \"\")\n\t\t\tif err != nil {\n\t\t\t\tif c.tryH3Direct {\n\t\t\t\t\treturn nil, errFailConnectSocks5\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn socksConn, err\n\t\t}\n\t\treturn dialer.DialContext(ctx, network, addr)\n\t}\n\tc.trySocks5 = true\n}\n\nfunc (c *httpClient) TryH3Direct() {\n\tc.tryH3Direct = true\n}\n\nfunc (c *httpClient) KeepAlive() {\n\tc.h1h2Transport.ForceAttemptHTTP2 = true\n\tc.h1h2Transport.DisableKeepAlives = false\n}\n\nfunc (c *httpClient) NewRequest() HTTPRequest {\n\treq := &httpRequest{httpClient: c}\n\treq.request = http.Request{\n\t\tMethod: \"GET\",\n\t\tHeader: http.Header{},\n\t}\n\treturn req\n}\n\nfunc (c *httpClient) Close() {\n\tc.h1h2Transport.CloseIdleConnections()\n}\n\ntype httpRequest struct {\n\t*httpClient\n\trequest http.Request\n}\n\nfunc (r *httpRequest) AllowInsecure() {\n\tr.tls.InsecureSkipVerify = true\n}\n\nfunc (r *httpRequest) SetURL(link string) (err error) {\n\tr.request.URL, err = url.Parse(link)\n\tif err != nil {\n\t\treturn\n\t}\n\tif r.request.URL.User != nil {\n\t\tuser := r.request.URL.User.Username()\n\t\tpassword, _ := r.request.URL.User.Password()\n\t\tr.request.SetBasicAuth(user, password)\n\t}\n\treturn\n}\n\nfunc (r *httpRequest) SetMethod(method string) {\n\tr.request.Method = method\n}\n\nfunc (r *httpRequest) SetHeader(key string, value string) {\n\tr.request.Header.Set(key, value)\n}\n\nfunc (r *httpRequest) SetUserAgent(userAgent string) {\n\tr.request.Header.Set(\"User-Agent\", userAgent)\n}\n\nfunc (r *httpRequest) SetContent(content []byte) {\n\tbuffer := bytes.Buffer{}\n\tbuffer.Write(content)\n\tr.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))\n\tr.request.ContentLength = int64(len(content))\n}\n\nfunc (r *httpRequest) SetContentString(content string) {\n\tr.SetContent([]byte(content))\n}\n\nfunc (r *httpRequest) Execute() (HTTPResponse, error) {\n\tdefer device.DeferPanicToError(\"http execute\", func(err error) { log.Println(err) })\n\t// full direct\n\tif r.tryH3Direct && !r.trySocks5 {\n\t\treturn r.doH3Direct()\n\t}\n\tresponse, err := r.h1h2Client.Do(&r.request)\n\tif err != nil {\n\t\t// trySocks5 && tryH3Direct\n\t\tif r.tryH3Direct && errors.Is(err, errFailConnectSocks5) {\n\t\t\treturn r.doH3Direct()\n\t\t}\n\t\treturn nil, err\n\t}\n\thttpResp := &httpResponse{Response: response}\n\tif response.StatusCode != http.StatusOK {\n\t\treturn nil, errors.New(httpResp.errorString())\n\t}\n\treturn httpResp, nil\n}\n\ntype requestFunc func() (response *http.Response, err error)\n\nfunc (r *httpRequest) doH3Direct() (HTTPResponse, error) {\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\n\tdefer cancel()\n\n\tsuccessCh := make(chan *http.Response, 1)\n\tvar finalErr error\n\tvar failedCount atomic.Uint32\n\tvar successCount atomic.Uint32\n\tvar mu sync.Mutex\n\n\tfuncs := []requestFunc{\n\t\t// Http(s) With Ech\n\t\tfunc() (response *http.Response, err error) {\n\t\t\trequest := r.request.Clone(context.Background())\n\t\t\techClient := &http.Client{\n\t\t\t\tTransport: &http.Transport{\n\t\t\t\t\tDialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {\n\t\t\t\t\t\tvar d net.Dialer\n\t\t\t\t\t\tc, err := d.DialContext(ctx, network, addr)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn c, err\n\t\t\t\t\t\t}\n\t\t\t\t\t\tdomain := addr\n\t\t\t\t\t\tif host, _, _ := net.SplitHostPort(addr); host != \"\" {\n\t\t\t\t\t\t\tdomain = host\n\t\t\t\t\t\t}\n\t\t\t\t\t\techTls := ech.NewECHClientConfig(domain, &r.tls, gLocalDNSTransport)\n\t\t\t\t\t\treturn echTls.Client(ctx, c)\n\t\t\t\t\t},\n\t\t\t\t\tDisableKeepAlives: true,\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn echClient.Do(request)\n\t\t},\n\t\t// H3 HTTPS\n\t\tfunc() (response *http.Response, err error) {\n\t\t\trequest := r.request.Clone(context.Background())\n\t\t\th3Client := &http.Client{\n\t\t\t\tTransport: &http3.Transport{\n\t\t\t\t\tTLSClientConfig: r.tls.Clone(),\n\t\t\t\t\tQUICConfig: &quic.Config{\n\t\t\t\t\t\tMaxIdleTimeout: time.Second,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t\treturn h3Client.Do(request)\n\t\t},\n\t}\n\n\tif r.request.URL.Scheme == \"http\" {\n\t\tfuncs = funcs[:1]\n\t}\n\n\tfor i, f := range funcs {\n\t\tgo func(f requestFunc) {\n\t\t\tdefer device.DeferPanicToError(\"http\", func(err error) { log.Println(err) })\n\t\t\tdefer func() {\n\t\t\t\tif successCount.Load() == 0 {\n\t\t\t\t\tif failedCount.Add(1) >= uint32(len(funcs)) {\n\t\t\t\t\t\t// 全部失败了\n\t\t\t\t\t\tcancel()\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\tvar t string\n\t\t\tswitch i {\n\t\t\tcase 0:\n\t\t\t\tt = \"http(s)\"\n\t\t\tcase 1:\n\t\t\t\tt = \"h3\"\n\t\t\t}\n\n\t\t\t// 执行HTTP请求\n\t\t\trsp, err := f()\n\t\t\tif rsp == nil || err != nil {\n\t\t\t\tmu.Lock()\n\t\t\t\tfinalErr = errors.Join(finalErr, fmt.Errorf(\"%s: %w\", t, err))\n\t\t\t\tmu.Unlock()\n\t\t\t\tif rsp != nil && rsp.Body != nil {\n\t\t\t\t\trsp.Body.Close()\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// 处理 HTTP 状态码\n\t\t\tif rsp.StatusCode != http.StatusOK {\n\t\t\t\thr := &httpResponse{Response: rsp}\n\t\t\t\terr = fmt.Errorf(\"%s: %s\", t, hr.errorString())\n\t\t\t\tmu.Lock()\n\t\t\t\tfinalErr = errors.Join(finalErr, err)\n\t\t\t\tmu.Unlock()\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tselect {\n\t\t\tcase successCh <- rsp:\n\t\t\t\t// 第一个成功的请求，不要关闭 body\n\t\t\t\tsuccessCount.Add(1)\n\t\t\tdefault:\n\t\t\t\trsp.Body.Close()\n\t\t\t}\n\t\t}(f)\n\t}\n\n\tselect {\n\tcase result := <-successCh:\n\t\treturn &httpResponse{Response: result}, nil\n\tcase <-ctx.Done():\n\t\treturn nil, finalErr\n\t}\n}\n\ntype httpResponse struct {\n\t*http.Response\n\n\tgetContentOnce sync.Once\n\tcontent        []byte\n\tcontentError   error\n}\n\nfunc (h *httpResponse) errorString() string {\n\tcontent, err := h.getContentString()\n\tif err != nil {\n\t\treturn fmt.Sprint(\"HTTP \", h.Status)\n\t}\n\tif len(content) > 100 {\n\t\tcontent = content[:100] + \" ...\"\n\t}\n\treturn fmt.Sprint(\"HTTP \", h.Status, \": \", content)\n}\n\nfunc (h *httpResponse) GetHeader(key string) *StringBox {\n\treturn wrapString(h.Header.Get(key))\n}\n\nfunc (h *httpResponse) GetContent() ([]byte, error) {\n\th.getContentOnce.Do(func() {\n\t\tdefer h.Body.Close()\n\t\th.content, h.contentError = io.ReadAll(h.Body)\n\t})\n\treturn h.content, h.contentError\n}\n\nfunc (h *httpResponse) GetContentString() (*StringBox, error) {\n\tcontent, err := h.getContentString()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn wrapString(content), nil\n}\n\nfunc (h *httpResponse) getContentString() (string, error) {\n\tcontent, err := h.GetContent()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn string(content), nil\n}\n\nfunc (h *httpResponse) WriteTo(path string) error {\n\tdefer h.Body.Close()\n\tfile, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer file.Close()\n\t_, err = io.Copy(file, h.Body)\n\treturn err\n}\n"
  },
  {
    "path": "libcore/init.sh",
    "content": "#!/bin/bash\n\nchmod -R 777 .build 2>/dev/null\nrm -rf .build 2>/dev/null\n\nif [ -z \"$GOPATH\" ]; then\n    GOPATH=$(go env GOPATH)\nfi\n\n# Install gomobile\nif [ ! -f \"$GOPATH/bin/gomobile-matsuri\" ]; then\n    git clone https://github.com/MatsuriDayo/gomobile.git\n    pushd gomobile\n\tgit checkout origin/master2\n    pushd cmd\n    pushd gomobile\n    go install -v\n    popd\n    pushd gobind\n    go install -v\n    popd\n    popd\n    rm -rf gomobile\n    mv \"$GOPATH/bin/gomobile\" \"$GOPATH/bin/gomobile-matsuri\"\n    mv \"$GOPATH/bin/gobind\" \"$GOPATH/bin/gobind-matsuri\"\nfi\n\nGOBIND=gobind-matsuri gomobile-matsuri init\n"
  },
  {
    "path": "libcore/interface_monitor.go",
    "content": "package libcore\n\nimport (\n\ttun \"github.com/sagernet/sing-tun\"\n\t\"github.com/sagernet/sing/common/control\"\n\t\"github.com/sagernet/sing/common/x/list\"\n)\n\n// wtf\n\ntype interfaceMonitorStub struct{}\n\nfunc (s *interfaceMonitorStub) Start() error {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) Close() error {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) DefaultInterface() *control.Interface {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) OverrideAndroidVPN() bool {\n\treturn false\n}\n\nfunc (s *interfaceMonitorStub) AndroidVPNEnabled() bool {\n\treturn false\n}\n\nfunc (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpdateCallback) *list.Element[tun.DefaultInterfaceUpdateCallback] {\n\treturn nil\n}\n\nfunc (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {\n}\n\nfunc (s *interfaceMonitorStub) RegisterMyInterface(interfaceName string) {\n}\n\nfunc (s *interfaceMonitorStub) MyInterface() string {\n\treturn \"\"\n}\n"
  },
  {
    "path": "libcore/io.go",
    "content": "package libcore\n\nimport (\n\t\"archive/zip\"\n\t\"io\"\n\t\"os\"\n        \"path/filepath\"\n\n\t\"github.com/ulikunitz/xz\"\n\t\"github.com/sagernet/sing/common\"\n        E \"github.com/sagernet/sing/common/exceptions\"\n)\n\nfunc Unxz(archive string, path string) error {\n\ti, err := os.Open(archive)\n\tif err != nil {\n\t\treturn err\n\t}\n\tr, err := xz.NewReader(i)\n\tif err != nil {\n\t\ti.Close()\n\t\treturn err\n\t}\n\to, err := os.Create(path)\n\tif err != nil {\n\t\ti.Close()\n\t\treturn err\n\t}\n\t_, err = io.Copy(o, r)\n\ti.Close()\n\treturn err\n}\n\nfunc Unzip(archive string, path string) error {\n\tr, err := zip.OpenReader(archive)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer r.Close()\n\n\terr = os.MkdirAll(path, os.ModePerm)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, file := range r.File {\n\t\tfilePath := filepath.Join(path, file.Name)\n\n\t\tif file.FileInfo().IsDir() {\n\t\t\terr = os.MkdirAll(filePath, os.ModePerm)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tnewFile, err := os.Create(filePath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tzipFile, err := file.Open()\n\t\tif err != nil {\n\t\t\tnewFile.Close()\n\t\t\treturn err\n\t\t}\n\n\t\tvar errs error\n\t\t_, err = io.Copy(newFile, zipFile)\n\t\terrs = E.Errors(errs, err)\n\t\terrs = E.Errors(errs, common.Close(zipFile, newFile))\n\t\tif errs != nil {\n\t\t\treturn errs\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "libcore/nb4a.go",
    "content": "package libcore\n\nimport (\n\t\"fmt\"\n\t\"libcore/device\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t_ \"unsafe\"\n\n\t\"log\"\n\n\t\"github.com/matsuridayo/libneko/neko_common\"\n\t\"github.com/matsuridayo/libneko/neko_log\"\n\t\"github.com/sagernet/sing-box/nekoutils\"\n\t\"github.com/sagernet/sing-box/option\"\n\t\"golang.org/x/sys/unix\"\n)\n\n//go:linkname resourcePaths github.com/sagernet/sing-box/constant.resourcePaths\nvar resourcePaths []string\n\nfunc NekoLogPrintln(s string) {\n\tlog.Println(s)\n}\n\nfunc NekoLogClear() {\n\tneko_log.LogWriter.Truncate()\n}\n\nfunc ForceGc() {\n\tgo debug.FreeOSMemory()\n}\n\nfunc InitCore(process, cachePath, internalAssets, externalAssets string,\n\tmaxLogSizeKb int32, logEnable bool,\n\tif1 NB4AInterface, if2 BoxPlatformInterface, if3 LocalDNSTransport,\n) {\n\tdefer device.DeferPanicToError(\"InitCore\", func(err error) { log.Println(err) })\n\tisBgProcess = strings.HasSuffix(process, \":bg\")\n\n\tneko_common.RunMode = neko_common.RunMode_NekoBoxForAndroid\n\tintfNB4A = if1\n\tintfBox = if2\n\tuseProcfs = intfBox.UseProcFS()\n\tgLocalDNSTransport = newPlatformTransport(if3, \"\", option.LocalDNSServerOptions{})\n\n\t// Working dir\n\ttmp := filepath.Join(cachePath, \"../no_backup\")\n\tos.MkdirAll(tmp, 0755)\n\tos.Chdir(tmp)\n\n\t// sing-box fs\n\tresourcePaths = append(resourcePaths, externalAssets)\n\texternalAssetsPath = externalAssets\n\tinternalAssetsPath = internalAssets\n\n\t// Set up log\n\tif maxLogSizeKb < 50 {\n\t\tmaxLogSizeKb = 50\n\t}\n\tneko_log.LogWriterDisable = !logEnable\n\tneko_log.TruncateOnStart = isBgProcess\n\tneko_log.SetupLog(int(maxLogSizeKb)*1024, filepath.Join(cachePath, \"neko.log\"))\n\n\t// nekoutils\n\tnekoutils.Selector_OnProxySelected = intfNB4A.Selector_OnProxySelected\n\n\t// Set up some component\n\tgo func() {\n\t\tdefer device.DeferPanicToError(\"InitCore-go\", func(err error) { log.Println(err) })\n\t\tdevice.GoDebug(process)\n\n\t\t// certs\n\t\tpem, err := os.ReadFile(externalAssetsPath + \"ca.pem\")\n\t\tif err == nil {\n\t\t\tupdateRootCACerts(pem)\n\t\t}\n\n\t\t// bg\n\t\tif isBgProcess {\n\t\t\textractAssets()\n\t\t}\n\t}()\n}\n\nfunc sendFdToProtect(fd int, path string) error {\n\tsocketFd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to create unix socket: %w\", err)\n\t}\n\tdefer unix.Close(socketFd)\n\n\tvar timeout unix.Timeval\n\ttimeout.Usec = 100 * 1000\n\n\t_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &timeout)\n\t_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_SNDTIMEO, &timeout)\n\n\terr = unix.Connect(socketFd, &unix.SockaddrUnix{Name: path})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to connect: %w\", err)\n\t}\n\n\terr = unix.Sendmsg(socketFd, nil, unix.UnixRights(fd), nil, 0)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to send: %w\", err)\n\t}\n\n\tdummy := []byte{1}\n\tn, err := unix.Read(socketFd, dummy)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to receive: %w\", err)\n\t}\n\tif n != 1 {\n\t\treturn fmt.Errorf(\"socket closed unexpectedly\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "libcore/platform_box.go",
    "content": "package libcore\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"libcore/procfs\"\n\t\"log\"\n\t\"net/netip\"\n\t\"strings\"\n\t\"syscall\"\n\n\t\"github.com/matsuridayo/libneko/neko_log\"\n\t\"github.com/sagernet/sing-box/adapter\"\n\t\"github.com/sagernet/sing-box/common/process\"\n\t\"github.com/sagernet/sing-box/experimental/libbox/platform\"\n\tsblog \"github.com/sagernet/sing-box/log\"\n\t\"github.com/sagernet/sing-box/option\"\n\ttun \"github.com/sagernet/sing-tun\"\n\tE \"github.com/sagernet/sing/common/exceptions\"\n\t\"github.com/sagernet/sing/common/logger\"\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar boxPlatformInterfaceInstance platform.Interface = &boxPlatformInterfaceWrapper{}\n\ntype boxPlatformInterfaceWrapper struct{}\n\nfunc (w *boxPlatformInterfaceWrapper) ReadWIFIState() adapter.WIFIState {\n\tstate := strings.Split(intfBox.WIFIState(), \",\")\n\treturn adapter.WIFIState{\n\t\tSSID:  state[0],\n\t\tBSSID: state[1],\n\t}\n}\n\nfunc (w *boxPlatformInterfaceWrapper) Initialize(n adapter.NetworkManager) error {\n\treturn nil\n}\n\nfunc (w *boxPlatformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool {\n\treturn true\n}\n\nfunc (w *boxPlatformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {\n\t// call protect_path\n\tif !isBgProcess {\n\t\t_ = sendFdToProtect(fd, \"protect_path\")\n\t\treturn nil\n\t}\n\t// bg process call VPNService\n\treturn intfBox.AutoDetectInterfaceControl(int32(fd))\n}\n\nfunc (w *boxPlatformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {\n\tif len(options.IncludeUID) > 0 || len(options.ExcludeUID) > 0 {\n\t\treturn nil, E.New(\"android: unsupported uid options\")\n\t}\n\tif len(options.IncludeAndroidUser) > 0 {\n\t\treturn nil, E.New(\"android: unsupported android_user option\")\n\t}\n\ta, _ := json.Marshal(options)\n\tb, _ := json.Marshal(platformOptions)\n\ttunFd, err := intfBox.OpenTun(string(a), string(b))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"intfBox.OpenTun: %v\", err)\n\t}\n\t// Do you want to close it?\n\ttunFd, err = syscall.Dup(tunFd)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"syscall.Dup: %v\", err)\n\t}\n\t//\n\toptions.FileDescriptor = int(tunFd)\n\treturn tun.New(*options)\n}\n\nfunc (w *boxPlatformInterfaceWrapper) CloseTun() error {\n\treturn nil\n}\n\nfunc (w *boxPlatformInterfaceWrapper) UsePlatformDefaultInterfaceMonitor() bool {\n\treturn true\n}\n\nfunc (w *boxPlatformInterfaceWrapper) CreateDefaultInterfaceMonitor(l logger.Logger) tun.DefaultInterfaceMonitor {\n\treturn &interfaceMonitorStub{}\n}\n\nfunc (w *boxPlatformInterfaceWrapper) UsePlatformInterfaceGetter() bool {\n\treturn false\n}\n\nfunc (w *boxPlatformInterfaceWrapper) Interfaces() ([]adapter.NetworkInterface, error) {\n\treturn nil, errors.New(\"wtf\")\n}\n\nfunc (w *boxPlatformInterfaceWrapper) IncludeAllNetworks() bool {\n\treturn false\n}\n\nfunc (w *boxPlatformInterfaceWrapper) SendNotification(notification *platform.Notification) error {\n\treturn nil\n}\n\nfunc (s *boxPlatformInterfaceWrapper) SystemCertificates() []string {\n\treturn nil\n}\n\n// Android not using\n\nfunc (w *boxPlatformInterfaceWrapper) UnderNetworkExtension() bool {\n\treturn false\n}\n\nfunc (w *boxPlatformInterfaceWrapper) ClearDNSCache() {\n}\n\n// process.Searcher\n\nfunc (w *boxPlatformInterfaceWrapper) FindProcessInfo(ctx context.Context, network string, source netip.AddrPort, destination netip.AddrPort) (*process.Info, error) {\n\tvar uid int32\n\tif useProcfs {\n\t\tuid = procfs.ResolveSocketByProcSearch(network, source, destination)\n\t\tif uid == -1 {\n\t\t\treturn nil, E.New(\"procfs: not found\")\n\t\t}\n\t} else {\n\t\tvar ipProtocol int32\n\t\tswitch N.NetworkName(network) {\n\t\tcase N.NetworkTCP:\n\t\t\tipProtocol = syscall.IPPROTO_TCP\n\t\tcase N.NetworkUDP:\n\t\t\tipProtocol = syscall.IPPROTO_UDP\n\t\tdefault:\n\t\t\treturn nil, E.New(\"unknown network: \", network)\n\t\t}\n\t\tvar err error\n\t\tuid, err = intfBox.FindConnectionOwner(ipProtocol, source.Addr().String(), int32(source.Port()), destination.Addr().String(), int32(destination.Port()))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tpackageName, _ := intfBox.PackageNameByUid(uid)\n\treturn &process.Info{UserId: uid, PackageName: packageName}, nil\n}\n\n// io.Writer\n\nvar disableSingBoxLog = false\n\nfunc (w *boxPlatformInterfaceWrapper) Write(p []byte) (n int, err error) {\n\t// use neko_log\n\tif !disableSingBoxLog {\n\t\tlog.Print(string(p))\n\t}\n\treturn len(p), nil\n}\n\n// 日志\n\ntype boxPlatformLogWriterWrapper struct {\n}\n\nvar boxPlatformLogWriter sblog.PlatformWriter = &boxPlatformLogWriterWrapper{}\n\nfunc (w *boxPlatformLogWriterWrapper) DisableColors() bool { return true }\n\nfunc (w *boxPlatformLogWriterWrapper) WriteMessage(level uint8, message string) {\n\tif !strings.HasSuffix(message, \"\\n\") {\n\t\tmessage += \"\\n\"\n\t}\n\tneko_log.LogWriter.Write([]byte(message))\n}\n"
  },
  {
    "path": "libcore/platform_java.go",
    "content": "package libcore\n\nvar intfBox BoxPlatformInterface\nvar intfNB4A NB4AInterface\n\nvar useProcfs bool\nvar isBgProcess bool\n\ntype NB4AInterface interface {\n\tUseOfficialAssets() bool\n\tSelector_OnProxySelected(selectorTag string, tag string)\n}\n\ntype BoxPlatformInterface interface {\n\tAutoDetectInterfaceControl(fd int32) error\n\tOpenTun(singTunOptionsJson, tunPlatformOptionsJson string) (int, error)\n\tUseProcFS() bool\n\tFindConnectionOwner(ipProtocol int32, sourceAddress string, sourcePort int32, destinationAddress string, destinationPort int32) (int32, error)\n\tPackageNameByUid(uid int32) (string, error)\n\tUIDByPackageName(packageName string) (int32, error)\n\tWIFIState() string\n}\n"
  },
  {
    "path": "libcore/procfs/procfs.go",
    "content": "package procfs\n\nimport (\n\t\"bufio\"\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"unsafe\"\n\n\tN \"github.com/sagernet/sing/common/network\"\n)\n\nvar (\n\tnetIndexOfLocal = -1\n\tnetIndexOfUid   = -1\n\tnativeEndian    binary.ByteOrder\n)\n\nfunc init() {\n\tvar x uint32 = 0x01020304\n\tif *(*byte)(unsafe.Pointer(&x)) == 0x01 {\n\t\tnativeEndian = binary.BigEndian\n\t} else {\n\t\tnativeEndian = binary.LittleEndian\n\t}\n}\n\nfunc ResolveSocketByProcSearch(network string, source, _ netip.AddrPort) int32 {\n\tif netIndexOfLocal < 0 || netIndexOfUid < 0 {\n\t\treturn -1\n\t}\n\n\tpath := \"/proc/net/\"\n\n\tif network == N.NetworkTCP {\n\t\tpath += \"tcp\"\n\t} else {\n\t\tpath += \"udp\"\n\t}\n\n\tif source.Addr().Is6() {\n\t\tpath += \"6\"\n\t}\n\n\tsIP := source.Addr().AsSlice()\n\tif len(sIP) == 0 {\n\t\treturn -1\n\t}\n\n\tvar bytes [2]byte\n\tbinary.BigEndian.PutUint16(bytes[:], source.Port())\n\tlocal := fmt.Sprintf(\"%s:%s\", hex.EncodeToString(nativeEndianIP(sIP)), hex.EncodeToString(bytes[:]))\n\n\tfile, err := os.Open(path)\n\tif err != nil {\n\t\treturn -1\n\t}\n\n\tdefer file.Close()\n\n\treader := bufio.NewReader(file)\n\n\tfor {\n\t\trow, _, err := reader.ReadLine()\n\t\tif err != nil {\n\t\t\treturn -1\n\t\t}\n\n\t\tfields := strings.Fields(string(row))\n\n\t\tif len(fields) <= netIndexOfLocal || len(fields) <= netIndexOfUid {\n\t\t\tcontinue\n\t\t}\n\n\t\tif strings.EqualFold(local, fields[netIndexOfLocal]) {\n\t\t\tuid, err := strconv.Atoi(fields[netIndexOfUid])\n\t\t\tif err != nil {\n\t\t\t\treturn -1\n\t\t\t}\n\n\t\t\treturn int32(uid)\n\t\t}\n\t}\n}\n\nfunc nativeEndianIP(ip net.IP) []byte {\n\tresult := make([]byte, len(ip))\n\n\tfor i := 0; i < len(ip); i += 4 {\n\t\tvalue := binary.BigEndian.Uint32(ip[i:])\n\n\t\tnativeEndian.PutUint32(result[i:], value)\n\t}\n\n\treturn result\n}\n\nfunc init() {\n\tfile, err := os.Open(\"/proc/net/tcp\")\n\tif err != nil {\n\t\treturn\n\t}\n\n\tdefer file.Close()\n\n\treader := bufio.NewReader(file)\n\n\theader, _, err := reader.ReadLine()\n\tif err != nil {\n\t\treturn\n\t}\n\n\tcolumns := strings.Fields(string(header))\n\n\tvar txQueue, rxQueue, tr, tmWhen bool\n\n\tfor idx, col := range columns {\n\t\toffset := 0\n\n\t\tif txQueue && rxQueue {\n\t\t\toffset--\n\t\t}\n\n\t\tif tr && tmWhen {\n\t\t\toffset--\n\t\t}\n\n\t\tswitch col {\n\t\tcase \"tx_queue\":\n\t\t\ttxQueue = true\n\t\tcase \"rx_queue\":\n\t\t\trxQueue = true\n\t\tcase \"tr\":\n\t\t\ttr = true\n\t\tcase \"tm->when\":\n\t\t\ttmWhen = true\n\t\tcase \"local_address\":\n\t\t\tnetIndexOfLocal = idx + offset\n\t\tcase \"uid\":\n\t\t\tnetIndexOfUid = idx + offset\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "libcore/stun/README",
    "content": "from  https://github.com/ccding/go-stun/commit/877ebaff7ba7b0f0ce44e92d785075647545e777\n"
  },
  {
    "path": "libcore/stun/attribute.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"encoding/binary\"\n\t\"hash/crc32\"\n\t\"net\"\n)\n\ntype attribute struct {\n\ttypes  uint16\n\tlength uint16\n\tvalue  []byte\n}\n\nfunc newAttribute(types uint16, value []byte) *attribute {\n\tatt := new(attribute)\n\tatt.types = types\n\tatt.value = padding(value)\n\tatt.length = uint16(len(att.value))\n\treturn att\n}\n\nfunc newFingerprintAttribute(packet *packet) *attribute {\n\tcrc := crc32.ChecksumIEEE(packet.bytes()) ^ fingerprint\n\tbuf := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(buf, crc)\n\treturn newAttribute(attributeFingerprint, buf)\n}\n\nfunc newSoftwareAttribute(name string) *attribute {\n\treturn newAttribute(attributeSoftware, []byte(name))\n}\n\nfunc newChangeReqAttribute(changeIP bool, changePort bool) *attribute {\n\tvalue := make([]byte, 4)\n\tif changeIP {\n\t\tvalue[3] |= 0x04\n\t}\n\tif changePort {\n\t\tvalue[3] |= 0x02\n\t}\n\treturn newAttribute(attributeChangeRequest, value)\n}\n\n//      0                   1                   2                   3\n//      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n//     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//     |x x x x x x x x|    Family     |         X-Port                |\n//     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//     |                X-Address (Variable)\n//     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\n//             Figure 6: Format of XOR-MAPPED-ADDRESS Attribute\nfunc (v *attribute) xorAddr(transID []byte) *Host {\n\txorIP := make([]byte, 16)\n\tfor i := 0; i < len(v.value)-4; i++ {\n\t\txorIP[i] = v.value[i+4] ^ transID[i]\n\t}\n\tfamily := uint16(v.value[1])\n\tport := binary.BigEndian.Uint16(v.value[2:4])\n\t// Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address.\n\tif family == attributeFamilyIPv4 {\n\t\txorIP = xorIP[:4]\n\t}\n\tx := binary.BigEndian.Uint16(transID[:2])\n\treturn &Host{family, net.IP(xorIP).String(), port ^ x}\n}\n\n//       0                   1                   2                   3\n//       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//      |0 0 0 0 0 0 0 0|    Family     |           Port                |\n//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//      |                                                               |\n//      |                 Address (32 bits or 128 bits)                 |\n//      |                                                               |\n//      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n//\n//               Figure 5: Format of MAPPED-ADDRESS Attribute\nfunc (v *attribute) rawAddr() *Host {\n\thost := new(Host)\n\thost.family = uint16(v.value[1])\n\thost.port = binary.BigEndian.Uint16(v.value[2:4])\n\t// Truncate if IPv4, otherwise net.IP sometimes renders it as an IPv6 address.\n\tif host.family == attributeFamilyIPv4 {\n\t\tv.value = v.value[:8]\n\t}\n\thost.ip = net.IP(v.value[4:]).String()\n\treturn host\n}\n"
  },
  {
    "path": "libcore/stun/client.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"errors\"\n\t\"net\"\n\t\"strconv\"\n)\n\n// Client is a STUN client, which can be set STUN server address and is used\n// to discover NAT type.\ntype Client struct {\n\tserverAddr   string\n\tsoftwareName string\n\tconn         net.PacketConn\n\tlogger       *Logger\n}\n\n// NewClient returns a client without network connection. The network\n// connection will be build when calling Discover function.\nfunc NewClient() *Client {\n\tc := new(Client)\n\tc.SetSoftwareName(DefaultSoftwareName)\n\tc.logger = NewLogger()\n\treturn c\n}\n\n// NewClientWithConnection returns a client which uses the given connection.\n// Please note the connection should be acquired via net.Listen* method.\nfunc NewClientWithConnection(conn net.PacketConn) *Client {\n\tc := new(Client)\n\tc.conn = conn\n\tc.SetSoftwareName(DefaultSoftwareName)\n\tc.logger = NewLogger()\n\treturn c\n}\n\n// SetVerbose sets the client to be in the verbose mode, which prints\n// information in the discover process.\nfunc (c *Client) SetVerbose(v bool) {\n\tc.logger.SetDebug(v)\n}\n\n// SetVVerbose sets the client to be in the double verbose mode, which prints\n// information and packet in the discover process.\nfunc (c *Client) SetVVerbose(v bool) {\n\tc.logger.SetInfo(v)\n}\n\n// SetServerHost allows user to set the STUN hostname and port.\nfunc (c *Client) SetServerHost(host string, port int) {\n\tc.serverAddr = net.JoinHostPort(host, strconv.Itoa(port))\n}\n\n// SetServerAddr allows user to set the transport layer STUN server address.\nfunc (c *Client) SetServerAddr(address string) {\n\tc.serverAddr = address\n}\n\n// SetSoftwareName allows user to set the name of the software, which is used\n// for logging purpose (NOT used in the current implementation).\nfunc (c *Client) SetSoftwareName(name string) {\n\tc.softwareName = name\n}\n\n// Discover contacts the STUN server and gets the response of NAT type, host\n// for UDP punching.\nfunc (c *Client) Discover() (NATType, *Host, error, bool) {\n\tif c.serverAddr == \"\" {\n\t\tc.SetServerAddr(DefaultServerAddr)\n\t}\n\tserverUDPAddr, err := net.ResolveUDPAddr(\"udp\", c.serverAddr)\n\tif err != nil {\n\t\treturn NATError, nil, err, false\n\t}\n\t// Use the connection passed to the client if it is not nil, otherwise\n\t// create a connection and close it at the end.\n\tconn := c.conn\n\tif conn == nil {\n\t\tconn, err = net.ListenUDP(\"udp\", nil)\n\t\tif err != nil {\n\t\t\treturn NATError, nil, err, false\n\t\t}\n\t\tdefer conn.Close()\n\t}\n\treturn c.discover(conn, serverUDPAddr)\n}\n\nfunc (c *Client) BehaviorTest() (*NATBehavior, error) {\n\tif c.serverAddr == \"\" {\n\t\tc.SetServerAddr(DefaultServerAddr)\n\t}\n\tserverUDPAddr, err := net.ResolveUDPAddr(\"udp\", c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// Use the connection passed to the client if it is not nil, otherwise\n\t// create a connection and close it at the end.\n\tconn := c.conn\n\tif conn == nil {\n\t\tconn, err = net.ListenUDP(\"udp\", nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer conn.Close()\n\t}\n\treturn c.behaviorTest(conn, serverUDPAddr)\n}\n\n// Keepalive sends and receives a bind request, which ensures the mapping stays open\n// Only applicable when client was created with a connection.\nfunc (c *Client) Keepalive() (*Host, error) {\n\tif c.conn == nil {\n\t\treturn nil, errors.New(\"no connection available\")\n\t}\n\tif c.serverAddr == \"\" {\n\t\tc.SetServerAddr(DefaultServerAddr)\n\t}\n\tserverUDPAddr, err := net.ResolveUDPAddr(\"udp\", c.serverAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresp, err := c.test1(c.conn, serverUDPAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp == nil || resp.packet == nil {\n\t\treturn nil, errors.New(\"failed to contact\")\n\t}\n\treturn resp.mappedAddr, nil\n}\n"
  },
  {
    "path": "libcore/stun/const.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\n// Default server address and client name.\nconst (\n\tDefaultServerAddr   = \"stun.ekiga.net:3478\"\n\tDefaultSoftwareName = \"StunClient\"\n)\n\nconst (\n\tmagicCookie = 0x2112A442\n\tfingerprint = 0x5354554e\n)\n\n// NATType is the type of NAT described by int.\ntype NATType int\n\n// NAT behavior type\ntype BehaviorType int\n\ntype NATBehavior struct {\n\tMappingType   BehaviorType\n\tFilteringType BehaviorType\n}\n\n// NAT types.\nconst (\n\tNATError NATType = iota\n\tNATUnknown\n\tNATNone\n\tNATBlocked\n\tNATFull\n\tNATSymmetric\n\tNATRestricted\n\tNATPortRestricted\n\tSymmetricUDPFirewall\n\n\t// Deprecated spellings of these constants\n\tNATSymetric             = NATSymmetric\n\tNATSymetricUDPFirewall  = SymmetricUDPFirewall\n\tNATSymmetricUDPFirewall = SymmetricUDPFirewall\n)\n\nconst (\n\tBehaviorTypeUnknown BehaviorType = iota\n\tBehaviorTypeEndpoint\n\tBehaviorTypeAddr\n\tBehaviorTypeAddrAndPort\n)\n\nvar natStr map[NATType]string\nvar natBehaviorTypeStr map[BehaviorType]string\nvar natNormalTypeStr map[NATBehavior]string\n\nfunc init() {\n\tnatStr = map[NATType]string{\n\t\tNATError:             \"Test failed\",\n\t\tNATUnknown:           \"Unexpected response from the STUN server\",\n\t\tNATBlocked:           \"UDP is blocked\",\n\t\tNATFull:              \"Full cone NAT\",\n\t\tNATSymmetric:         \"Symmetric NAT\",\n\t\tNATRestricted:        \"Restricted NAT\",\n\t\tNATPortRestricted:    \"Port restricted NAT\",\n\t\tNATNone:              \"Not behind a NAT\",\n\t\tSymmetricUDPFirewall: \"Symmetric UDP firewall\",\n\t}\n\n\tnatBehaviorTypeStr = map[BehaviorType]string{\n\t\tBehaviorTypeEndpoint:    \"EndpointIndependent\",\n\t\tBehaviorTypeAddr:        \"AddressDependent\",\n\t\tBehaviorTypeAddrAndPort: \"AddressAndPortDependent\",\n\t}\n\n\t// Defined in RFC 3489\n\tnatNormalTypeStr = map[NATBehavior]string{\n\t\tNATBehavior{BehaviorTypeEndpoint, BehaviorTypeEndpoint}:       \"Full cone NAT\",\n\t\tNATBehavior{BehaviorTypeEndpoint, BehaviorTypeAddr}:           \"Restricted cone NAT\",\n\t\tNATBehavior{BehaviorTypeEndpoint, BehaviorTypeAddrAndPort}:    \"Port Restricted cone NAT\",\n\t\tNATBehavior{BehaviorTypeAddrAndPort, BehaviorTypeAddrAndPort}: \"Symmetric NAT\",\n\t}\n}\n\nfunc (nat NATType) String() string {\n\tif s, ok := natStr[nat]; ok {\n\t\treturn s\n\t}\n\treturn \"Unknown\"\n}\n\nfunc (natBhType BehaviorType) String() string {\n\tif s, ok := natBehaviorTypeStr[natBhType]; ok {\n\t\treturn s\n\t}\n\treturn \"Unknown\"\n}\n\nfunc (natBehavior NATBehavior) NormalType() string {\n\tif s, ok := natNormalTypeStr[natBehavior]; ok {\n\t\treturn s\n\t}\n\treturn \"Undefined\"\n}\n\nconst (\n\terrorTryAlternate                 = 300\n\terrorBadRequest                   = 400\n\terrorUnauthorized                 = 401\n\terrorUnassigned402                = 402\n\terrorForbidden                    = 403\n\terrorUnknownAttribute             = 420\n\terrorAllocationMismatch           = 437\n\terrorStaleNonce                   = 438\n\terrorUnassigned439                = 439\n\terrorAddressFamilyNotSupported    = 440\n\terrorWrongCredentials             = 441\n\terrorUnsupportedTransportProtocol = 442\n\terrorPeerAddressFamilyMismatch    = 443\n\terrorConnectionAlreadyExists      = 446\n\terrorConnectionTimeoutOrFailure   = 447\n\terrorAllocationQuotaReached       = 486\n\terrorRoleConflict                 = 487\n\terrorServerError                  = 500\n\terrorInsufficientCapacity         = 508\n)\nconst (\n\tattributeFamilyIPv4 = 0x01\n\tattributeFamilyIPV6 = 0x02\n)\n\nconst (\n\tattributeMappedAddress          = 0x0001\n\tattributeResponseAddress        = 0x0002\n\tattributeChangeRequest          = 0x0003\n\tattributeSourceAddress          = 0x0004\n\tattributeChangedAddress         = 0x0005\n\tattributeUsername               = 0x0006\n\tattributePassword               = 0x0007\n\tattributeMessageIntegrity       = 0x0008\n\tattributeErrorCode              = 0x0009\n\tattributeUnknownAttributes      = 0x000a\n\tattributeReflectedFrom          = 0x000b\n\tattributeChannelNumber          = 0x000c\n\tattributeLifetime               = 0x000d\n\tattributeBandwidth              = 0x0010\n\tattributeXorPeerAddress         = 0x0012\n\tattributeData                   = 0x0013\n\tattributeRealm                  = 0x0014\n\tattributeNonce                  = 0x0015\n\tattributeXorRelayedAddress      = 0x0016\n\tattributeRequestedAddressFamily = 0x0017\n\tattributeEvenPort               = 0x0018\n\tattributeRequestedTransport     = 0x0019\n\tattributeDontFragment           = 0x001a\n\tattributeXorMappedAddress       = 0x0020\n\tattributeTimerVal               = 0x0021\n\tattributeReservationToken       = 0x0022\n\tattributePriority               = 0x0024\n\tattributeUseCandidate           = 0x0025\n\tattributePadding                = 0x0026\n\tattributeResponsePort           = 0x0027\n\tattributeConnectionID           = 0x002a\n\tattributeXorMappedAddressExp    = 0x8020\n\tattributeSoftware               = 0x8022\n\tattributeAlternateServer        = 0x8023\n\tattributeCacheTimeout           = 0x8027\n\tattributeFingerprint            = 0x8028\n\tattributeIceControlled          = 0x8029\n\tattributeIceControlling         = 0x802a\n\tattributeResponseOrigin         = 0x802b\n\tattributeOtherAddress           = 0x802c\n\tattributeEcnCheckStun           = 0x802d\n\tattributeCiscoFlowdata          = 0xc000\n)\n\nconst (\n\ttypeBindingRequest                 = 0x0001\n\ttypeBindingResponse                = 0x0101\n\ttypeBindingErrorResponse           = 0x0111\n\ttypeSharedSecretRequest            = 0x0002\n\ttypeSharedSecretResponse           = 0x0102\n\ttypeSharedErrorResponse            = 0x0112\n\ttypeAllocate                       = 0x0003\n\ttypeAllocateResponse               = 0x0103\n\ttypeAllocateErrorResponse          = 0x0113\n\ttypeRefresh                        = 0x0004\n\ttypeRefreshResponse                = 0x0104\n\ttypeRefreshErrorResponse           = 0x0114\n\ttypeSend                           = 0x0006\n\ttypeSendResponse                   = 0x0106\n\ttypeSendErrorResponse              = 0x0116\n\ttypeData                           = 0x0007\n\ttypeDataResponse                   = 0x0107\n\ttypeDataErrorResponse              = 0x0117\n\ttypeCreatePermisiion               = 0x0008\n\ttypeCreatePermisiionResponse       = 0x0108\n\ttypeCreatePermisiionErrorResponse  = 0x0118\n\ttypeChannelBinding                 = 0x0009\n\ttypeChannelBindingResponse         = 0x0109\n\ttypeChannelBindingErrorResponse    = 0x0119\n\ttypeConnect                        = 0x000a\n\ttypeConnectResponse                = 0x010a\n\ttypeConnectErrorResponse           = 0x011a\n\ttypeConnectionBind                 = 0x000b\n\ttypeConnectionBindResponse         = 0x010b\n\ttypeConnectionBindErrorResponse    = 0x011b\n\ttypeConnectionAttempt              = 0x000c\n\ttypeConnectionAttemptResponse      = 0x010c\n\ttypeConnectionAttemptErrorResponse = 0x011c\n)\n"
  },
  {
    "path": "libcore/stun/discover.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\n// Follow RFC 3489 and RFC 5389.\n// Figure 2: Flow for type discovery process (from RFC 3489).\n//                        +--------+\n//                        |  Test  |\n//                        |   I    |\n//                        +--------+\n//                             |\n//                             |\n//                             V\n//                            /\\              /\\\n//                         N /  \\ Y          /  \\ Y             +--------+\n//          UDP     <-------/Resp\\--------->/ IP \\------------->|  Test  |\n//          Blocked         \\ ?  /          \\Same/              |   II   |\n//                           \\  /            \\? /               +--------+\n//                            \\/              \\/                    |\n//                                             | N                  |\n//                                             |                    V\n//                                             V                    /\\\n//                                         +--------+  Sym.      N /  \\\n//                                         |  Test  |  UDP    <---/Resp\\\n//                                         |   II   |  Firewall   \\ ?  /\n//                                         +--------+              \\  /\n//                                             |                    \\/\n//                                             V                     |Y\n//                  /\\                         /\\                    |\n//   Symmetric  N  /  \\       +--------+   N  /  \\                   V\n//      NAT  <--- / IP \\<-----|  Test  |<--- /Resp\\               Open\n//                \\Same/      |   I    |     \\ ?  /               Internet\n//                 \\? /       +--------+      \\  /\n//                  \\/                         \\/\n//                  |Y                          |Y\n//                  |                           |\n//                  |                           V\n//                  |                           Full\n//                  |                           Cone\n//                  V              /\\\n//              +--------+        /  \\ Y\n//              |  Test  |------>/Resp\\---->Restricted\n//              |   III  |       \\ ?  /\n//              +--------+        \\  /\n//                                 \\/\n//                                  |N\n//                                  |       Port\n//                                  +------>Restricted\nfunc (c *Client) discover(conn net.PacketConn, addr *net.UDPAddr) (_ NATType, _ *Host, _ error, fakeFullCone bool) {\n\t// Perform test1 to check if it is under NAT.\n\tc.logger.Debugln(\"Do Test1\")\n\tc.logger.Debugln(\"Send To:\", addr)\n\tresp, err := c.test1(conn, addr)\n\tif err != nil {\n\t\treturn NATError, nil, err, fakeFullCone\n\t}\n\tc.logger.Debugln(\"Received:\", resp)\n\tif resp == nil {\n\t\treturn NATBlocked, nil, nil, fakeFullCone\n\t}\n\t// identical used to check if it is open Internet or not.\n\tidentical := resp.identical\n\t// changedAddr is used to perform second time test1 and test3.\n\tchangedAddr := resp.changedAddr\n\t// mappedAddr is used as the return value, its IP is used for tests\n\tmappedAddr := resp.mappedAddr\n\t// Make sure IP and port are not changed.\n\tif resp.serverAddr.IP() != addr.IP.String() ||\n\t\tresp.serverAddr.Port() != uint16(addr.Port) {\n\t\tfakeFullCone = true\n\t}\n\t// if changedAddr is not available, use otherAddr as changedAddr,\n\t// which is updated in RFC 5780\n\tif changedAddr == nil {\n\t\tchangedAddr = resp.otherAddr\n\t}\n\t// changedAddr shall not be nil\n\tif changedAddr == nil {\n\t\treturn NATError, mappedAddr, errors.New(\"Server error: no changed address.\"), fakeFullCone\n\t}\n\t// Perform test2 to see if the client can receive packet sent from\n\t// another IP and port.\n\tc.logger.Debugln(\"Do Test2\")\n\tc.logger.Debugln(\"Send To:\", addr)\n\tresp, err = c.test2(conn, addr)\n\tif err != nil {\n\t\treturn NATError, mappedAddr, err, fakeFullCone\n\t}\n\tc.logger.Debugln(\"Received:\", resp)\n\t// Make sure IP and port are changed.\n\tif resp != nil &&\n\t\t(resp.serverAddr.IP() == addr.IP.String() ||\n\t\t\tresp.serverAddr.Port() == uint16(addr.Port)) {\n\t\tfakeFullCone = true\n\t}\n\tif identical {\n\t\tif resp == nil {\n\t\t\treturn SymmetricUDPFirewall, mappedAddr, nil, fakeFullCone\n\t\t}\n\t\treturn NATNone, mappedAddr, nil, fakeFullCone\n\t}\n\tif resp != nil {\n\t\treturn NATFull, mappedAddr, nil, fakeFullCone\n\t}\n\t// Perform test1 to another IP and port to see if the NAT use the same\n\t// external IP.\n\tc.logger.Debugln(\"Do Test1\")\n\tc.logger.Debugln(\"Send To:\", changedAddr)\n\tcaddr, err := net.ResolveUDPAddr(\"udp\", changedAddr.String())\n\tif err != nil {\n\t\tc.logger.Debugf(\"ResolveUDPAddr error: %v\", err)\n\t}\n\tresp, err = c.test1(conn, caddr)\n\tif err != nil {\n\t\treturn NATError, mappedAddr, err, fakeFullCone\n\t}\n\tc.logger.Debugln(\"Received:\", resp)\n\tif resp == nil {\n\t\t// It should be NAT_BLOCKED, but will be detected in the first\n\t\t// step. So this will never happen.\n\t\treturn NATUnknown, mappedAddr, nil, fakeFullCone\n\t}\n\t// Make sure IP/port is not changed.\n\tif resp.serverAddr.IP() != caddr.IP.String() ||\n\t\tresp.serverAddr.Port() != uint16(caddr.Port) {\n\t\tfakeFullCone = true\n\t}\n\tif mappedAddr.IP() == resp.mappedAddr.IP() && mappedAddr.Port() == resp.mappedAddr.Port() {\n\t\t// Perform test3 to see if the client can receive packet sent\n\t\t// from another port.\n\t\tc.logger.Debugln(\"Do Test3\")\n\t\tc.logger.Debugln(\"Send To:\", caddr)\n\t\tresp, err = c.test3(conn, caddr)\n\t\tif err != nil {\n\t\t\treturn NATError, mappedAddr, err, fakeFullCone\n\t\t}\n\t\tc.logger.Debugln(\"Received:\", resp)\n\t\tif resp == nil {\n\t\t\treturn NATPortRestricted, mappedAddr, nil, fakeFullCone\n\t\t}\n\t\t// Make sure IP is not changed, and port is changed.\n\t\tif resp.serverAddr.IP() != caddr.IP.String() ||\n\t\t\tresp.serverAddr.Port() == uint16(caddr.Port) {\n\t\t\tfakeFullCone = true\n\t\t}\n\t\treturn NATRestricted, mappedAddr, nil, fakeFullCone\n\t}\n\treturn NATSymmetric, mappedAddr, nil, fakeFullCone\n}\n\nfunc (c *Client) behaviorTest(conn net.PacketConn, addr *net.UDPAddr) (*NATBehavior, error) {\n\tnatBehavior := &NATBehavior{}\n\n\t// Test1   ->(IP1,port1)\n\t// Perform test to check if it is under NAT.\n\tc.logger.Debugln(\"Do Test1\")\n\tresp1, err := c.test(conn, addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// identical used to check if it is open Internet or not.\n\tif resp1.identical {\n\t\treturn nil, errors.New(\"Not behind a NAT.\")\n\t}\n\t// use otherAddr or changedAddr\n\totherAddr := resp1.otherAddr\n\tif otherAddr == nil {\n\t\tif resp1.changedAddr != nil {\n\t\t\totherAddr = resp1.changedAddr\n\t\t} else {\n\t\t\treturn nil, errors.New(\"Server error: no other address and changed address.\")\n\t\t}\n\t}\n\n\t// Test2   ->(IP2,port1)\n\t// Perform test to see if mapping to the same IP and port when\n\t// send to another IP.\n\tc.logger.Debugln(\"Do Test2\")\n\ttmpAddr := &net.UDPAddr{IP: net.ParseIP(otherAddr.IP()), Port: addr.Port}\n\tresp2, err := c.test(conn, tmpAddr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif resp2.mappedAddr.IP() == resp1.mappedAddr.IP() &&\n\t\tresp2.mappedAddr.Port() == resp1.mappedAddr.Port() {\n\t\tnatBehavior.MappingType = BehaviorTypeEndpoint\n\t}\n\n\t// Test3   ->(IP2,port2)\n\t// Perform test to see if mapping to the same IP and port when\n\t// send to another port.\n\tif natBehavior.MappingType == BehaviorTypeUnknown {\n\t\tc.logger.Debugln(\"Do Test3\")\n\t\ttmpAddr.Port = int(otherAddr.Port())\n\t\tresp3, err := c.test(conn, tmpAddr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif resp3.mappedAddr.IP() == resp2.mappedAddr.IP() &&\n\t\t\tresp3.mappedAddr.Port() == resp2.mappedAddr.Port() {\n\t\t\tnatBehavior.MappingType = BehaviorTypeAddr\n\t\t} else {\n\t\t\tnatBehavior.MappingType = BehaviorTypeAddrAndPort\n\t\t}\n\t}\n\n\t// Test4   ->(IP1,port1)   (IP2,port2)->\n\t// Perform test to see if the client can receive packet sent from\n\t// another IP and port.\n\tc.logger.Debugln(\"Do Test4\")\n\tresp4, err := c.testChangeBoth(conn, addr)\n\tif err != nil {\n\t\treturn natBehavior, err\n\t}\n\tif resp4 != nil {\n\t\tnatBehavior.FilteringType = BehaviorTypeEndpoint\n\t}\n\n\t// Test5   ->(IP1,port1)   (IP1,port2)->\n\t// Perform test to see if the client can receive packet sent from\n\t// another port.\n\tif natBehavior.FilteringType == BehaviorTypeUnknown {\n\t\tc.logger.Debugln(\"Do Test5\")\n\t\tresp5, err := c.testChangePort(conn, addr)\n\t\tif err != nil {\n\t\t\treturn natBehavior, err\n\t\t}\n\t\tif resp5 != nil {\n\t\t\tnatBehavior.FilteringType = BehaviorTypeAddr\n\t\t} else {\n\t\t\tnatBehavior.FilteringType = BehaviorTypeAddrAndPort\n\t\t}\n\t}\n\n\treturn natBehavior, nil\n}\n"
  },
  {
    "path": "libcore/stun/doc.go",
    "content": "// Copyright 2016 Cong Ding\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// Package stun is a STUN (RFC 3489 and RFC 5389) client implementation in\n// golang.\n//\n// It is extremely easy to use -- just one line of code.\n//\n// \tnat, host, err := stun.NewClient().Discover()\n//\n// More details please go to `main.go`.\npackage stun\n"
  },
  {
    "path": "libcore/stun/host.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"net\"\n\t\"strconv\"\n)\n\n// Host defines the network address including address family, IP address and port.\ntype Host struct {\n\tfamily uint16\n\tip     string\n\tport   uint16\n}\n\nfunc newHostFromStr(s string) *Host {\n\tudpAddr, err := net.ResolveUDPAddr(\"udp\", s)\n\tif err != nil {\n\t\treturn nil\n\t}\n\thost := new(Host)\n\tif udpAddr.IP.To4() != nil {\n\t\thost.family = attributeFamilyIPv4\n\t} else {\n\t\thost.family = attributeFamilyIPV6\n\t}\n\thost.ip = udpAddr.IP.String()\n\thost.port = uint16(udpAddr.Port)\n\treturn host\n}\n\n// Family returns the family type of a host (IPv4 or IPv6).\nfunc (h *Host) Family() uint16 {\n\treturn h.family\n}\n\n// IP returns the internet protocol address of the host.\nfunc (h *Host) IP() string {\n\treturn h.ip\n}\n\n// Port returns the port number of the host.\nfunc (h *Host) Port() uint16 {\n\treturn h.port\n}\n\n// TransportAddr returns the transport layer address of the host.\nfunc (h *Host) TransportAddr() string {\n\treturn net.JoinHostPort(h.ip, strconv.Itoa(int(h.port)))\n}\n\n// String returns the string representation of the host address.\nfunc (h *Host) String() string {\n\treturn h.TransportAddr()\n}\n"
  },
  {
    "path": "libcore/stun/log.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"log\"\n\t\"os\"\n)\n\n// Logger is a simple logger specified for this STUN client.\ntype Logger struct {\n\tlog.Logger\n\tdebug bool\n\tinfo  bool\n}\n\n// NewLogger creates a default logger.\nfunc NewLogger() *Logger {\n\tlogger := &Logger{*log.New(os.Stdout, \"\", log.LstdFlags), false, false}\n\treturn logger\n}\n\n// SetDebug sets the logger running in debug mode or not.\nfunc (l *Logger) SetDebug(v bool) {\n\tl.debug = v\n}\n\n// SetInfo sets the logger running in info mode or not.\nfunc (l *Logger) SetInfo(v bool) {\n\tl.info = v\n}\n\n// Debug outputs the log in the format of log.Print.\nfunc (l *Logger) Debug(v ...interface{}) {\n\tif l.debug {\n\t\tl.Print(v...)\n\t}\n}\n\n// Debugf outputs the log in the format of log.Printf.\nfunc (l *Logger) Debugf(format string, v ...interface{}) {\n\tif l.debug {\n\t\tl.Printf(format, v...)\n\t}\n}\n\n// Debugln outputs the log in the format of log.Println.\nfunc (l *Logger) Debugln(v ...interface{}) {\n\tif l.debug {\n\t\tl.Println(v...)\n\t}\n}\n\n// Info outputs the log in the format of log.Print.\nfunc (l *Logger) Info(v ...interface{}) {\n\tif l.info {\n\t\tl.Print(v...)\n\t}\n}\n\n// Infof outputs the log in the format of log.Printf.\nfunc (l *Logger) Infof(format string, v ...interface{}) {\n\tif l.info {\n\t\tl.Printf(format, v...)\n\t}\n}\n\n// Infoln outputs the log in the format of log.Println.\nfunc (l *Logger) Infoln(v ...interface{}) {\n\tif l.info {\n\t\tl.Println(v...)\n\t}\n}\n"
  },
  {
    "path": "libcore/stun/net.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"time\"\n)\n\nconst (\n\tnumRetransmit  = 9\n\tdefaultTimeout = 100\n\tmaxTimeout     = 1600\n\tmaxPacketSize  = 1024\n)\n\nfunc (c *Client) sendBindingReq(conn net.PacketConn, addr net.Addr, changeIP bool, changePort bool) (*response, error) {\n\t// Construct packet.\n\tpkt, err := newPacket()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tpkt.types = typeBindingRequest\n\tattribute := newSoftwareAttribute(c.softwareName)\n\tpkt.addAttribute(*attribute)\n\tif changeIP || changePort {\n\t\tattribute = newChangeReqAttribute(changeIP, changePort)\n\t\tpkt.addAttribute(*attribute)\n\t}\n\t// length of fingerprint attribute must be included into crc,\n\t// so we add it before calculating crc, then subtract it after calculating crc.\n\tpkt.length += 8\n\tattribute = newFingerprintAttribute(pkt)\n\tpkt.length -= 8\n\tpkt.addAttribute(*attribute)\n\t// Send packet.\n\treturn c.send(pkt, conn, addr)\n}\n\n// RFC 3489: Clients SHOULD retransmit the request starting with an interval\n// of 100ms, doubling every retransmit until the interval reaches 1.6s.\n// Retransmissions continue with intervals of 1.6s until a response is\n// received, or a total of 9 requests have been sent.\nfunc (c *Client) send(pkt *packet, conn net.PacketConn, addr net.Addr) (*response, error) {\n\tc.logger.Info(\"\\n\" + hex.Dump(pkt.bytes()))\n\ttimeout := defaultTimeout\n\tpacketBytes := make([]byte, maxPacketSize)\n\tfor i := 0; i < numRetransmit; i++ {\n\t\t// Send packet to the server.\n\t\tlength, err := conn.WriteTo(pkt.bytes(), addr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif length != len(pkt.bytes()) {\n\t\t\treturn nil, errors.New(\"Error in sending data.\")\n\t\t}\n\t\terr = conn.SetReadDeadline(time.Now().Add(time.Duration(timeout) * time.Millisecond))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif timeout < maxTimeout {\n\t\t\ttimeout *= 2\n\t\t}\n\t\tfor {\n\t\t\t// Read from the port.\n\t\t\tlength, raddr, err := conn.ReadFrom(packetBytes)\n\t\t\tif err != nil {\n\t\t\t\tif nerr, ok := err.(net.Error); ok && nerr.Timeout() {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tp, err := newPacketFromBytes(packetBytes[0:length])\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// If transId mismatches, keep reading until get a\n\t\t\t// matched packet or timeout.\n\t\t\tif !bytes.Equal(pkt.transID, p.transID) {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tc.logger.Info(\"\\n\" + hex.Dump(packetBytes[0:length]))\n\t\t\tresp := newResponse(p, conn)\n\t\t\tresp.serverAddr = newHostFromStr(raddr.String())\n\t\t\treturn resp, err\n\t\t}\n\t}\n\treturn nil, nil\n}\n"
  },
  {
    "path": "libcore/stun/packet.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"crypto/rand\"\n\t\"encoding/binary\"\n\t\"errors\"\n\t\"math\"\n)\n\ntype packet struct {\n\ttypes      uint16\n\tlength     uint16\n\ttransID    []byte // 4 bytes magic cookie + 12 bytes transaction id\n\tattributes []attribute\n}\n\nfunc newPacket() (*packet, error) {\n\tv := new(packet)\n\tv.transID = make([]byte, 16)\n\tbinary.BigEndian.PutUint32(v.transID[:4], magicCookie)\n\t_, err := rand.Read(v.transID[4:])\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tv.attributes = make([]attribute, 0, 10)\n\tv.length = 0\n\treturn v, nil\n}\n\nfunc newPacketFromBytes(packetBytes []byte) (*packet, error) {\n\tif len(packetBytes) < 20 {\n\t\treturn nil, errors.New(\"Received data length too short.\")\n\t}\n\tif len(packetBytes) > math.MaxUint16+20 {\n\t\treturn nil, errors.New(\"Received data length too long.\")\n\t}\n\tpkt := new(packet)\n\tpkt.types = binary.BigEndian.Uint16(packetBytes[0:2])\n\tpkt.length = binary.BigEndian.Uint16(packetBytes[2:4])\n\tpkt.transID = packetBytes[4:20]\n\tpkt.attributes = make([]attribute, 0, 10)\n\tpacketBytes = packetBytes[20:]\n\tfor pos := uint16(0); pos+4 < uint16(len(packetBytes)); {\n\t\ttypes := binary.BigEndian.Uint16(packetBytes[pos : pos+2])\n\t\tlength := binary.BigEndian.Uint16(packetBytes[pos+2 : pos+4])\n\t\tend := pos + 4 + length\n\t\tif end < pos+4 || end > uint16(len(packetBytes)) {\n\t\t\treturn nil, errors.New(\"Received data format mismatch.\")\n\t\t}\n\t\tvalue := packetBytes[pos+4 : end]\n\t\tattribute := newAttribute(types, value)\n\t\tpkt.addAttribute(*attribute)\n\t\tpos += align(length) + 4\n\t}\n\treturn pkt, nil\n}\n\nfunc (v *packet) addAttribute(a attribute) {\n\tv.attributes = append(v.attributes, a)\n\tv.length += align(a.length) + 4\n}\n\nfunc (v *packet) bytes() []byte {\n\tpacketBytes := make([]byte, 4)\n\tbinary.BigEndian.PutUint16(packetBytes[0:2], v.types)\n\tbinary.BigEndian.PutUint16(packetBytes[2:4], v.length)\n\tpacketBytes = append(packetBytes, v.transID...)\n\tfor _, a := range v.attributes {\n\t\tbuf := make([]byte, 2)\n\t\tbinary.BigEndian.PutUint16(buf, a.types)\n\t\tpacketBytes = append(packetBytes, buf...)\n\t\tbinary.BigEndian.PutUint16(buf, a.length)\n\t\tpacketBytes = append(packetBytes, buf...)\n\t\tpacketBytes = append(packetBytes, a.value...)\n\t}\n\treturn packetBytes\n}\n\nfunc (v *packet) getSourceAddr() *Host {\n\treturn v.getRawAddr(attributeSourceAddress)\n}\n\nfunc (v *packet) getMappedAddr() *Host {\n\treturn v.getRawAddr(attributeMappedAddress)\n}\n\nfunc (v *packet) getChangedAddr() *Host {\n\treturn v.getRawAddr(attributeChangedAddress)\n}\n\nfunc (v *packet) getOtherAddr() *Host {\n\treturn v.getRawAddr(attributeOtherAddress)\n}\n\nfunc (v *packet) getRawAddr(attribute uint16) *Host {\n\tfor _, a := range v.attributes {\n\t\tif a.types == attribute {\n\t\t\treturn a.rawAddr()\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (v *packet) getXorMappedAddr() *Host {\n\taddr := v.getXorAddr(attributeXorMappedAddress)\n\tif addr == nil {\n\t\taddr = v.getXorAddr(attributeXorMappedAddressExp)\n\t}\n\treturn addr\n}\n\nfunc (v *packet) getXorAddr(attribute uint16) *Host {\n\tfor _, a := range v.attributes {\n\t\tif a.types == attribute {\n\t\t\treturn a.xorAddr(v.transID)\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "libcore/stun/response.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"fmt\"\n\t\"net\"\n)\n\ntype response struct {\n\tpacket      *packet // the original packet from the server\n\tserverAddr  *Host   // the address received packet\n\tchangedAddr *Host   // parsed from packet\n\tmappedAddr  *Host   // parsed from packet, external addr of client NAT\n\totherAddr   *Host   // parsed from packet, to replace changedAddr in RFC 5780\n\tidentical   bool    // if mappedAddr is in local addr list\n}\n\nfunc newResponse(pkt *packet, conn net.PacketConn) *response {\n\tresp := &response{pkt, nil, nil, nil, nil, false}\n\tif pkt == nil {\n\t\treturn resp\n\t}\n\t// RFC 3489 doesn't require the server return XOR mapped address.\n\tmappedAddr := pkt.getXorMappedAddr()\n\tif mappedAddr == nil {\n\t\tmappedAddr = pkt.getMappedAddr()\n\t}\n\tresp.mappedAddr = mappedAddr\n\t// compute identical\n\tlocalAddrStr := conn.LocalAddr().String()\n\tif mappedAddr != nil {\n\t\tmappedAddrStr := mappedAddr.String()\n\t\tresp.identical = isLocalAddress(localAddrStr, mappedAddrStr)\n\t}\n\t// compute changedAddr\n\tchangedAddr := pkt.getChangedAddr()\n\tif changedAddr != nil {\n\t\tchangedAddrHost := newHostFromStr(changedAddr.String())\n\t\tresp.changedAddr = changedAddrHost\n\t}\n\t// compute otherAddr\n\totherAddr := pkt.getOtherAddr()\n\tif otherAddr != nil {\n\t\totherAddrHost := newHostFromStr(otherAddr.String())\n\t\tresp.otherAddr = otherAddrHost\n\t}\n\n\treturn resp\n}\n\n// String is only used for verbose mode output.\nfunc (r *response) String() string {\n\tif r == nil {\n\t\treturn \"Nil\"\n\t}\n\treturn fmt.Sprintf(\"{packet nil: %v, local: %v, remote: %v, changed: %v, other: %v, identical: %v}\",\n\t\tr.packet == nil,\n\t\tr.mappedAddr,\n\t\tr.serverAddr,\n\t\tr.changedAddr,\n\t\tr.otherAddr,\n\t\tr.identical)\n}\n"
  },
  {
    "path": "libcore/stun/tests.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"errors\"\n\t\"net\"\n)\n\nfunc (c *Client) sendWithLog(conn net.PacketConn, addr *net.UDPAddr, changeIP bool, changePort bool) (*response, error) {\n\tc.logger.Debugln(\"Send To:\", addr)\n\tresp, err := c.sendBindingReq(conn, addr, changeIP, changePort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.logger.Debugln(\"Received:\", resp)\n\tif resp == nil && changeIP == false && changePort == false {\n\t\treturn nil, errors.New(\"NAT blocked.\")\n\t}\n\tif resp != nil && !addrCompare(resp.serverAddr, addr, changeIP, changePort) {\n\t\treturn nil, errors.New(\"Server error: response IP/port\")\n\t}\n\treturn resp, err\n}\n\n// Make sure IP and port  have or haven't change\nfunc addrCompare(host *Host, addr *net.UDPAddr, IPChange, portChange bool) bool {\n\tisIPChange := host.IP() != addr.IP.String()\n\tisPortChange := host.Port() != uint16(addr.Port)\n\treturn isIPChange == IPChange && isPortChange == portChange\n}\n\nfunc (c *Client) test(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {\n\treturn c.sendWithLog(conn, addr, false, false)\n}\n\nfunc (c *Client) testChangePort(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {\n\treturn c.sendWithLog(conn, addr, false, true)\n}\n\nfunc (c *Client) testChangeBoth(conn net.PacketConn, addr *net.UDPAddr) (*response, error) {\n\treturn c.sendWithLog(conn, addr, true, true)\n}\n\nfunc (c *Client) test1(conn net.PacketConn, addr net.Addr) (*response, error) {\n\treturn c.sendBindingReq(conn, addr, false, false)\n}\n\nfunc (c *Client) test2(conn net.PacketConn, addr net.Addr) (*response, error) {\n\treturn c.sendBindingReq(conn, addr, true, true)\n}\n\nfunc (c *Client) test3(conn net.PacketConn, addr net.Addr) (*response, error) {\n\treturn c.sendBindingReq(conn, addr, false, true)\n}\n"
  },
  {
    "path": "libcore/stun/utils.go",
    "content": "// Copyright 2016 Cong Ding\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\npackage stun\n\nimport (\n\t\"net\"\n)\n\n// Padding the length of the byte slice to multiple of 4.\nfunc padding(bytes []byte) []byte {\n\tlength := uint16(len(bytes))\n\treturn append(bytes, make([]byte, align(length)-length)...)\n}\n\n// Align the uint16 number to the smallest multiple of 4, which is larger than\n// or equal to the uint16 number.\nfunc align(n uint16) uint16 {\n\treturn (n + 3) & 0xfffc\n}\n\n// isLocalAddress check if localRemote is a local address.\nfunc isLocalAddress(local, localRemote string) bool {\n\t// Resolve the IP returned by the STUN server first.\n\tlocalRemoteAddr, err := net.ResolveUDPAddr(\"udp\", localRemote)\n\tif err != nil {\n\t\treturn false\n\t}\n\t// Try comparing with the local address on the socket first, but only if\n\t// it's actually specified.\n\taddr, err := net.ResolveUDPAddr(\"udp\", local)\n\tif err == nil && addr.IP != nil && !addr.IP.IsUnspecified() {\n\t\treturn addr.IP.Equal(localRemoteAddr.IP)\n\t}\n\t// Fallback to checking IPs of all interfaces\n\taddrs, err := net.InterfaceAddrs()\n\tif err != nil {\n\t\treturn false\n\t}\n\tfor _, addr := range addrs {\n\t\tip, _, err := net.ParseCIDR(addr.String())\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif ip.Equal(localRemoteAddr.IP) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "libcore/stun.go",
    "content": "package libcore\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"libcore/stun\"\n)\n\ntype StunResult struct {\n\tText    string\n\tSuccess bool\n}\n\nfunc StunTest(server string) *StunResult {\n\t//note: this library doesn't support stun1.l.google.com:19302\n\tret := &StunResult{}\n\tvar text string\n\n\t// Old NAT Type Test\n\tclient := stun.NewClient()\n\tclient.SetServerAddr(server)\n\tnat, host, err, fakeFullCone := client.Discover()\n\tif err != nil {\n\t\ttext += fmt.Sprintln(\"Discover Error:\", err.Error())\n\t}\n\n\tif fakeFullCone {\n\t\ttext += fmt.Sprintln(\"Fake fullcone (no endpoint IP change) detected!!\")\n\t}\n\n\tif host != nil {\n\t\ttext += fmt.Sprintln(\"NAT Type:\", nat)\n\t\ttext += fmt.Sprintln(\"External IP Family:\", host.Family())\n\t\ttext += fmt.Sprintln(\"External IP:\", host.IP())\n\t\ttext += fmt.Sprintln(\"External Port:\", host.Port())\n\t}\n\n\t// New NAT Test\n\n\tnatBehavior, err := client.BehaviorTest()\n\tif err != nil {\n\t\ttext += fmt.Sprintln(\"BehaviorTest Error:\", err.Error())\n\t}\n\n\tif natBehavior != nil {\n\t\ttext += fmt.Sprintln(\"Mapping Behavior:\", natBehavior.MappingType)\n\t\ttext += fmt.Sprintln(\"Filtering Behavior:\", natBehavior.FilteringType)\n\t\ttext += fmt.Sprintln(\"Normal NAT Type:\", natBehavior.NormalType())\n\t}\n\n\tret.Success = true\n\tret.Text = strings.TrimRight(text, \"\\n\")\n\treturn ret\n}\n"
  },
  {
    "path": "lint.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<lint>\n    <issue id=\"UnusedAttribute\" severity=\"ignore\" />\n    <issue id=\"UnknownNullness\" severity=\"ignore\" />\n    <issue id=\"DiscouragedPrivateApi\" severity=\"ignore\" />\n    <issue id=\"Typos\" severity=\"ignore\" />\n    <issue id=\"RtlSymmetry\" severity=\"ignore\" />\n    <issue id=\"ContentDescription\" severity=\"ignore\" />\n    <issue id=\"UnusedResources\" severity=\"ignore\" />\n    <issue id=\"VectorRaster\" severity=\"ignore\" />\n    <issue id=\"PrivateResource\" severity=\"ignore\" />\n\n    <issue id=\"Untranslatable\" severity=\"ignore\" />\n    <issue id=\"PluralsCandidate\" severity=\"ignore\" />\n    <issue id=\"TypographyQuotes\" severity=\"ignore\" />\n    <issue id=\"DuplicateStrings\" severity=\"ignore\" />\n    <issue id=\"ImpliedQuantity\" severity=\"ignore\" />\n    <issue id=\"MissingQuantity\" severity=\"ignore\" />\n\n    <issue id=\"SelectableText\" severity=\"ignore\" />\n    <issue id=\"InsecureBaseConfiguration\" severity=\"ignore\" />\n    <issue id=\"NotifyDataSetChanged\" severity=\"ignore\" />\n    <issue id=\"UselessParent\" severity=\"ignore\" />\n    <issue id=\"SyntheticAccessor\" severity=\"ignore\" />\n    <issue id=\"UselessLeaf\" severity=\"ignore\" />\n    <issue id=\"UnusedIds\" severity=\"ignore\" />\n    <issue id=\"ConvertToWebp\" severity=\"ignore\" />\n    <issue id=\"SetTextI18n\" severity=\"ignore\" />\n    <issue id=\"HardcodedText\" severity=\"ignore\" />\n    <issue id=\"RtlHardcoded\" severity=\"ignore\" />\n    <issue id=\"MissingTranslation\" severity=\"ignore\" />\n    <issue id=\"ExtraTranslation\" severity=\"ignore\" />\n    <issue id=\"RestrictedApi\" severity=\"ignore\" />\n\n    <issue id=\"InvalidPackage\" severity=\"ignore\" />\n    <issue id=\"TrustAllX509TrustManager\" severity=\"ignore\" />\n</lint>\n"
  },
  {
    "path": "nb4a.properties",
    "content": "PACKAGE_NAME=moe.nb4a\nVERSION_NAME=1.4.2\nPRE_VERSION_NAME=pre-1.4.2-20260202-1\nVERSION_CODE=46\n"
  },
  {
    "path": "repositories.gradle.kts",
    "content": "repositories {\n    google()\n    mavenCentral()\n    gradlePluginPortal()\n    maven(url = \"https://jitpack.io\")\n}"
  },
  {
    "path": "run",
    "content": "#!/bin/bash\n\nEXEC=\"\"\nTARGET=\"buildScript\"\ndeclare -A PARAMS\ndeclare -i INDEX=0\n\nfor e in $@; do\n  TARGET=\"$TARGET/$e\"\n  PARAMS[$INDEX]=$e\n  INDEX=$INDEX+1\n  shift\n  if [ -x \"${TARGET}.sh\" ]; then\n    EXEC=\"${TARGET}.sh\"\n    PARAMS=()\n    INDEX=0\n  fi\ndone\n\necho \">> $EXEC\"\nexec \"$EXEC\" $PARAMS\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "include(\":app\")\nrootProject.name = \"NB4A\"\n"
  }
]